2026-07-23 20:09:59 +00:00
package scenedescriptions
import (
"context"
"errors"
"reflect"
"strings"
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
)
func TestExtractMapsExactlyOneSceneToTheChunk ( t * testing . T ) {
client := & fakeSceneDescriptionsLLMClient { response : extractionResponse {
Kind : dnd . SceneKindCombat , Title : " Fight at the Watchtower " , Summary : " Bandits attack the party. " ,
} }
request := extractionRequest ( )
result , err := newExtractor ( t , client ) . Extract ( context . Background ( ) , request )
if err != nil {
t . Fatalf ( "Extract() error = %v, want nil" , err )
}
want := dnd . SceneDescriptionList { Scenes : [ ] dnd . SceneDescription { {
ID : request . Chunk . ID , SourceRef : request . Chunk . Ref , Kind : dnd . SceneKindCombat ,
Title : "Fight at the Watchtower" , Summary : "Bandits attack the party." ,
} } }
if ! reflect . DeepEqual ( result . Value , want ) {
t . Fatalf ( "Value = %#v, want %#v" , result . Value , want )
}
if len ( client . requests ) != 1 {
t . Fatalf ( "LLM calls = %d, want 1" , len ( client . requests ) )
}
completed := client . requests [ 0 ]
if completed . StageName != Key || completed . PromptID != PromptID || completed . PromptVersion != SchemaVersion || completed . ProfileID != "profile-scene-descriptions" || completed . SessionID != "session-123" {
t . Fatalf ( "LLM request identity = %#v" , completed )
}
transcript := completed . Inputs [ "transcript" ]
if transcript . Name != "transcript" || transcript . MediaType != "application/json" || transcript . Digest != "sha256:chunk" || transcript . OriginURI != "file:///session-alpha.json" || string ( transcript . Content ) != string ( request . Chunk . Content ) {
t . Fatalf ( "transcript input = %#v, want chunk-scoped material" , transcript )
}
}
func TestExtractPassesOptionalReferencesAndUsesEmptyPlaceholders ( t * testing . T ) {
client := & fakeSceneDescriptionsLLMClient { response : extractionResponse { Kind : dnd . SceneKindNarrative , Title : "Arrival" , Summary : "The party arrives." } }
req := extractionRequest ( )
if _ , err := newExtractor ( t , client ) . Extract ( context . Background ( ) , req ) ; err != nil {
t . Fatalf ( "Extract() error = %v, want nil" , err )
}
for _ , slot := range [ ] string { "players" , "party" , "glossary" } {
if got := string ( client . requests [ 0 ] . Inputs [ slot ] . Content ) ; got != " " {
t . Fatalf ( "empty %s input = %q, want explicit placeholder" , slot , got )
}
}
req . References = contracts . ReferenceSet { Slots : map [ string ] contracts . ResolvedReferenceSlot {
"players" : { Slot : contracts . ReferenceSlot { Name : "players" } , Items : [ ] contracts . ReferenceItem { { SlotName : "players" , Content : [ ] byte ( "Dana: Mira" ) } } } ,
"party" : { Slot : contracts . ReferenceSlot { Name : "party" } , Items : [ ] contracts . ReferenceItem { { SlotName : "party" , Content : [ ] byte ( "Mira: ranger" ) } } } ,
"glossary" : { Slot : contracts . ReferenceSlot { Name : "glossary" } , Items : [ ] contracts . ReferenceItem { { SlotName : "glossary" , Content : [ ] byte ( "Greencloak: local title" ) } } } ,
} }
if _ , err := newExtractor ( t , client ) . Extract ( context . Background ( ) , req ) ; err != nil {
t . Fatalf ( "Extract() error = %v, want nil" , err )
}
inputs := client . requests [ 1 ] . Inputs
if string ( inputs [ "players" ] . Content ) != "Dana: Mira" || string ( inputs [ "party" ] . Content ) != "Mira: ranger" || string ( inputs [ "glossary" ] . Content ) != "Greencloak: local title" {
t . Fatalf ( "reference inputs = %#v" , inputs )
}
if strings . Contains ( string ( inputs [ "transcript" ] . Content ) , "local title" ) {
t . Fatal ( "transcript input contains reference content" )
}
}
2026-07-24 14:40:54 +00:00
func TestExtractReturnsSemanticallyInvalidResponseForDeterministicValidation ( t * testing . T ) {
schema , err := loadResponseSchema ( )
if err != nil {
t . Fatalf ( "loadResponseSchema() error = %v" , err )
}
2026-08-29 01:24:45 +00:00
if err := validateJSONSchema ( t , map [ string ] any { "kind" : "narrative" , "title" : " " , "summary" : "" } , schema . JSONSchema ) ; err != nil {
2026-07-24 14:40:54 +00:00
t . Fatalf ( "semantic candidate rejected by private schema: %v" , err )
}
2026-07-23 20:09:59 +00:00
client := & fakeSceneDescriptionsLLMClient { response : extractionResponse {
2026-08-29 01:24:45 +00:00
Kind : dnd . SceneKindNarrative , Title : " " , Summary : "" ,
2026-07-23 20:09:59 +00:00
} }
result , err := newExtractor ( t , client ) . Extract ( context . Background ( ) , extractionRequest ( ) )
if err != nil {
t . Fatalf ( "Extract() error = %v, want nil" , err )
}
scene := result . Value . Scenes [ 0 ]
2026-08-29 01:24:45 +00:00
if scene . Kind != dnd . SceneKindNarrative || scene . Title != "" || scene . Summary != "" {
2026-07-24 14:40:54 +00:00
t . Fatalf ( "scene = %#v, want semantic candidates returned for deterministic validation" , scene )
2026-07-23 20:09:59 +00:00
}
}
func TestExtractValidatesRequestsAndSurfacesProviderFailures ( t * testing . T ) {
request := extractionRequest ( )
extractor := newExtractor ( t , & fakeSceneDescriptionsLLMClient { response : extractionResponse { Kind : dnd . SceneKindMeta , Title : "Table talk" , Summary : "The group discusses rules." } } )
2026-07-24 14:38:42 +00:00
var nilExtractor * Extractor
2026-07-23 20:09:59 +00:00
for _ , test := range [ ] struct {
2026-07-24 14:38:42 +00:00
name string
extractor * Extractor
req contracts . TypedExtractionRequest
want string
2026-07-23 20:09:59 +00:00
} {
2026-07-24 14:38:42 +00:00
{ name : "nil extractor" , extractor : nilExtractor , req : request , want : "extractor" } ,
{ name : "nil LLM client" , extractor : & Extractor { } , req : request , want : "LLM client" } ,
{ name : "wrapped preflight failure" , extractor : extractor , req : mismatchedSourceInputRequest ( request ) , want : "must match chunk" } ,
2026-07-23 20:09:59 +00:00
} {
t . Run ( test . name , func ( t * testing . T ) {
2026-07-24 14:38:42 +00:00
if _ , err := test . extractor . Extract ( context . Background ( ) , test . req ) ; err == nil || ! strings . Contains ( err . Error ( ) , "dnd scene descriptions" ) || ! strings . Contains ( err . Error ( ) , test . want ) {
2026-07-23 20:09:59 +00:00
t . Fatalf ( "Extract() error = %v, want contextual validation error" , err )
}
} )
}
_ , err := newExtractor ( t , & fakeSceneDescriptionsLLMClient { err : errors . New ( "provider unavailable" ) } ) . Extract ( context . Background ( ) , request )
if err == nil || ! strings . Contains ( err . Error ( ) , "dnd scene descriptions" ) || ! strings . Contains ( err . Error ( ) , "provider unavailable" ) {
t . Fatalf ( "provider Extract() error = %v, want contextual provider error" , err )
}
}
func TestMapResponseUsesOnlyChunkIdentityAndRange ( t * testing . T ) {
chunk := & source . Chunk { ID : "source:chunk:7" , Ref : source . SourceRef { SourceID : "source" , StartUnitID : 12 , EndUnitID : 14 } }
got := mapResponse ( extractionResponse { Kind : dnd . SceneKindRecap , Title : " Recap " , Summary : " Summary " } , chunk )
want := dnd . SceneDescriptionList { Scenes : [ ] dnd . SceneDescription { {
ID : "source:chunk:7" , SourceRef : source . SourceRef { SourceID : "source" , StartUnitID : 12 , EndUnitID : 14 } ,
Kind : dnd . SceneKindRecap , Title : "Recap" , Summary : "Summary" ,
} } }
if ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "mapResponse() = %#v, want %#v" , got , want )
}
}