Preflight and publish required outputs through an output session
This commit is contained in:
201
internal/core/diagnostics/output_session_test.go
Normal file
201
internal/core/diagnostics/output_session_test.go
Normal file
@@ -0,0 +1,201 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user