package pitransport import ( "encoding/json" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" ) func TestSafeRelPathRejectsEscape(t *testing.T) { if SafeRelPath("/etc/passwd") || SafeRelPath("../x") || SafeRelPath("a/../../b") || SafeRelPath("./../x") { t.Fatal("escape") } if !SafeRelPath("output.md") || !SafeRelPath("tasks/01_draft/output.md") { t.Fatal("relative deliverable") } } func TestResultV1FailClosedOnBadDigest(t *testing.T) { body := []byte("hello") r := BuildResultV1("run-1", "success", false, "", []struct { RelPath string Content []byte }{{"output.md", body}}) patches := map[string][]byte{"output.md": []byte("tampered")} if err := r.Validate("run-1", patches); err == nil { t.Fatal("tamper must fail closed") } patches["output.md"] = body if err := r.Validate("run-1", patches); err != nil { t.Fatal(err) } if err := r.Validate("other", patches); err == nil { t.Fatal("run mismatch") } } func TestResultV1RejectsUnknownMajor(t *testing.T) { r := ResultV1{SchemaVersion: 99, RunID: "r", Outcome: "success", ContentDigest: "x"} if err := r.Validate("r", nil); err == nil { t.Fatal("unknown major") } } func TestResultV1RejectsUnknownErrorCode(t *testing.T) { r := BuildResultV1("run-1", "error", true, "DROP TABLE users", nil) if err := r.Validate("run-1", map[string][]byte{}); err == nil { t.Fatal("arbitrary error_code must fail closed") } } func TestResultV1RejectsMissingMemberBody(t *testing.T) { r := BuildResultV1("run-1", "success", false, "", []struct { RelPath string Content []byte }{{"output.md", []byte("hello")}}) if err := r.Validate("run-1", map[string][]byte{}); err == nil { t.Fatal("member without patch body must fail closed") } } func TestTypedRetryableAllowlist(t *testing.T) { ok := BuildResultV1("r", "error", true, "agent.transient", nil) if !TypedRetryable(ok) { t.Fatal("agent.transient retryable") } denied := BuildResultV1("r", "error", true, "agent.prompt_empty", nil) if TypedRetryable(denied) { t.Fatal("agent.prompt_empty must not retry") } } func TestMembersDigestMatchesNodeCanonicalJSON(t *testing.T) { r := BuildResultV1("run-js", "success", false, "", []struct { RelPath string Content []byte }{{"output.md", []byte("hello")}}) script := `import { membersDigest } from './adapters/pitransport/testdata/result-v1.mjs'; const members = ` + mustJSON(t, r.Members) + `; process.stdout.write(membersDigest(members)); ` _, thisFile, _, _ := runtime.Caller(0) root := filepath.Join(filepath.Dir(thisFile), "..", "..") cmd := exec.Command("node", "--input-type=module", "-e", script) cmd.Dir = root out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("node digest: %v %s", err, out) } if got := strings.TrimSpace(string(out)); got != r.ContentDigest { t.Fatalf("js digest %s != go %s", got, r.ContentDigest) } } func mustJSON(t *testing.T, v any) string { t.Helper() b, err := json.Marshal(v) if err != nil { t.Fatal(err) } return string(b) } func TestGoldenResultV1Fixture(t *testing.T) { raw, err := os.ReadFile(filepath.Join("testdata", "pi-run-result-v1.golden.json")) if err != nil { t.Fatal(err) } var rv ResultV1 if err := json.Unmarshal(raw, &rv); err != nil { t.Fatal(err) } if err := rv.Validate("golden-run-1", map[string][]byte{"output.md": []byte("hello")}); err != nil { t.Fatal(err) } built := BuildResultV1("golden-run-1", "success", false, "", []struct { RelPath string Content []byte }{{"output.md", []byte("hello")}}) if built.ContentDigest != rv.ContentDigest { t.Fatalf("golden digest drift go=%s file=%s", built.ContentDigest, rv.ContentDigest) } }