202 lines
7.5 KiB
Go
202 lines
7.5 KiB
Go
package diagnostics
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestPrepareOutputRejectsDirectAndAliasCollisions(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := filepath.Join(dir, "input.json")
|
|
if err := os.WriteFile(input, []byte("source"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
alias := filepath.Join(dir, "alias.json")
|
|
if err := os.Symlink(input, alias); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hardlink := filepath.Join(dir, "hardlink.json")
|
|
if err := os.Link(input, hardlink); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, output := range []string{input, alias, hardlink} {
|
|
if _, err := PrepareOutput(OutputSelection{TranscriptPath: output, ProtectedPaths: []string{input}}); err == nil {
|
|
t.Fatalf("collision accepted: %s", output)
|
|
}
|
|
}
|
|
if _, err := PrepareOutput(OutputSelection{TranscriptPath: filepath.Join(dir, ErrorsFile), ReportDir: dir, ReportingEnabled: true}); err == nil {
|
|
t.Fatal("report/transcript collision accepted")
|
|
}
|
|
if _, err := PrepareOutput(OutputSelection{ReportDir: dir, ReportingEnabled: true, ProtectedPaths: []string{filepath.Join(dir, ErrorsFile)}}); err == nil {
|
|
t.Fatal("report/source collision accepted")
|
|
}
|
|
if err := os.Mkdir(filepath.Join(dir, MarkdownFile), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := PrepareOutput(OutputSelection{ReportDir: dir, ReportingEnabled: true}); err == nil {
|
|
t.Fatal("directory report target accepted")
|
|
}
|
|
if got, err := os.ReadFile(input); err != nil || string(got) != "source" {
|
|
t.Fatalf("source changed: %s %v", got, err)
|
|
}
|
|
}
|
|
|
|
func TestPrepareOutputPreservesExistingTranscriptAndChecksParent(t *testing.T) {
|
|
dir := t.TempDir()
|
|
output := filepath.Join(dir, "out.json")
|
|
if err := os.WriteFile(output, []byte("old content"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
session, err := PrepareOutput(OutputSelection{TranscriptPath: output})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, err := os.ReadFile(output); err != nil || string(got) != "old content" {
|
|
t.Fatalf("preflight truncated output: %s %v", got, err)
|
|
}
|
|
if failures := session.Finalize([]byte("new content"), ReportBundle{}, ReportBundle{}); len(failures) != 0 {
|
|
t.Fatal(failures)
|
|
}
|
|
if got, err := os.ReadFile(output); err != nil || string(got) != "new content" {
|
|
t.Fatalf("publication: %s %v", got, err)
|
|
}
|
|
if info, _ := os.Stat(output); info.Mode().Perm() != 0600 {
|
|
t.Fatalf("permission changed: %v", info.Mode())
|
|
}
|
|
if _, err := PrepareOutput(OutputSelection{TranscriptPath: filepath.Join(dir, "missing", "out.json")}); err == nil {
|
|
t.Fatal("absent parent accepted")
|
|
}
|
|
if _, err := PrepareOutput(OutputSelection{TranscriptPath: dir}); err == nil {
|
|
t.Fatal("directory target accepted")
|
|
}
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, entry := range entries {
|
|
if strings.HasPrefix(entry.Name(), ".audita-") {
|
|
t.Fatalf("probe left behind: %s", entry.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDisabledReportingIgnoresUnusableDirectory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
session, err := PrepareOutput(OutputSelection{TranscriptPath: filepath.Join(dir, "out.json"), ReportDir: filepath.Join(dir, "missing"), ReportingEnabled: false})
|
|
if err != nil || len(session.ReportPaths()) != 0 {
|
|
t.Fatalf("disabled reports probed: %+v %v", session, err)
|
|
}
|
|
if err := session.Initialize(ReportBundle{}, ReportBundle{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "missing")); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("disabled destination created: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEnabledReportingRequiresExistingDirectory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if _, err := PrepareOutput(OutputSelection{ReportDir: filepath.Join(dir, "missing"), ReportingEnabled: true}); err == nil {
|
|
t.Fatal("missing report directory accepted")
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "missing")); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("report parent created: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestStdoutModeInitializesAndFinalizesReports(t *testing.T) {
|
|
dir := t.TempDir()
|
|
session, err := PrepareOutput(OutputSelection{ReportDir: dir, ReportingEnabled: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
initial := ReportBundle{Errors: []byte("in progress errors"), Recoveries: []byte("in progress recoveries"), Markdown: []byte("in progress report")}
|
|
finished := ReportBundle{Errors: []byte("completed errors"), Recoveries: []byte("completed recoveries"), Markdown: []byte("completed report")}
|
|
if err := session.Initialize(initial, ReportBundle{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if session.TranscriptPath() != "" {
|
|
t.Fatal("stdout mode acquired transcript path")
|
|
}
|
|
for i, path := range session.ReportPaths() {
|
|
got, err := os.ReadFile(path)
|
|
if err != nil || string(got) != string(bundleParts(initial)[i]) {
|
|
t.Fatalf("initial %s: %s %v", path, got, err)
|
|
}
|
|
}
|
|
if failures := session.Finalize(nil, finished, ReportBundle{}); len(failures) != 0 {
|
|
t.Fatal(failures)
|
|
}
|
|
for i, path := range session.ReportPaths() {
|
|
got, err := os.ReadFile(path)
|
|
if err != nil || string(got) != string(bundleParts(finished)[i]) {
|
|
t.Fatalf("final %s: %s %v", path, got, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInitializeFailureMarksPriorReportFailedBeforeModelWork(t *testing.T) {
|
|
dir := t.TempDir()
|
|
session, err := PrepareOutput(OutputSelection{ReportDir: dir, ReportingEnabled: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
actual := session.replace
|
|
session.replace = func(path string, payload []byte) error {
|
|
if filepath.Base(path) == RecoveriesFile && string(payload) == "initial" {
|
|
return os.ErrPermission
|
|
}
|
|
return actual(path, payload)
|
|
}
|
|
modelCalled := false
|
|
if err := session.Initialize(ReportBundle{Errors: []byte("initial"), Recoveries: []byte("initial"), Markdown: []byte("initial")}, ReportBundle{Errors: []byte("failed"), Recoveries: []byte("failed"), Markdown: []byte("failed")}); err == nil {
|
|
modelCalled = true
|
|
}
|
|
if modelCalled {
|
|
t.Fatal("model work continued after initialization failure")
|
|
}
|
|
got, err := os.ReadFile(filepath.Join(dir, ErrorsFile))
|
|
if err != nil || string(got) != "failed" {
|
|
t.Fatalf("prior report not failed: %s %v", got, err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, MarkdownFile)); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("later report created: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestFinalizationCollectsFailuresAndRetainsPublishedTranscript(t *testing.T) {
|
|
dir := t.TempDir()
|
|
output := filepath.Join(dir, "output.json")
|
|
session, err := PrepareOutput(OutputSelection{TranscriptPath: output, ReportDir: dir, ReportingEnabled: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := session.Initialize(ReportBundle{Errors: []byte("initial"), Recoveries: []byte("initial"), Markdown: []byte("initial")}, ReportBundle{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
actual := session.replace
|
|
session.replace = func(path string, payload []byte) error {
|
|
if filepath.Base(path) == ErrorsFile && string(payload) == "complete" {
|
|
return os.ErrPermission
|
|
}
|
|
return actual(path, payload)
|
|
}
|
|
complete := ReportBundle{Errors: []byte("complete"), Recoveries: []byte("complete"), Markdown: []byte("complete")}
|
|
failed := ReportBundle{Errors: []byte("failed"), Recoveries: []byte("failed"), Markdown: []byte("failed")}
|
|
failures := session.Finalize([]byte("transcript"), complete, failed)
|
|
if len(failures) != 1 || failures[0].Path != filepath.Join(dir, ErrorsFile) {
|
|
t.Fatalf("failures: %+v", failures)
|
|
}
|
|
if got, err := os.ReadFile(output); err != nil || string(got) != "transcript" {
|
|
t.Fatalf("transcript erased: %s %v", got, err)
|
|
}
|
|
for _, path := range session.ReportPaths() {
|
|
if got, err := os.ReadFile(path); err != nil || string(got) != "failed" {
|
|
t.Fatalf("failed-status pass %s: %s %v", path, got, err)
|
|
}
|
|
}
|
|
}
|