Files
notarius/internal/modules/sharedassets/dnd/prompt_inputs_test.go

152 lines
5.9 KiB
Go

package dnd
import (
"strings"
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
)
func TestPromptInputsBuildExpectedInputs(t *testing.T) {
source := contracts.NewLLMInputMaterial("source", "application/json", []byte("source text"), "sha256:source", "file:///source.json")
references := contracts.ReferenceSet{Slots: map[string]contracts.ResolvedReferenceSlot{
"players": slotWithContent("players", "Alice: Aria"),
"party": slotWithContent("party", "Aria: cleric"),
"glossary": slotWithContent("glossary", "Brightmantle: temple"),
}}
inputs := PromptInputs(source, references)
for _, name := range []string{"transcript", "players", "party", "glossary"} {
if _, ok := inputs[name]; !ok {
t.Fatalf("PromptInputs() missing %q: %#v", name, inputs)
}
}
if _, ok := inputs["roster"]; ok {
t.Fatalf("PromptInputs() included roster input: %#v", inputs)
}
if got := inputs["transcript"].Name; got != "transcript" {
t.Fatalf("transcript name = %q, want transcript", got)
}
if got := string(inputs["transcript"].Content); got != "source text" {
t.Fatalf("transcript content = %q, want source text", got)
}
if got := string(inputs["players"].Content); got != "Alice: Aria" {
t.Fatalf("players content = %q, want player reference", got)
}
if got := string(inputs["party"].Content); got != "Aria: cleric" {
t.Fatalf("party content = %q, want party reference", got)
}
if got := string(inputs["glossary"].Content); got != "Brightmantle: temple" {
t.Fatalf("glossary content = %q, want glossary reference", got)
}
}
func TestPromptInputsUseRosterWhenPartyIsEmpty(t *testing.T) {
inputs := PromptInputs(contracts.LLMInputMaterial{}, contracts.ReferenceSet{Slots: map[string]contracts.ResolvedReferenceSlot{
"party": {},
"roster": slotWithContent("roster", "Legacy roster text"),
}})
if got := string(inputs["party"].Content); got != "Legacy roster text" {
t.Fatalf("party content = %q, want roster fallback content", got)
}
if _, ok := inputs["roster"]; ok {
t.Fatalf("PromptInputs() included roster input: %#v", inputs)
}
}
func TestTranscriptPromptMaterialClonesSource(t *testing.T) {
source := contracts.NewLLMInputMaterial("source", "text/plain", []byte("source text"), "sha256:source", "file:///source.txt")
got := TranscriptPromptMaterial(source)
if got.Name != "transcript" {
t.Fatalf("Name = %q, want transcript", got.Name)
}
if got.MediaType != source.MediaType || got.Digest != source.Digest || got.OriginURI != source.OriginURI || got.SizeBytes != source.SizeBytes {
t.Fatalf("TranscriptPromptMaterial() = %#v, want cloned metadata from %#v", got, source)
}
source.Content[0] = 'X'
if string(got.Content) != "source text" {
t.Fatalf("TranscriptPromptMaterial() reused content slice: %q", got.Content)
}
}
func TestReferencePromptMaterialUsesTextPlainAndSingleReferenceMetadata(t *testing.T) {
slot := contracts.ResolvedReferenceSlot{Items: []contracts.ReferenceItem{{
Content: []byte("single reference"),
Digest: "sha256:reference",
Origin: contracts.ReferenceOrigin{Type: "file", URI: "file:///reference.md"},
}}}
got := ReferencePromptMaterial("party", slot)
if got.Name != "party" || got.MediaType != "text/plain" {
t.Fatalf("ReferencePromptMaterial() name/media = %q/%q, want party/text/plain", got.Name, got.MediaType)
}
if got.Digest != "sha256:reference" || got.OriginURI != "file:///reference.md" {
t.Fatalf("ReferencePromptMaterial() digest/origin = %q/%q, want single reference metadata", got.Digest, got.OriginURI)
}
if string(got.Content) != "single reference" {
t.Fatalf("ReferencePromptMaterial() content = %q, want raw single reference", got.Content)
}
}
func TestReferencePromptMaterialOmitsAggregateMetadata(t *testing.T) {
got := ReferencePromptMaterial("party", contracts.ResolvedReferenceSlot{Items: []contracts.ReferenceItem{
{Content: []byte("one"), Digest: "sha256:one", Origin: contracts.ReferenceOrigin{URI: "file:///one.md"}},
{Content: []byte("two"), Digest: "sha256:two", Origin: contracts.ReferenceOrigin{URI: "file:///two.md"}},
}})
if got.Digest != "" || got.OriginURI != "" {
t.Fatalf("ReferencePromptMaterial() digest/origin = %q/%q, want empty aggregate metadata", got.Digest, got.OriginURI)
}
}
func TestReferencePromptInputRendering(t *testing.T) {
if got := string(ReferencePromptInput(contracts.ResolvedReferenceSlot{})); got != " " {
t.Fatalf("empty rendering = %q, want single space", got)
}
if got := string(ReferencePromptInput(slotWithContent("party", "single reference"))); got != "single reference" {
t.Fatalf("single rendering = %q, want raw content", got)
}
slot := contracts.ResolvedReferenceSlot{Items: []contracts.ReferenceItem{
{
SlotName: "party",
MediaType: "text/plain",
Content: []byte("second"),
Digest: "sha256:bbb",
Origin: contracts.ReferenceOrigin{Type: "file", URI: "file:///b.txt"},
SizeBytes: 6,
},
{
SlotName: "party",
MediaType: "text/plain",
Content: []byte("first"),
Digest: "sha256:aaa",
Origin: contracts.ReferenceOrigin{Type: "file", URI: "file:///a.txt"},
SizeBytes: 5,
},
}}
first := string(ReferencePromptInput(slot))
second := string(ReferencePromptInput(slot))
if first != second {
t.Fatalf("ReferencePromptInput() was not deterministic:\nfirst=%q\nsecond=%q", first, second)
}
if !strings.Contains(first, "Reference 1\nOrigin-Type: file\nOrigin-URI: file:///a.txt\nDigest: sha256:aaa\nMedia-Type: text/plain\nSize-Bytes: 5\n\nfirst") {
t.Fatalf("first reference block = %q, want sorted first reference metadata", first)
}
if strings.Index(first, "first") > strings.Index(first, "second") {
t.Fatalf("references were not sorted deterministically: %q", first)
}
}
func slotWithContent(name string, content string) contracts.ResolvedReferenceSlot {
return contracts.ResolvedReferenceSlot{
Slot: contracts.ReferenceSlot{Name: name},
Items: []contracts.ReferenceItem{{
SlotName: name,
Content: []byte(content),
}},
}
}