2026-07-04 13:02:02 +00:00
package scenes
import (
"context"
"encoding/json"
"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/framework/pipeline"
2026-07-17 05:05:48 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
2026-07-04 13:02:02 +00:00
)
func TestNewModuleSpecAndRegister ( t * testing . T ) {
2026-07-17 06:30:56 +00:00
client := & fakeScenesLLMClient { }
chunker := newChunker ( t , client )
2026-07-04 13:02:02 +00:00
if chunker . Key ( ) != Key {
t . Fatalf ( "Key() = %q, want %q" , chunker . Key ( ) , Key )
}
want := pipeline . ModuleSpec {
2026-08-26 23:55:23 +00:00
Key : Key ,
Stage : pipeline . StageChunk ,
ExecutionClass : contracts . ExecutionClassLLMBacked ,
CorrectionProtocol : contracts . CorrectionProtocolSingleResponseV1 ,
Requires : [ ] string { "source.transcript" } ,
Provides : [ ] string { "chunks" } ,
ReferenceSlots : wantReferenceSlots ( ) ,
2026-07-04 13:02:02 +00:00
}
if got := ModuleSpec ( ) ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "ModuleSpec() = %#v, want %#v" , got , want )
}
got := ModuleSpec ( )
got . Requires [ 0 ] = "changed"
got . Provides [ 0 ] = "changed"
2026-07-05 18:48:45 -05:00
got . ReferenceSlots [ 0 ] . AcceptedMediaTypes [ 0 ] = "changed"
2026-07-04 13:02:02 +00:00
if again := ModuleSpec ( ) ; ! reflect . DeepEqual ( again , want ) {
t . Fatalf ( "ModuleSpec() after caller mutation = %#v, want %#v" , again , want )
}
registry := pipeline . NewChunkerRegistry ( )
if err := Register ( registry ) ; err != nil {
t . Fatalf ( "Register() error = %v, want nil" , err )
}
registered , ok := registry . Spec ( Key )
if ! ok {
t . Fatalf ( "Spec(%q) ok = false, want true" , Key )
}
if ! reflect . DeepEqual ( registered , want ) {
t . Fatalf ( "registered spec = %#v, want %#v" , registered , want )
}
2026-07-17 06:30:56 +00:00
built , err := registry . BuildWithRequest ( Key , pipeline . BuildRequest { Dependencies : pipeline . ModuleDependencies { LLM : client } } )
2026-07-04 13:02:02 +00:00
if err != nil {
t . Fatalf ( "Build(%q) error = %v, want nil" , Key , err )
}
if built . Key ( ) != Key {
t . Fatalf ( "built Key() = %q, want %q" , built . Key ( ) , Key )
}
2026-07-05 18:48:45 -05:00
if slots := built . ReferenceSlots ( ) ; ! reflect . DeepEqual ( slots , want . ReferenceSlots ) {
t . Fatalf ( "ReferenceSlots() = %#v, want %#v" , slots , want . ReferenceSlots )
2026-07-05 16:06:47 +00:00
}
2026-07-17 06:30:56 +00:00
if err := registry . ValidateOptions ( Key , map [ string ] any { "unexpected" : true } ) ; err == nil || ! strings . Contains ( err . Error ( ) , "unknown option" ) {
t . Fatalf ( "ValidateOptions() error = %v, want unknown option error" , err )
}
}
func TestConstructionRejectsMissingDependencyAndUnknownOptions ( t * testing . T ) {
if _ , err := New ( nil , Options { } ) ; err == nil || ! strings . Contains ( err . Error ( ) , "LLM client" ) {
t . Fatalf ( "New(nil) error = %v, want LLM client error" , err )
}
if _ , err := DecodeOptions ( map [ string ] any { "unexpected" : true } ) ; err == nil || ! strings . Contains ( err . Error ( ) , "unknown option" ) {
t . Fatalf ( "DecodeOptions() error = %v, want unknown option error" , err )
}
2026-07-04 13:02:02 +00:00
}
func TestRegisterNilRegistryReturnsError ( t * testing . T ) {
err := Register ( nil )
if err == nil {
t . Fatal ( "Register(nil) error = nil, want error" )
}
if ! strings . Contains ( err . Error ( ) , "chunker registry" ) {
t . Fatalf ( "Register(nil) error = %q, want registry context" , err . Error ( ) )
}
}
2026-07-05 18:48:45 -05:00
func wantReferenceSlots ( ) [ ] contracts . ReferenceSlot {
accepted := [ ] string { "application/json" , "application/x-yaml" , "application/yaml" , "text/markdown" , "text/plain" }
return [ ] contracts . ReferenceSlot {
{
Name : "glossary" ,
Description : "Optional campaign glossary reference material used only for scene disambiguation." ,
AcceptedMediaTypes : append ( [ ] string ( nil ) , accepted ... ) ,
} ,
2026-07-06 11:26:21 -05:00
{
Name : "party" ,
Description : "Optional party roster reference material used only for scene disambiguation." ,
AcceptedMediaTypes : append ( [ ] string ( nil ) , accepted ... ) ,
} ,
{
Name : "players" ,
Description : "Optional player list reference material used only for scene disambiguation." ,
AcceptedMediaTypes : append ( [ ] string ( nil ) , accepted ... ) ,
} ,
2026-07-05 18:48:45 -05:00
{
Name : "roster" ,
2026-07-06 11:26:21 -05:00
Description : "Deprecated alias for party roster reference material used only for scene disambiguation." ,
2026-07-05 18:48:45 -05:00
AcceptedMediaTypes : append ( [ ] string ( nil ) , accepted ... ) ,
} ,
}
}
2026-07-24 00:35:10 +00:00
func TestPlanReturnsAnnotationFreeSceneRangesFromStructuredOutput ( t * testing . T ) {
2026-07-04 13:02:02 +00:00
client := & fakeScenesLLMClient {
response : chunkResponse {
Scenes : [ ] sceneResponse {
2026-07-24 00:35:10 +00:00
scene ( 1 , 2 ) ,
scene ( 3 , 4 ) ,
2026-07-04 13:02:02 +00:00
} ,
} ,
}
2026-07-17 23:57:59 +00:00
result , err := newChunker ( t , client ) . Plan ( context . Background ( ) , chunkRequest ( ) )
2026-07-04 13:02:02 +00:00
if err != nil {
2026-07-17 23:57:59 +00:00
t . Fatalf ( "Plan() error = %v, want nil" , err )
2026-07-04 13:02:02 +00:00
}
if len ( client . requests ) != 1 {
t . Fatalf ( "LLM calls = %d, want 1" , len ( client . requests ) )
}
req := client . requests [ 0 ]
if req . StageName != Key {
t . Fatalf ( "StageName = %q, want %q" , req . StageName , Key )
}
2026-07-05 18:27:07 +00:00
if req . PromptID != PromptID || req . PromptVersion != ResponseSchemaVersion {
t . Fatalf ( "prompt = %q/%q, want %q/%q" , req . PromptID , req . PromptVersion , PromptID , ResponseSchemaVersion )
2026-07-04 13:02:02 +00:00
}
2026-07-05 18:27:07 +00:00
if req . SessionID != "session-123" || req . ProfileID != "profile-scenes" {
t . Fatalf ( "session/profile = %q/%q, want session-123/profile-scenes" , req . SessionID , req . ProfileID )
2026-07-04 13:02:02 +00:00
}
2026-07-05 18:27:07 +00:00
transcript , ok := req . Inputs [ "transcript" ]
if ! ok {
t . Fatalf ( "transcript input missing from %#v" , req . Inputs )
2026-07-04 13:02:02 +00:00
}
2026-07-05 18:27:07 +00:00
if transcript . Name != "transcript" || transcript . MediaType != "application/json" || transcript . Digest != "sha256:transcript" || transcript . OriginURI != "file:///session-alpha.json" {
t . Fatalf ( "transcript metadata = %#v" , transcript )
}
if got := string ( transcript . Content ) ; got != sceneTranscriptJSON {
t . Fatalf ( "transcript content = %q, want original source input" , got )
2026-07-04 13:02:02 +00:00
}
2026-07-06 11:26:21 -05:00
if got := string ( req . Inputs [ "players" ] . Content ) ; got != " " {
t . Fatalf ( "players input = %q, want empty reference placeholder" , got )
}
if got := string ( req . Inputs [ "party" ] . Content ) ; got != " " {
t . Fatalf ( "party input = %q, want empty reference placeholder" , got )
2026-07-05 18:48:45 -05:00
}
if got := string ( req . Inputs [ "glossary" ] . Content ) ; got != " " {
t . Fatalf ( "glossary input = %q, want empty reference placeholder" , got )
}
2026-07-04 13:02:02 +00:00
2026-07-17 23:57:59 +00:00
if result . Plan . SourceDigest != "sha256:source" {
t . Fatalf ( "SourceDigest = %q, want source digest" , result . Plan . SourceDigest )
2026-07-04 13:02:02 +00:00
}
2026-07-17 23:57:59 +00:00
wantRanges := [ ] source . ChunkRange { { StartUnitID : 1 , EndUnitID : 2 } , { StartUnitID : 3 , EndUnitID : 4 } }
if len ( result . Plan . Ranges ) != len ( wantRanges ) {
t . Fatalf ( "ranges = %#v, want two" , result . Plan . Ranges )
2026-07-04 13:02:02 +00:00
}
2026-07-17 23:57:59 +00:00
for i , want := range wantRanges {
2026-07-24 00:35:10 +00:00
got := result . Plan . Ranges [ i ]
if got . StartUnitID != want . StartUnitID || got . EndUnitID != want . EndUnitID {
2026-07-17 23:57:59 +00:00
t . Fatalf ( "range[%d] = %#v, want %#v" , i , result . Plan . Ranges [ i ] , want )
}
2026-07-24 03:14:34 +00:00
if len ( got . Annotations ) != 0 {
2026-07-24 00:35:10 +00:00
t . Fatalf ( "range[%d] annotations = %#v, want absent" , i , got . Annotations )
}
2026-07-17 23:57:59 +00:00
}
2026-07-24 03:14:34 +00:00
if len ( result . Plan . Annotations ) != 0 {
2026-07-24 00:35:10 +00:00
t . Fatalf ( "plan annotations = %#v, want absent" , result . Plan . Annotations )
2026-07-04 13:02:02 +00:00
}
2026-08-26 23:55:23 +00:00
wantCandidate , err := json . Marshal ( client . response )
if err != nil {
t . Fatalf ( "marshal expected candidate: %v" , err )
}
if result . ModelCandidate == nil || result . ModelCandidate . Protocol != contracts . CorrectionProtocolSingleResponseV1 || ! reflect . DeepEqual ( result . ModelCandidate . Response , wantCandidate ) {
t . Fatalf ( "model candidate = %#v, want exact validated response" , result . ModelCandidate )
}
2026-07-24 00:35:10 +00:00
}
func TestPlanUsesDocumentOrderForNonconsecutiveUnitIDs ( t * testing . T ) {
doc := & source . SourceDocument {
ID : "session-nonnumeric" ,
Kind : "transcript" ,
Format : "application/vnd.seriatim.minimal+json" ,
Digest : "sha256:nonnumeric" ,
Units : [ ] source . SourceUnit {
{ ID : 10 , Kind : "transcript_segment" , Text : "The party arrives." , Ref : source . SourceRef { SourceID : "session-nonnumeric" , StartUnitID : 10 , EndUnitID : 10 } } ,
{ ID : 3 , Kind : "transcript_segment" , Text : "The party explores." , Ref : source . SourceRef { SourceID : "session-nonnumeric" , StartUnitID : 3 , EndUnitID : 3 } } ,
{ ID : 20 , Kind : "transcript_segment" , Text : "The party rests." , Ref : source . SourceRef { SourceID : "session-nonnumeric" , StartUnitID : 20 , EndUnitID : 20 } } ,
} ,
2026-07-04 13:02:02 +00:00
}
2026-07-24 00:35:10 +00:00
req := chunkRequest ( )
req . Source = doc
t . Run ( "accepts document-ordered ranges" , func ( t * testing . T ) {
client := & fakeScenesLLMClient { response : chunkResponse { Scenes : [ ] sceneResponse {
scene ( 10 , 3 ) ,
scene ( 20 , 20 ) ,
} } }
result , err := newChunker ( t , client ) . Plan ( context . Background ( ) , req )
if err != nil {
t . Fatalf ( "Plan() error = %v, want nil" , err )
}
if got := result . Plan . Ranges ; ! reflect . DeepEqual ( got , [ ] source . ChunkRange { { StartUnitID : 10 , EndUnitID : 3 } , { StartUnitID : 20 , EndUnitID : 20 } } ) {
t . Fatalf ( "ranges = %#v, want source-document order" , got )
}
} )
t . Run ( "rejects reversed document positions" , func ( t * testing . T ) {
client := & fakeScenesLLMClient { response : chunkResponse { Scenes : [ ] sceneResponse {
scene ( 3 , 10 ) ,
scene ( 20 , 20 ) ,
} } }
_ , err := newChunker ( t , client ) . Plan ( context . Background ( ) , req )
if err == nil || ! strings . Contains ( err . Error ( ) , "appears after" ) {
t . Fatalf ( "Plan() error = %v, want document-position reversal" , err )
}
} )
2026-07-04 13:02:02 +00:00
}
2026-07-17 23:57:59 +00:00
func TestPlanPassesReferencesAsPromptInputs ( t * testing . T ) {
2026-07-05 18:48:45 -05:00
client := & fakeScenesLLMClient { response : chunkResponse {
Scenes : [ ] sceneResponse {
2026-07-24 00:35:10 +00:00
scene ( 1 , 4 ) ,
2026-07-05 18:48:45 -05:00
} ,
} }
2026-07-17 06:30:56 +00:00
req := chunkRequest ( )
2026-07-05 18:48:45 -05:00
req . References = contracts . ReferenceSet {
Slots : map [ string ] contracts . ResolvedReferenceSlot {
2026-07-06 11:26:21 -05:00
"players" : {
Slot : contracts . ReferenceSlot { Name : "players" } ,
Items : [ ] contracts . ReferenceItem {
{ SlotName : "players" , Content : [ ] byte ( "Alice: Aria" ) } ,
} ,
} ,
"party" : {
Slot : contracts . ReferenceSlot { Name : "party" } ,
2026-07-05 18:48:45 -05:00
Items : [ ] contracts . ReferenceItem {
2026-07-06 11:26:21 -05:00
{ SlotName : "party" , Content : [ ] byte ( "Aria: cleric" ) } ,
2026-07-05 18:48:45 -05:00
} ,
} ,
"glossary" : {
Slot : contracts . ReferenceSlot { Name : "glossary" } ,
Items : [ ] contracts . ReferenceItem {
{ SlotName : "glossary" , Content : [ ] byte ( "Brightmantle: local temple" ) } ,
} ,
} ,
} ,
}
2026-07-17 23:57:59 +00:00
if _ , err := newChunker ( t , client ) . Plan ( context . Background ( ) , req ) ; err != nil {
t . Fatalf ( "Plan() error = %v, want nil" , err )
2026-07-05 18:48:45 -05:00
}
request := client . requests [ 0 ]
2026-07-06 11:26:21 -05:00
if got := string ( request . Inputs [ "players" ] . Content ) ; got != "Alice: Aria" {
t . Fatalf ( "players input = %q, want reference content" , got )
}
if got := string ( request . Inputs [ "party" ] . Content ) ; got != "Aria: cleric" {
t . Fatalf ( "party input = %q, want reference content" , got )
2026-07-05 18:48:45 -05:00
}
if got := string ( request . Inputs [ "glossary" ] . Content ) ; got != "Brightmantle: local temple" {
t . Fatalf ( "glossary input = %q, want reference content" , got )
}
if strings . Contains ( string ( request . Inputs [ "transcript" ] . Content ) , "Aria: cleric" ) {
t . Fatalf ( "transcript input contains reference content" )
}
}
2026-07-06 11:26:21 -05:00
func TestPromptInputsMapLegacyRosterReferenceToParty ( t * testing . T ) {
2026-07-17 05:05:48 +00:00
inputs := shared . PromptInputs ( sceneSourceInput ( ) , contracts . ReferenceSet {
2026-07-06 17:22:21 +00:00
Slots : map [ string ] contracts . ResolvedReferenceSlot {
"roster" : {
Slot : contracts . ReferenceSlot { Name : "roster" } ,
Items : [ ] contracts . ReferenceItem {
{ SlotName : "roster" , Content : [ ] byte ( "Legacy roster text" ) } ,
2026-07-06 11:26:21 -05:00
} ,
} ,
} ,
} )
if got := string ( inputs [ "party" ] . Content ) ; got != "Legacy roster text" {
t . Fatalf ( "party input = %q, want legacy roster content" , got )
}
if _ , ok := inputs [ "roster" ] ; ok {
t . Fatalf ( "roster prompt input was present; want only party input" )
}
}
2026-07-04 13:02:02 +00:00
func TestChunkerManifestMetadataIncludesPromptAndSchemaProvenance ( t * testing . T ) {
2026-07-17 06:30:56 +00:00
metadata := newChunker ( t , & fakeScenesLLMClient { } ) . ManifestMetadata ( )
2026-07-04 13:02:02 +00:00
tests := map [ string ] string {
"prompt_id" : PromptID ,
"prompt_version" : ResponseSchemaVersion ,
"response_schema_key" : string ( ResponseSchemaKey ) ,
"response_schema_id" : ResponseSchemaID ,
"response_schema_name" : ResponseSchemaName ,
"response_schema_version" : ResponseSchemaVersion ,
}
for key , want := range tests {
if metadata [ key ] != want {
t . Fatalf ( "metadata[%q] = %#v, want %q" , key , metadata [ key ] , want )
}
}
for _ , key := range [ ] string { "prompt_sha256" , "response_schema_sha256" } {
value , ok := metadata [ key ] . ( string )
if ! ok || ! strings . HasPrefix ( value , "sha256:" ) {
t . Fatalf ( "metadata[%q] = %#v, want sha256 value" , key , metadata [ key ] )
}
}
for _ , forbidden := range [ ] string { "prompt" , "schema" , "source" , "text" } {
if _ , ok := metadata [ forbidden ] ; ok {
t . Fatalf ( "metadata includes raw %q field: %#v" , forbidden , metadata )
}
}
}
2026-07-17 23:57:59 +00:00
func TestPlanRejectsInvalidRequests ( t * testing . T ) {
2026-07-04 13:02:02 +00:00
validClient := & fakeScenesLLMClient { response : validSceneResponse ( ) }
2026-07-17 06:30:56 +00:00
validReq := chunkRequest ( )
2026-07-04 13:02:02 +00:00
canceledCtx , cancel := context . WithCancel ( context . Background ( ) )
cancel ( )
invalidDoc := sceneSourceDocument ( )
2026-07-07 18:34:23 +00:00
invalidDoc . Units [ 0 ] . ID = 0
2026-07-04 13:02:02 +00:00
emptyDoc := sceneSourceDocument ( )
emptyDoc . Units = nil
tests := [ ] struct {
name string
chunker * Chunker
ctx context . Context
req contracts . ChunkRequest
want string
} {
{ name : "nil chunker" , chunker : nil , ctx : context . Background ( ) , req : validReq , want : "chunker" } ,
2026-07-17 06:30:56 +00:00
{ name : "nil context" , chunker : newChunker ( t , validClient ) , ctx : nil , req : validReq , want : "context" } ,
{ name : "canceled context" , chunker : newChunker ( t , validClient ) , ctx : canceledCtx , req : validReq , want : "context" } ,
{ name : "nil source" , chunker : newChunker ( t , validClient ) , ctx : context . Background ( ) , req : contracts . ChunkRequest { } , want : "source" } ,
{ name : "empty source units" , chunker : newChunker ( t , validClient ) , ctx : context . Background ( ) , req : contracts . ChunkRequest { Source : emptyDoc } , want : "units" } ,
{ name : "invalid source" , chunker : newChunker ( t , validClient ) , ctx : context . Background ( ) , req : contracts . ChunkRequest { Source : invalidDoc } , want : "validate source document" } ,
2026-07-04 13:02:02 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2026-07-17 23:57:59 +00:00
_ , err := tt . chunker . Plan ( tt . ctx , tt . req )
2026-07-04 13:02:02 +00:00
if err == nil {
2026-07-17 23:57:59 +00:00
t . Fatal ( "Plan() error = nil, want error" )
2026-07-04 13:02:02 +00:00
}
if ! strings . Contains ( err . Error ( ) , "dnd scenes" ) || ! strings . Contains ( err . Error ( ) , tt . want ) {
2026-07-17 23:57:59 +00:00
t . Fatalf ( "Plan() error = %q, want module context and %q" , err . Error ( ) , tt . want )
2026-07-04 13:02:02 +00:00
}
} )
}
}
2026-07-17 23:57:59 +00:00
func TestPlanRejectsMalformedStructuredOutput ( t * testing . T ) {
2026-07-04 13:02:02 +00:00
tests := [ ] struct {
name string
response chunkResponse
want string
} {
{ name : "missing scenes" , response : chunkResponse { } , want : "scenes" } ,
{ name : "empty scenes" , response : chunkResponse { Scenes : [ ] sceneResponse { } } , want : "scenes" } ,
{
name : "unknown boundary id" ,
response : replaceScenes ( validSceneResponse ( ) , [ ] sceneResponse {
2026-07-07 18:34:23 +00:00
scene ( 1 , 999 ) ,
2026-07-04 13:02:02 +00:00
} ) ,
want : "was not found" ,
} ,
{
name : "out of order boundaries" ,
response : replaceScenes ( validSceneResponse ( ) , [ ] sceneResponse {
2026-07-07 18:34:23 +00:00
scene ( 3 , 2 ) ,
2026-07-04 13:02:02 +00:00
} ) ,
want : "appears after" ,
} ,
{
name : "gap" ,
response : replaceScenes ( validSceneResponse ( ) , [ ] sceneResponse {
2026-07-07 18:34:23 +00:00
scene ( 1 , 1 ) ,
scene ( 3 , 4 ) ,
2026-07-04 13:02:02 +00:00
} ) ,
want : "gap" ,
} ,
{
name : "overlap" ,
response : replaceScenes ( validSceneResponse ( ) , [ ] sceneResponse {
2026-07-07 18:34:23 +00:00
scene ( 1 , 2 ) ,
scene ( 2 , 4 ) ,
2026-07-04 13:02:02 +00:00
} ) ,
want : "overlap" ,
} ,
{
name : "incomplete coverage" ,
response : replaceScenes ( validSceneResponse ( ) , [ ] sceneResponse {
2026-07-07 18:34:23 +00:00
scene ( 1 , 3 ) ,
2026-07-04 13:02:02 +00:00
} ) ,
want : "final scene" ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
client := & fakeScenesLLMClient { response : tt . response }
2026-07-17 23:57:59 +00:00
_ , err := newChunker ( t , client ) . Plan ( context . Background ( ) , chunkRequest ( ) )
2026-07-04 13:02:02 +00:00
if err == nil {
2026-07-17 23:57:59 +00:00
t . Fatal ( "Plan() error = nil, want error" )
2026-07-04 13:02:02 +00:00
}
if ! strings . Contains ( err . Error ( ) , "dnd scenes" ) || ! strings . Contains ( err . Error ( ) , tt . want ) {
2026-07-17 23:57:59 +00:00
t . Fatalf ( "Plan() error = %q, want module context and %q" , err . Error ( ) , tt . want )
2026-07-04 13:02:02 +00:00
}
} )
}
}
2026-07-17 23:57:59 +00:00
func TestPlanWrapsLLMClientError ( t * testing . T ) {
2026-07-04 13:02:02 +00:00
client := & fakeScenesLLMClient { err : errors . New ( "provider unavailable" ) }
2026-07-17 23:57:59 +00:00
_ , err := newChunker ( t , client ) . Plan ( context . Background ( ) , chunkRequest ( ) )
2026-07-04 13:02:02 +00:00
if err == nil {
2026-07-17 23:57:59 +00:00
t . Fatal ( "Plan() error = nil, want LLM error" )
2026-07-04 13:02:02 +00:00
}
if ! strings . Contains ( err . Error ( ) , "dnd scenes" ) || ! strings . Contains ( err . Error ( ) , "provider unavailable" ) {
2026-07-17 23:57:59 +00:00
t . Fatalf ( "Plan() error = %q, want wrapped LLM context" , err . Error ( ) )
2026-07-04 13:02:02 +00:00
}
}
2026-07-17 06:30:56 +00:00
func chunkRequest ( ) contracts . ChunkRequest {
2026-07-04 13:02:02 +00:00
return contracts . ChunkRequest {
2026-07-05 18:27:07 +00:00
Source : sceneSourceDocument ( ) ,
SourceInput : sceneSourceInput ( ) ,
SessionID : "session-123" ,
LLMProfile : "profile-scenes" ,
2026-07-04 13:02:02 +00:00
}
}
2026-07-07 18:34:23 +00:00
const sceneTranscriptJSON = ` { "id":"session-alpha","segments":[ { "id":1,"text":"Aria asks whether the goblin will parley."}]} `
2026-07-05 18:27:07 +00:00
func sceneSourceInput ( ) contracts . LLMInputMaterial {
return contracts . NewLLMInputMaterial ( "source" , "application/json" , [ ] byte ( sceneTranscriptJSON ) , "sha256:transcript" , "file:///session-alpha.json" )
}
2026-07-17 06:30:56 +00:00
func newChunker ( t * testing . T , client contracts . StructuredLLMClient ) * Chunker {
t . Helper ( )
chunker , err := New ( client , Options { } )
if err != nil {
t . Fatalf ( "New() error = %v, want nil" , err )
}
return chunker
2026-07-04 13:02:02 +00:00
}
func sceneSourceDocument ( ) * source . SourceDocument {
return & source . SourceDocument {
ID : "session-alpha" ,
Kind : "transcript" ,
Format : "application/vnd.seriatim.minimal+json" ,
Digest : "sha256:source" ,
Units : [ ] source . SourceUnit {
2026-07-17 05:19:57 +00:00
{ ID : 1 , Kind : "transcript_segment" , Text : "Aria asks whether the goblin will parley." , Ref : source . SourceRef { SourceID : "session-alpha" , StartUnitID : 1 , EndUnitID : 1 } , Metadata : map [ string ] any { "speaker" : "Alice" } } ,
{ ID : 2 , Kind : "transcript_segment" , Text : "The goblin scout describes the gate guards." , Ref : source . SourceRef { SourceID : "session-alpha" , StartUnitID : 2 , EndUnitID : 2 } } ,
{ ID : 3 , Kind : "transcript_segment" , Text : "The guards rush out with blades drawn." , Ref : source . SourceRef { SourceID : "session-alpha" , StartUnitID : 3 , EndUnitID : 3 } } ,
{ ID : 4 , Kind : "transcript_segment" , Text : "The party defeats the ambushers." , Ref : source . SourceRef { SourceID : "session-alpha" , StartUnitID : 4 , EndUnitID : 4 } } ,
2026-07-04 13:02:02 +00:00
} ,
}
}
func validSceneResponse ( ) chunkResponse {
return chunkResponse {
Scenes : [ ] sceneResponse {
2026-07-07 18:34:23 +00:00
scene ( 1 , 4 ) ,
2026-07-04 13:02:02 +00:00
} ,
}
}
func replaceScenes ( response chunkResponse , scenes [ ] sceneResponse ) chunkResponse {
response . Scenes = scenes
return response
}
2026-07-07 18:34:23 +00:00
func scene ( startUnitID int , endUnitID int ) sceneResponse {
2026-07-04 13:02:02 +00:00
return sceneResponse {
2026-07-24 00:35:10 +00:00
StartUnitID : shared . UnitRefFromInt ( startUnitID ) ,
EndUnitID : shared . UnitRefFromInt ( endUnitID ) ,
2026-07-04 13:02:02 +00:00
}
}
2026-07-17 05:32:11 +00:00
func chunkIDs ( chunks [ ] source . Chunk ) [ ] string {
2026-07-04 13:02:02 +00:00
ids := make ( [ ] string , 0 , len ( chunks ) )
for _ , chunk := range chunks {
ids = append ( ids , chunk . ID )
}
return ids
}
2026-07-07 18:34:23 +00:00
func unitIDs ( units [ ] source . SourceUnit ) [ ] int {
ids := make ( [ ] int , 0 , len ( units ) )
2026-07-04 13:02:02 +00:00
for _ , unit := range units {
ids = append ( ids , unit . ID )
}
return ids
}
type fakeScenesLLMClient struct {
response chunkResponse
err error
requests [ ] contracts . StructuredCompletionRequest
}
func ( client * fakeScenesLLMClient ) CompleteStructured ( ctx context . Context , req contracts . StructuredCompletionRequest , out any ) ( contracts . StructuredCompletionResponse , error ) {
2026-07-05 18:27:07 +00:00
client . requests = append ( client . requests , cloneStructuredCompletionRequest ( req ) )
2026-07-04 13:02:02 +00:00
if client . err != nil {
return contracts . StructuredCompletionResponse { } , client . err
}
target , ok := out . ( * chunkResponse )
if ! ok {
return contracts . StructuredCompletionResponse { } , errors . New ( "unexpected output target" )
}
* target = client . response
content , err := json . Marshal ( client . response )
if err != nil {
return contracts . StructuredCompletionResponse { } , err
}
return contracts . StructuredCompletionResponse { Content : content } , nil
}
2026-07-05 18:27:07 +00:00
func cloneStructuredCompletionRequest ( req contracts . StructuredCompletionRequest ) contracts . StructuredCompletionRequest {
req . Inputs = req . Inputs . Clone ( )
2026-08-26 23:24:20 +00:00
correction , err := contracts . CloneSemanticCorrection ( req . Correction )
if err != nil {
panic ( err )
}
req . Correction = correction
2026-07-05 18:26:31 -05:00
req . Vars = cloneVars ( req . Vars )
2026-07-05 18:27:07 +00:00
return req
}
2026-07-05 18:26:31 -05:00
func cloneVars ( in map [ string ] any ) map [ string ] any {
if len ( in ) == 0 {
return nil
}
out := make ( map [ string ] any , len ( in ) )
for key , value := range in {
out [ key ] = value
}
return out
}