package diagnostics import ( "errors" "os" "path/filepath" "strings" "testing" ) func TestPrepareOutputAllowsInPlaceTranscriptButRejectsReportCollisions(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, InputTranscriptPath: input}); err != nil { t.Fatalf("in-place output rejected: %s: %v", output, err) } } 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.Symlink(input, filepath.Join(dir, ErrorsFile)); err != nil { t.Fatal(err) } if _, err := PrepareOutput(OutputSelection{ReportDir: dir, ReportingEnabled: true, InputTranscriptPath: input}); err == nil { t.Fatal("report/input alias accepted") } if err := os.Remove(filepath.Join(dir, ErrorsFile)); err != nil { t.Fatal(err) } 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{}, func([]FileFailure) ReportBundle { return 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 TestAtomicReplacementCleansTemporaryFileAfterRenameFailure(t *testing.T) { dir := t.TempDir() blocked := filepath.Join(dir, "directory-target") if err := os.Mkdir(blocked, 0700); err != nil { t.Fatal(err) } if err := atomicReplace(blocked, []byte("new content")); err == nil { t.Fatal("directory replacement unexpectedly succeeded") } entries, err := os.ReadDir(dir) if err != nil { t.Fatal(err) } for _, entry := range entries { if strings.HasPrefix(entry.Name(), ".audita-write-") { t.Fatalf("replacement left temporary file: %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, func([]FileFailure) ReportBundle { return 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, func([]FileFailure) ReportBundle { return 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) } } } func TestDebugRunDirectoryUsesSuppliedIDAndIsRetained(t *testing.T) { parent := filepath.Join(t.TempDir(), "debug") if _, err := os.Stat(parent); !errors.Is(err, os.ErrNotExist) { t.Fatalf("unexpected parent: %v", err) } run, err := NewRunDirectory(parent, "run-123", "secret") if err != nil { t.Fatal(err) } if run.Path() != filepath.Join(parent, "run-123") { t.Fatalf("run ID changed: %s", run.Path()) } if _, err := NewRunDirectory(parent, "run-123"); err == nil { t.Fatal("duplicate run ID accepted") } if err := run.WriteJSONArtifact(ArtifactCorrectionLedger, map[string]string{"proposal": "secret"}); err != nil { t.Fatal(err) } contents, err := os.ReadFile(filepath.Join(run.Path(), ArtifactCorrectionLedger)) if err != nil || strings.Contains(string(contents), "secret") { t.Fatalf("debug artifact: %s %v", contents, err) } if _, err := os.Stat(run.Path()); err != nil { t.Fatalf("debug directory removed: %v", err) } }