107 lines
3.5 KiB
Go
107 lines
3.5 KiB
Go
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)
|
|
}
|
|
resultPath := filepath.Join(runPath, "nested", "result.json")
|
|
data, err := os.ReadFile(resultPath)
|
|
if err != nil || string(data) != "result" {
|
|
t.Fatalf("nested output = %q, %v", data, err)
|
|
}
|
|
for path, want := range map[string]os.FileMode{runPath: 0o755, filepath.Join(runPath, "nested"): 0o755, resultPath: 0o644} {
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if info.Mode().Perm() != want {
|
|
t.Fatalf("%s mode = %#o, want %#o", path, info.Mode().Perm(), want)
|
|
}
|
|
}
|
|
entries, err := os.ReadDir(filepath.Join(runPath, "nested"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, entry := range entries {
|
|
if strings.Contains(entry.Name(), ".tmp-") {
|
|
t.Fatalf("temporary file remains: %s", entry.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
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(), `write output file "blocked/nested.json"`) {
|
|
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)
|
|
}
|
|
}
|