Files
audita/internal/core/diagnostics/output_session_test.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)
}
}
}