package approvalfact import ( "bytes" "encoding/json" "os" "strings" "testing" "time" ) type vectors struct { Native []struct { Binding json.RawMessage `json:"binding"` Canonical string `json:"canonical_binding_utf8"` Digest string `json:"native_digest"` } `json:"native_binding_vectors"` Claims []struct { Name string `json:"name"` Now time.Time `json:"validation_time"` Submitted json.RawMessage `json:"submitted_claim"` Observed json.RawMessage `json:"observed_claim"` Binding json.RawMessage `json:"expected_binding"` PDP string `json:"expected_pdp_digest"` Accept bool `json:"expected_accept"` } `json:"claim_cases"` Transport []struct { Name string `json:"name"` Body string `json:"body_utf8"` } `json:"transport_cases"` } func load(t *testing.T) vectors { t.Helper() raw, e := os.ReadFile("testdata/owner-vectors.json") if e != nil { t.Fatal(e) } var v vectors if e = json.Unmarshal(raw, &v); e != nil { t.Fatal(e) } return v } func TestProducerCanonicalVectors(t *testing.T) { for _, v := range load(t).Native { t.Run(v.Digest, func(t *testing.T) { raw, d, e := CanonicalBinding(v.Binding) if e != nil || string(raw) != v.Canonical || d != v.Digest { t.Fatalf("producer parity: %q %s %v, want %q %s", raw, d, e, v.Canonical, v.Digest) } }) } } func TestProducerClaimVectors(t *testing.T) { for _, v := range load(t).Claims { t.Run(v.Name, func(t *testing.T) { var id struct { ID string `json:"approval_id"` } if e := json.Unmarshal(v.Submitted, &id); e != nil { t.Fatal(e) } expected := Expected{id.ID, v.Binding, v.PDP} old, e1 := ValidateClaim(v.Submitted, expected, v.Now) fresh, e2 := ValidateClaim(v.Observed, expected, v.Now) accepted := e1 == nil && e2 == nil && MatchObserved(old, fresh) == nil if accepted != v.Accept { t.Fatalf("accepted=%v want=%v original=%v observed=%v", accepted, v.Accept, e1, e2) } if accepted { raw := old.RawJSON() if !bytes.Equal(raw, v.Submitted) { t.Fatal("original bytes changed") } raw[0] = 'x' if old.RawJSON()[0] != '{' { t.Fatal("mutable fact storage") } if !old.Deadline().Before(fresh.Deadline()) { t.Fatal("refresh must not extend original deadline") } } }) } } func TestStrictJSONRefusals(t *testing.T) { for _, raw := range []string{`{"a":1,"a":2}`, `{"a":{"x":1,"x":2}}`, `{"a":NaN}`, `{"a":1e999}`, `{"a":"\ud800"}`, `{"a":"\udfff"}`, `{"a":"\ud800x"}`, `{"a":1} {}`, `{"a":"` + string([]byte{0xff}) + `"}`, strings.Repeat("[", 130) + strings.Repeat("]", 130), strings.Repeat(" ", MaxJSONBytes+1)} { if _, e := DecodeJSON([]byte(raw)); e == nil { t.Fatalf("accepted malformed JSON %.80q", raw) } } } func TestUnicodeAndControlEncoding(t *testing.T) { for _, raw := range []string{`{"v":"\ud83d\ude00"}`, `{"v":"escaped \\\" quote"}`, `{"v":"\u0000\b\f\n\r\t"}`} { if _, e := DecodeJSON([]byte(raw)); e != nil { t.Fatalf("refused valid Unicode %q", raw) } } } func TestGoldenDuplicateAndNonfiniteBodies(t *testing.T) { for _, v := range load(t).Transport { if v.Name == "duplicate_valid_now" || v.Name == "nonfinite_json" { if _, e := DecodeJSON([]byte(v.Body)); e == nil { t.Fatal(v.Name) } } } } func TestInvalidExpectedAndZeroFacts(t *testing.T) { v := load(t).Claims[0] if _, e := ValidateClaim(v.Submitted, Expected{}, v.Now); e == nil { t.Fatal("missing expected act accepted") } if MatchObserved(Fact{}, Fact{}) == nil { t.Fatal("zero facts accepted") } expected := Expected{"11111111-1111-4111-8111-111111111111", v.Binding, v.PDP} a, e := ValidateClaim(v.Submitted, expected, v.Now) if e != nil { t.Fatal(e) } b, e := ValidateClaim(v.Observed, expected, v.Now.Add(time.Second)) if e != nil { t.Fatal(e) } if MatchObserved(a, b) == nil { t.Fatal("different validation instants accepted") } } func TestTimestampPrecisionAndOffsetRefusals(t *testing.T) { for _, value := range []string{"2026-08-29T12:00:00,1Z", "2026-08-29T12:00:00+24:00", "2026-08-29T12:00:00+00:60", "2026-08-29T12:00:00.0000000001Z", "2026-08-29T12:00:00"} { if _, err := stamp(value); err == nil { t.Fatalf("accepted unsupported timestamp %q", value) } } }