diff --git a/internal/modules/dnd/extract/combatturns/scriptorium_assets_test.go b/internal/modules/dnd/extract/combatturns/scriptorium_assets_test.go index e1b3a0f..97e383c 100644 --- a/internal/modules/dnd/extract/combatturns/scriptorium_assets_test.go +++ b/internal/modules/dnd/extract/combatturns/scriptorium_assets_test.go @@ -62,16 +62,44 @@ func TestScriptoriumPromptPreparesRequiredInputs(t *testing.T) { if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_combat_turns_llm.v1.json" { t.Fatalf("prepared prompt = %#v, want combat prompt identity and schema", prepared) } - for _, want := range []string{transcript, "Dana: Mira", "Mira: ranger", "Greencloak: title", `{"npcs":[]}`} { - found := false - for _, message := range prepared.Messages { - if strings.Contains(message.Content, want) { - found = true - break - } + for index, want := range []struct { + role string + cached bool + }{ + {role: "system"}, + {role: "user"}, + {role: "user", cached: true}, + {role: "user", cached: true}, + {role: "user", cached: true}, + {role: "user"}, + {role: "user", cached: true}, + {role: "user"}, + } { + if index >= len(prepared.Messages) { + t.Fatalf("prepared prompt has %d messages, want at least %d", len(prepared.Messages), index+1) } - if !found { - t.Fatalf("prepared prompt did not render required input %q", want) + message := prepared.Messages[index] + if message.Role != want.role { + t.Errorf("message %d role = %q, want %q", index, message.Role, want.role) + } + if want.cached { + if message.CacheControl == nil || message.CacheControl.Type != scriptorium.CacheControlEphemeral { + t.Errorf("message %d cache control = %#v, want ephemeral", index, message.CacheControl) + } + } else if message.CacheControl != nil { + t.Errorf("message %d cache control = %#v, want nil", index, message.CacheControl) } } + if len(prepared.Messages) != 8 { + t.Fatalf("prepared prompt has %d messages, want 8", len(prepared.Messages)) + } + if references := prepared.Messages[3].Content; !strings.Contains(references, "Dana: Mira") || !strings.Contains(references, "Mira: ranger") || !strings.Contains(references, "Greencloak: title") { + t.Fatalf("campaign references message = %q, want rendered reference inputs", references) + } + if registry := prepared.Messages[4].Content; !strings.Contains(registry, `{"npcs":[]}`) { + t.Fatalf("NPC registry message = %q, want registry input", registry) + } + if final := prepared.Messages[7].Content; !strings.Contains(final, transcript) { + t.Fatalf("final message = %q, want transcript", final) + } } diff --git a/internal/modules/dnd/extract/npcinteractions/scriptorium_assets_test.go b/internal/modules/dnd/extract/npcinteractions/scriptorium_assets_test.go index 135a78e..7a6f5a7 100644 --- a/internal/modules/dnd/extract/npcinteractions/scriptorium_assets_test.go +++ b/internal/modules/dnd/extract/npcinteractions/scriptorium_assets_test.go @@ -51,22 +51,45 @@ func TestRegisterPromptAssetsAndPrepareInteractionPrompt(t *testing.T) { if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_npc_interactions_llm.v1.json" { t.Fatalf("prepared prompt = %#v", prepared) } - for _, want := range []string{ - "Mira Thorn", "mentioned", "combat_opponent", "Registry content is context, not event evidence", transcript, + for index, want := range []struct { + role string + cached bool + }{ + {role: "system"}, + {role: "user"}, + {role: "user", cached: true}, + {role: "user", cached: true}, + {role: "user", cached: true}, + {role: "user"}, + {role: "user", cached: true}, + {role: "user"}, } { - found := false - for _, message := range prepared.Messages { - if strings.Contains(message.Content, want) { - found = true - break - } + if index >= len(prepared.Messages) { + t.Fatalf("prepared prompt has %d messages, want at least %d", len(prepared.Messages), index+1) } - if !found { - t.Fatalf("prepared prompt did not include %q", want) + message := prepared.Messages[index] + if message.Role != want.role { + t.Errorf("message %d role = %q, want %q", index, message.Role, want.role) + } + if want.cached { + if message.CacheControl == nil || message.CacheControl.Type != scriptorium.CacheControlEphemeral { + t.Errorf("message %d cache control = %#v, want ephemeral", index, message.CacheControl) + } + } else if message.CacheControl != nil { + t.Errorf("message %d cache control = %#v, want nil", index, message.CacheControl) } } - if last := prepared.Messages[len(prepared.Messages)-1]; !strings.Contains(last.Content, transcript) { - t.Fatalf("last prompt message = %q, want transcript", last.Content) + if len(prepared.Messages) != 8 { + t.Fatalf("prepared prompt has %d messages, want 8", len(prepared.Messages)) + } + if references := prepared.Messages[3].Content; !strings.Contains(references, "Dana: Mira") || !strings.Contains(references, "Mira: ranger") || !strings.Contains(references, "Greencloak: title") { + t.Fatalf("campaign references message = %q, want rendered reference inputs", references) + } + if registry := prepared.Messages[4].Content; !strings.Contains(registry, `{"npcs":[{"name":"Mira Thorn"}]}`) { + t.Fatalf("NPC registry message = %q, want names-only registry input", registry) + } + if final := prepared.Messages[7].Content; !strings.Contains(final, transcript) { + t.Fatalf("final message = %q, want transcript", final) } } diff --git a/internal/modules/dnd/extract/npcs/scriptorium_assets_test.go b/internal/modules/dnd/extract/npcs/scriptorium_assets_test.go index 97a3203..e22047c 100644 --- a/internal/modules/dnd/extract/npcs/scriptorium_assets_test.go +++ b/internal/modules/dnd/extract/npcs/scriptorium_assets_test.go @@ -42,17 +42,41 @@ func TestRegisterPromptAssetsAndPrepareNPCPrompt(t *testing.T) { if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_npcs_llm.v1.json" { t.Fatalf("prepared prompt = %#v, want NPC prompt identity and wiring", prepared) } - for _, want := range []string{`{"units":[1]}`, "Dana: Mira", "Mira: ranger", "Greencloak: title"} { - found := false - for _, message := range prepared.Messages { - if strings.Contains(message.Content, want) { - found = true - break + for index, want := range []struct { + role string + cached bool + }{ + {role: "system"}, + {role: "user"}, + {role: "user", cached: true}, + {role: "user", cached: true}, + {role: "user"}, + {role: "user", cached: true}, + {role: "user"}, + } { + if index >= len(prepared.Messages) { + t.Fatalf("prepared prompt has %d messages, want at least %d", len(prepared.Messages), index+1) + } + message := prepared.Messages[index] + if message.Role != want.role { + t.Errorf("message %d role = %q, want %q", index, message.Role, want.role) + } + if want.cached { + if message.CacheControl == nil || message.CacheControl.Type != scriptorium.CacheControlEphemeral { + t.Errorf("message %d cache control = %#v, want ephemeral", index, message.CacheControl) } + } else if message.CacheControl != nil { + t.Errorf("message %d cache control = %#v, want nil", index, message.CacheControl) } - if !found { - t.Fatalf("prepared prompt did not render required input %q", want) - } + } + if len(prepared.Messages) != 7 { + t.Fatalf("prepared prompt has %d messages, want 7", len(prepared.Messages)) + } + if references := prepared.Messages[3].Content; !strings.Contains(references, "Dana: Mira") || !strings.Contains(references, "Mira: ranger") || !strings.Contains(references, "Greencloak: title") { + t.Fatalf("campaign references message = %q, want rendered reference inputs", references) + } + if transcript := prepared.Messages[6].Content; !strings.Contains(transcript, `{"units":[1]}`) { + t.Fatalf("final message = %q, want transcript", transcript) } transcriptMessages := 0 for _, message := range prepared.Messages { diff --git a/internal/modules/dnd/extract/spells/scriptorium_assets_test.go b/internal/modules/dnd/extract/spells/scriptorium_assets_test.go index 9bbfd86..2b77956 100644 --- a/internal/modules/dnd/extract/spells/scriptorium_assets_test.go +++ b/internal/modules/dnd/extract/spells/scriptorium_assets_test.go @@ -21,24 +21,49 @@ func TestScriptoriumPromptPreparesTranscriptReferencesAndTaskMessages(t *testing if prepared.OutputContract.SchemaPath != "dnd_spells_llm.v1.json" { t.Fatalf("schema path = %q, want LLM-only schema", prepared.OutputContract.SchemaPath) } - for _, want := range []string{ - string(transcript), - "Dana: Mira", - "Mira: wizard", - "Shield: abjuration", - `{"spell_names":["Cure Wounds"]}`, - `{"npcs":[]}`, + for index, want := range []struct { + role string + cached bool + }{ + {role: "system"}, + {role: "user"}, + {role: "user", cached: true}, + {role: "user", cached: true}, + {role: "user", cached: true}, + {role: "user"}, + {role: "user"}, + {role: "user", cached: true}, + {role: "user"}, } { - found := false - for _, message := range prepared.Messages { - if strings.Contains(message.Content, want) { - found = true - break + if index >= len(prepared.Messages) { + t.Fatalf("prepared prompt has %d messages, want at least %d", len(prepared.Messages), index+1) + } + message := prepared.Messages[index] + if message.Role != want.role { + t.Errorf("message %d role = %q, want %q", index, message.Role, want.role) + } + if want.cached { + if message.CacheControl == nil || message.CacheControl.Type != scriptorium.CacheControlEphemeral { + t.Errorf("message %d cache control = %#v, want ephemeral", index, message.CacheControl) } + } else if message.CacheControl != nil { + t.Errorf("message %d cache control = %#v, want nil", index, message.CacheControl) } - if !found { - t.Fatalf("prepared prompt did not render required input %q", want) - } + } + if len(prepared.Messages) != 9 { + t.Fatalf("prepared prompt has %d messages, want 9", len(prepared.Messages)) + } + if references := prepared.Messages[3].Content; !strings.Contains(references, "Dana: Mira") || !strings.Contains(references, "Mira: wizard") || !strings.Contains(references, "Shield: abjuration") { + t.Fatalf("campaign references message = %q, want rendered reference inputs", references) + } + if registry := prepared.Messages[4].Content; !strings.Contains(registry, `{"npcs":[]}`) { + t.Fatalf("NPC registry message = %q, want registry input", registry) + } + if catalog := prepared.Messages[5].Content; !strings.Contains(catalog, `{"spell_names":["Cure Wounds"]}`) { + t.Fatalf("spell catalog message = %q, want catalog input", catalog) + } + if final := prepared.Messages[8].Content; !strings.Contains(final, string(transcript)) { + t.Fatalf("final message = %q, want transcript", final) } }