2026-07-27 18:11:49 +00:00
package evidencecontext
import (
"bytes"
"math"
"os"
"reflect"
"strings"
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
)
2026-08-09 19:42:33 +00:00
func TestBuildSelectsExpandedSourceUnitUnion ( t * testing . T ) {
2026-07-27 18:11:49 +00:00
for _ , test := range [ ] struct {
2026-08-09 19:42:33 +00:00
name string
window int
refs [ ] source . SourceRef
wantIDs [ ] int
2026-07-27 18:11:49 +00:00
} {
2026-08-09 19:42:33 +00:00
{ name : "zero window" , refs : [ ] source . SourceRef { ref ( 3 , 3 ) } , wantIDs : [ ] int { 3 } } ,
2026-08-09 20:04:46 +00:00
{ name : "multi unit citation includes complete range" , refs : [ ] source . SourceRef { ref ( 3 , 7 ) } , wantIDs : [ ] int { 3 , 30 , 7 } } ,
2026-08-09 19:42:33 +00:00
{ 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 { } } ,
2026-07-27 18:11:49 +00:00
} {
t . Run ( test . name , func ( t * testing . T ) {
2026-08-09 19:42:33 +00:00
got , err := Build ( BuildRequest { Source : testDocument ( t ) , WindowUnits : test . window , SourceRefs : test . refs } )
2026-07-27 18:11:49 +00:00
if err != nil {
t . Fatalf ( "Build() error = %v" , err )
}
2026-08-09 19:42:33 +00:00
if got == nil {
t . Fatal ( "Build() returned a nil document" )
2026-07-27 18:11:49 +00:00
}
2026-08-09 19:42:33 +00:00
if actual := unitIDs ( got ) ; ! reflect . DeepEqual ( actual , test . wantIDs ) {
t . Fatalf ( "unit IDs = %#v, want %#v" , actual , test . wantIDs )
2026-07-27 18:11:49 +00:00
}
} )
}
}
2026-08-09 19:42:33 +00:00
func TestBuildCopiesSelectedUnitsAndMetadata ( t * testing . T ) {
2026-07-27 18:11:49 +00:00
document := testDocument ( t )
2026-08-09 19:42:33 +00:00
first , err := Build ( BuildRequest { Source : document , WindowUnits : 1 , SourceRefs : [ ] source . SourceRef { ref ( 3 , 3 ) } } )
2026-07-27 18:11:49 +00:00
if err != nil {
t . Fatal ( err )
}
2026-08-09 19:42:33 +00:00
second , err := Build ( BuildRequest { Source : document , WindowUnits : 1 , SourceRefs : [ ] source . SourceRef { ref ( 3 , 3 ) } } )
2026-07-27 18:11:49 +00:00
if err != nil {
t . Fatal ( err )
}
2026-08-09 19:42:33 +00:00
if ! reflect . DeepEqual ( first [ 0 ] , document . Units [ 0 ] ) {
t . Fatalf ( "first unit = %#v, want unchanged source unit %#v" , first [ 0 ] , document . Units [ 0 ] )
2026-07-27 18:11:49 +00:00
}
2026-08-09 19:42:33 +00:00
first [ 0 ] . Metadata [ "nested" ] . ( map [ string ] any ) [ "value" ] = "changed"
2026-07-27 18:11:49 +00:00
if document . Units [ 0 ] . Metadata [ "nested" ] . ( map [ string ] any ) [ "value" ] != "original" {
2026-08-09 19:42:33 +00:00
t . Fatal ( "Build() returned metadata aliases to the source document" )
2026-07-27 18:11:49 +00:00
}
document . Units [ 0 ] . Metadata [ "nested" ] . ( map [ string ] any ) [ "value" ] = "later"
2026-08-09 19:42:33 +00:00
if second [ 0 ] . Metadata [ "nested" ] . ( map [ string ] any ) [ "value" ] != "original" {
t . Fatal ( "Build() retained metadata aliases to the source document" )
2026-07-27 18:11:49 +00:00
}
}
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" } ,
2026-08-09 19:42:33 +00:00
{ name : "invalid reference" , mutate : func ( request * BuildRequest ) { request . SourceRefs = [ ] source . SourceRef { ref ( 99 , 99 ) } } , want : "source reference[0]" } ,
2026-07-27 18:11:49 +00:00
} {
t . Run ( test . name , func ( t * testing . T ) {
2026-08-09 19:42:33 +00:00
request := BuildRequest { Source : testDocument ( t ) , SourceRefs : [ ] source . SourceRef { ref ( 3 , 3 ) } }
2026-07-27 18:11:49 +00:00
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 )
}
} )
}
}
2026-08-09 19:42:33 +00:00
func TestCodecRoundTripsFixtureAndOwnsValues ( t * testing . T ) {
2026-07-27 18:11:49 +00:00
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 )
}
2026-08-09 19:42:33 +00:00
value [ 0 ] . Text = "changed"
2026-08-09 01:34:26 +00:00
decoded , err := codec . Decode ( encoded )
2026-07-27 18:11:49 +00:00
if err != nil {
t . Fatal ( err )
}
2026-08-09 19:42:33 +00:00
if decoded [ 0 ] . Text != "The party meets Rowan." {
2026-08-09 01:34:26 +00:00
t . Fatal ( "Encode() retained mutable document storage" )
2026-07-27 18:11:49 +00:00
}
2026-08-09 19:42:33 +00:00
built , err := Build ( BuildRequest { Source : testDocument ( t ) , WindowUnits : 1 , SourceRefs : [ ] source . SourceRef { ref ( 3 , 3 ) } } )
2026-07-27 18:11:49 +00:00
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 )
}
2026-08-09 19:42:33 +00:00
first [ 0 ] . Metadata [ "nested" ] . ( map [ string ] any ) [ "value" ] = "changed"
if second [ 0 ] . Metadata [ "nested" ] . ( map [ string ] any ) [ "value" ] != "original" {
2026-07-27 18:11:49 +00:00
t . Fatal ( "Decode() returned metadata aliases" )
}
}
2026-08-09 19:42:33 +00:00
func TestCodecRejectsInvalidDurablePayloads ( t * testing . T ) {
2026-07-27 18:11:49 +00:00
for _ , test := range [ ] struct {
2026-08-09 19:42:33 +00:00
name string
content string
2026-07-27 18:11:49 +00:00
} {
2026-08-09 19:42:33 +00:00
{ 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}}] ` } ,
2026-08-09 20:04:46 +00:00
{ 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}}] ` } ,
2026-08-09 19:42:33 +00:00
{ 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 : ` [] [] ` } ,
2026-07-27 18:11:49 +00:00
} {
t . Run ( test . name , func ( t * testing . T ) {
2026-08-09 19:42:33 +00:00
if _ , err := New ( ) . Decode ( [ ] byte ( test . content ) ) ; err == nil {
2026-07-27 18:11:49 +00:00
t . Fatal ( "Decode() error = nil, want strict payload rejection" )
}
} )
}
2026-08-09 19:42:33 +00:00
if _ , err := New ( ) . Encode ( nil ) ; err == nil {
t . Fatal ( "Encode(nil) error = nil, want array rejection" )
2026-07-27 18:11:49 +00:00
}
}
2026-08-09 19:42:33 +00:00
func TestSerializeUsesFixedArtifactIdentityAndEmptyArray ( t * testing . T ) {
artifact , err := Serialize ( BuildRequest { Source : testDocument ( t ) } )
2026-07-27 18:11:49 +00:00
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 )
}
2026-08-09 19:42:33 +00:00
if string ( artifact . Content ) != "[]" {
t . Fatalf ( "Serialize() content = %s, want []" , artifact . Content )
}
2026-07-27 18:11:49 +00:00
decoded , err := New ( ) . Decode ( artifact . Content )
2026-08-09 19:42:33 +00:00
if err != nil || decoded == nil || len ( decoded ) != 0 {
t . Fatalf ( "Decode(Serialize()) = %#v, %v; want explicit empty array" , decoded , err )
2026-07-27 18:11:49 +00:00
}
}
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 }
}
2026-08-09 19:42:33 +00:00
func unitIDs ( units Document ) [ ] int {
values := make ( [ ] int , len ( units ) )
for index , unit := range units {
values [ index ] = unit . ID
2026-07-27 18:11:49 +00:00
}
return values
}