2026-07-23 20:17:18 +00:00
package shape
import (
"context"
"reflect"
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
)
func TestValidatorRequiresSceneShapeAndExactlyOneExtractionRecord ( t * testing . T ) {
valid := list ( scene ( "one" , dnd . SceneKindNarrative , "Arrival" , "The party arrives." , 1 , 1 ) )
validator := New ( Options { } )
for _ , test := range [ ] struct {
name string
value dnd . SceneDescriptionList
stage string
approved bool
} {
{ name : "valid normalization" , value : valid , approved : true } ,
{ name : "valid extraction" , value : valid , stage : string ( pipeline . StageExtract ) , approved : true } ,
{ name : "multiple extraction records" , value : list ( valid . Scenes [ 0 ] , scene ( "two" , dnd . SceneKindMeta , "Rules" , "The table checks rules." , 2 , 2 ) ) , stage : string ( pipeline . StageExtract ) } ,
{ name : "nil list" , value : dnd . SceneDescriptionList { } } ,
{ name : "empty list" , value : dnd . SceneDescriptionList { Scenes : [ ] dnd . SceneDescription { } } } ,
{ name : "blank ID" , value : list ( scene ( " " , dnd . SceneKindNarrative , "Arrival" , "The party arrives." , 1 , 1 ) ) } ,
{ name : "unsupported kind" , value : list ( scene ( "one" , "other" , "Arrival" , "The party arrives." , 1 , 1 ) ) } ,
2026-07-24 14:40:54 +00:00
{ name : "empty title" , value : list ( scene ( "one" , dnd . SceneKindNarrative , "" , "The party arrives." , 1 , 1 ) ) } ,
{ name : "whitespace-only title" , value : list ( scene ( "one" , dnd . SceneKindNarrative , " " , "The party arrives." , 1 , 1 ) ) } ,
{ name : "empty summary" , value : list ( scene ( "one" , dnd . SceneKindNarrative , "Arrival" , "" , 1 , 1 ) ) } ,
{ name : "whitespace-only summary" , value : list ( scene ( "one" , dnd . SceneKindNarrative , "Arrival" , " " , 1 , 1 ) ) } ,
2026-07-23 20:17:18 +00:00
{ name : "untrimmed prose" , value : list ( scene ( "one" , dnd . SceneKindNarrative , " Arrival " , "The party arrives." , 1 , 1 ) ) } ,
{ name : "empty source reference" , value : dnd . SceneDescriptionList { Scenes : [ ] dnd . SceneDescription { { ID : "one" , Kind : dnd . SceneKindNarrative , Title : "Arrival" , Summary : "The party arrives." } } } } ,
} {
t . Run ( test . name , func ( t * testing . T ) {
result , err := validator . Validate ( context . Background ( ) , contracts . TypedValidationRequest [ dnd . SceneDescriptionList ] { Stage : test . stage , Value : test . value } )
if err != nil || result . Approved != test . approved {
t . Fatalf ( "Validate() = %#v, %v; want approved=%t" , result , err , test . approved )
}
} )
}
}
func TestValidatorContractAndRegistration ( t * testing . T ) {
registry := pipeline . NewValidatorRegistry ( )
if err := Register ( registry ) ; err != nil {
t . Fatalf ( "Register() error = %v" , err )
}
if _ , ok := registry . Spec ( Key ) ; ! ok {
t . Fatalf ( "registry has no spec for %q" , Key )
}
if _ , err := DecodeOptions ( map [ string ] any { "unexpected" : true } ) ; err == nil {
t . Fatal ( "DecodeOptions() accepted unknown options" )
}
validator := New ( Options { } )
if got , want := validator . CheckpointFingerprints ( ) , [ ] pipeline . CheckpointFingerprint { { Name : "policy" , Value : policy } } ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "fingerprints = %#v, want %#v" , got , want )
}
}
func list ( scenes ... dnd . SceneDescription ) dnd . SceneDescriptionList {
return dnd . SceneDescriptionList { Scenes : scenes }
}
func scene ( id string , kind dnd . SceneKind , title , summary string , start , end int ) dnd . SceneDescription {
return dnd . SceneDescription { ID : id , Kind : kind , Title : title , Summary : summary , SourceRef : source . SourceRef { SourceID : "session" , StartUnitID : start , EndUnitID : end } }
}