package evidencecontext import ( "bytes" "math" "os" "reflect" "strings" "testing" "gitea.maximumdirect.net/eric/notarius/internal/core/source" ) func TestBuildSelectsExpandedSourceUnitUnion(t *testing.T) { for _, test := range []struct { name string window int refs []source.SourceRef wantIDs []int }{ {name: "zero window", refs: []source.SourceRef{ref(3, 3)}, wantIDs: []int{3}}, {name: "non monotonic IDs use document positions", window: 1, refs: []source.SourceRef{ref(3, 3)}, wantIDs: []int{10, 3, 30}}, {name: "boundary clamping", window: 1, refs: []source.SourceRef{ref(10, 10), ref(50, 50)}, wantIDs: []int{10, 3, 7, 50}}, {name: "overlapping and adjacent windows merge", window: 1, refs: []source.SourceRef{ref(3, 3), ref(30, 30), ref(30, 30)}, wantIDs: []int{10, 3, 30, 7}}, {name: "adjacent expanded ranges merge", window: 1, refs: []source.SourceRef{ref(10, 10), ref(7, 7)}, wantIDs: []int{10, 3, 30, 7, 50}}, {name: "largest window clips without overflow", window: math.MaxInt, refs: []source.SourceRef{ref(30, 30)}, wantIDs: []int{10, 3, 30, 7, 50}}, {name: "no references returns an initialized empty document", wantIDs: []int{}}, } { t.Run(test.name, func(t *testing.T) { got, err := Build(BuildRequest{Source: testDocument(t), WindowUnits: test.window, SourceRefs: test.refs}) if err != nil { t.Fatalf("Build() error = %v", err) } if got == nil { t.Fatal("Build() returned a nil document") } if actual := unitIDs(got); !reflect.DeepEqual(actual, test.wantIDs) { t.Fatalf("unit IDs = %#v, want %#v", actual, test.wantIDs) } }) } } func TestBuildCopiesSelectedUnitsAndMetadata(t *testing.T) { document := testDocument(t) first, err := Build(BuildRequest{Source: document, WindowUnits: 1, SourceRefs: []source.SourceRef{ref(3, 3)}}) if err != nil { t.Fatal(err) } second, err := Build(BuildRequest{Source: document, WindowUnits: 1, SourceRefs: []source.SourceRef{ref(3, 3)}}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(first[0], document.Units[0]) { t.Fatalf("first unit = %#v, want unchanged source unit %#v", first[0], document.Units[0]) } first[0].Metadata["nested"].(map[string]any)["value"] = "changed" if document.Units[0].Metadata["nested"].(map[string]any)["value"] != "original" { t.Fatal("Build() returned metadata aliases to the source document") } document.Units[0].Metadata["nested"].(map[string]any)["value"] = "later" if second[0].Metadata["nested"].(map[string]any)["value"] != "original" { t.Fatal("Build() retained metadata aliases to the source document") } } func TestBuildRejectsInvalidInputs(t *testing.T) { for _, test := range []struct { name string mutate func(*BuildRequest) want string }{ {name: "negative window", mutate: func(request *BuildRequest) { request.WindowUnits = -1 }, want: "window_units"}, {name: "source digest mismatch", mutate: func(request *BuildRequest) { request.Source.Digest = "sha256:" + strings.Repeat("0", 64) }, want: "does not match"}, {name: "invalid reference", mutate: func(request *BuildRequest) { request.SourceRefs = []source.SourceRef{ref(99, 99)} }, want: "source reference[0]"}, } { t.Run(test.name, func(t *testing.T) { request := BuildRequest{Source: testDocument(t), SourceRefs: []source.SourceRef{ref(3, 3)}} test.mutate(&request) if _, err := Build(request); err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("Build() error = %v, want %q", err, test.want) } }) } } func TestCodecRoundTripsFixtureAndOwnsValues(t *testing.T) { fixture, err := os.ReadFile("testdata/source_evidence_context.v1.json") if err != nil { t.Fatal(err) } codec := New() value, err := codec.Decode(fixture) if err != nil { t.Fatalf("Decode(fixture) error = %v", err) } encoded, err := codec.Encode(value) if err != nil { t.Fatal(err) } if !bytes.Equal(encoded, bytes.TrimSpace(fixture)) { t.Fatalf("fixture does not use canonical encoding\nwant: %s\n got: %s", fixture, encoded) } value[0].Text = "changed" decoded, err := codec.Decode(encoded) if err != nil { t.Fatal(err) } if decoded[0].Text != "The party meets Rowan." { t.Fatal("Encode() retained mutable document storage") } built, err := Build(BuildRequest{Source: testDocument(t), WindowUnits: 1, SourceRefs: []source.SourceRef{ref(3, 3)}}) if err != nil { t.Fatal(err) } content, err := codec.Encode(built) if err != nil { t.Fatal(err) } first, err := codec.Decode(content) if err != nil { t.Fatal(err) } second, err := codec.Decode(content) if err != nil { t.Fatal(err) } first[0].Metadata["nested"].(map[string]any)["value"] = "changed" if second[0].Metadata["nested"].(map[string]any)["value"] != "original" { t.Fatal("Decode() returned metadata aliases") } } func TestCodecRejectsInvalidDurablePayloads(t *testing.T) { for _, test := range []struct { name string content string }{ {name: "null", content: "null"}, {name: "wrapper object", content: `{"units":[]}`}, {name: "missing required unit field", content: `[{"id":1,"kind":"segment","ref":{"source_id":"session","start_unit_id":1,"end_unit_id":1}}]`}, {name: "unknown unit field", content: `[{"id":1,"kind":"segment","text":"text","ref":{"source_id":"session","start_unit_id":1,"end_unit_id":1},"unknown":true}]`}, {name: "unknown reference field", content: `[{"id":1,"kind":"segment","text":"text","ref":{"source_id":"session","start_unit_id":1,"end_unit_id":1,"unknown":true}}]`}, {name: "invalid self reference", content: `[{"id":1,"kind":"segment","text":"text","ref":{"source_id":"session","start_unit_id":1,"end_unit_id":2}}]`}, {name: "duplicate units", content: `[{"id":1,"kind":"segment","text":"one","ref":{"source_id":"session","start_unit_id":1,"end_unit_id":1}},{"id":1,"kind":"segment","text":"two","ref":{"source_id":"session","start_unit_id":1,"end_unit_id":1}}]`}, {name: "multiple JSON values", content: `[] []`}, } { t.Run(test.name, func(t *testing.T) { if _, err := New().Decode([]byte(test.content)); err == nil { t.Fatal("Decode() error = nil, want strict payload rejection") } }) } if _, err := New().Encode(nil); err == nil { t.Fatal("Encode(nil) error = nil, want array rejection") } } func TestSerializeUsesFixedArtifactIdentityAndEmptyArray(t *testing.T) { artifact, err := Serialize(BuildRequest{Source: testDocument(t)}) if err != nil { t.Fatal(err) } if artifact.Kind != ArtifactKind || artifact.MediaType != MediaType || artifact.Schema.ID != SchemaID || artifact.Schema.Name != SchemaName || artifact.Schema.Version != SchemaVersion { t.Fatalf("Serialize() = %#v, want fixed artifact identity", artifact) } if string(artifact.Content) != "[]" { t.Fatalf("Serialize() content = %s, want []", artifact.Content) } decoded, err := New().Decode(artifact.Content) if err != nil || decoded == nil || len(decoded) != 0 { t.Fatalf("Decode(Serialize()) = %#v, %v; want explicit empty array", decoded, err) } } func testDocument(t *testing.T) *source.SourceDocument { t.Helper() document := &source.SourceDocument{ ID: "session", Kind: "transcript", Format: "application/json", Units: []source.SourceUnit{ {ID: 10, Kind: "segment", Text: "first", Ref: ref(10, 10), Metadata: map[string]any{"nested": map[string]any{"value": "original"}}}, {ID: 3, Kind: "segment", Text: "second", Ref: ref(3, 3)}, {ID: 30, Kind: "segment", Text: "third", Ref: ref(30, 30)}, {ID: 7, Kind: "segment", Text: "fourth", Ref: ref(7, 7)}, {ID: 50, Kind: "segment", Text: "fifth", Ref: ref(50, 50)}, }, } digest, err := source.DigestDocument(document) if err != nil { t.Fatal(err) } document.Digest = digest return document } func ref(start, end int) source.SourceRef { return source.SourceRef{SourceID: "session", StartUnitID: start, EndUnitID: end} } func unitIDs(units Document) []int { values := make([]int, len(units)) for index, unit := range units { values[index] = unit.ID } return values }