package cli import ( "os" "path/filepath" "regexp" "strings" "testing" "time" "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" ) func TestDefaultRunIDGeneratorProducesUniqueSafeIDs(t *testing.T) { startedAt := time.Unix(0, 123456789).UTC() pattern := regexp.MustCompile(`^run-123456789-[0-9a-f]{32}$`) seen := make(map[string]struct{}, 256) for i := 0; i < 256; i++ { runID, err := defaultRunIDGenerator(startedAt) if err != nil { t.Fatal(err) } if !pattern.MatchString(runID) { t.Fatalf("run ID %q does not match production format", runID) } if err := validateRunID(runID); err != nil { t.Fatalf("run ID %q is not path-safe: %v", runID, err) } if _, exists := seen[runID]; exists { t.Fatalf("duplicate run ID %q", runID) } seen[runID] = struct{}{} } } func TestWriteOutputFilesSupportsNestedLogicalPaths(t *testing.T) { runPath := filepath.Join(t.TempDir(), "output", "run-safe") if err := writeOutputFiles(runPath, []contracts.OutputFile{{Name: "nested/result.json", Bytes: []byte("result")}}); err != nil { t.Fatal(err) } data, err := os.ReadFile(filepath.Join(runPath, "nested", "result.json")) if err != nil || string(data) != "result" { t.Fatalf("nested output = %q, %v", data, err) } } func TestWriteOutputFilesRejectsUnsafeNamesBeforeAllocatingRunDirectory(t *testing.T) { outputRoot := filepath.Join(t.TempDir(), "output") runPath := filepath.Join(outputRoot, "run-safe") for _, name := range []string{"", "../outside", "/absolute", `nested\\outside`, "nested/../outside"} { t.Run(name, func(t *testing.T) { if err := writeOutputFiles(runPath, []contracts.OutputFile{{Name: "safe.json"}, {Name: name}}); err == nil { t.Fatalf("writeOutputFiles accepted %q", name) } if _, err := os.Stat(outputRoot); !os.IsNotExist(err) { t.Fatalf("output root exists or stat failed after %q: %v", name, err) } }) } } func TestWriteOutputFilesRetainsNewPartialDirectoryAndPreservesSibling(t *testing.T) { outputRoot := filepath.Join(t.TempDir(), "output") siblingPath := filepath.Join(outputRoot, "sibling") if err := os.MkdirAll(siblingPath, 0o755); err != nil { t.Fatal(err) } sentinelPath := filepath.Join(siblingPath, "sentinel") if err := os.WriteFile(sentinelPath, []byte("preserve sibling"), 0o644); err != nil { t.Fatal(err) } runPath := filepath.Join(outputRoot, "run-safe") err := writeOutputFiles(runPath, []contracts.OutputFile{ {Name: "blocked", Bytes: []byte("partial output")}, {Name: "blocked/nested.json", Bytes: []byte("unreachable")}, }) if err == nil || !strings.Contains(err.Error(), "create output directory") { t.Fatalf("writeOutputFiles() error = %v, want later directory failure", err) } if got, err := os.ReadFile(filepath.Join(runPath, "blocked")); err != nil || string(got) != "partial output" { t.Fatalf("partial output = %q, %v", got, err) } if got, err := os.ReadFile(sentinelPath); err != nil || string(got) != "preserve sibling" { t.Fatalf("sibling sentinel = %q, %v", got, err) } }