Files
notarius/internal/framework/evidencecontext/evidencecontext_test.go

210 lines
8.2 KiB
Go

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: "multi unit citation includes complete range", refs: []source.SourceRef{ref(3, 7)}, wantIDs: []int{3, 30, 7}},
{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: "mixed source documents", content: `[{"id":1,"kind":"segment","text":"one","ref":{"source_id":"session-one","start_unit_id":1,"end_unit_id":1}},{"id":2,"kind":"segment","text":"two","ref":{"source_id":"session-two","start_unit_id":2,"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
}