Files
notarius/internal/modules/dnd/extract/itemoccurrences/extractor_test.go

122 lines
5.4 KiB
Go
Raw Normal View History

package itemoccurrences
2026-07-25 21:49:24 +00:00
import (
"context"
2026-08-06 13:37:21 +00:00
"strings"
2026-07-25 21:49:24 +00:00
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
2026-08-05 19:55:34 +00:00
itemidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/items/identity"
2026-07-25 21:49:24 +00:00
)
2026-08-05 19:55:34 +00:00
func TestExtractGroundsOccurrencesInRequiredRegistry(t *testing.T) {
id := itemidentity.DeriveID("Torch")
client := &fakeItemOccurrencesLLMClient{response: extractionResponse{Occurrences: []itemOccurrenceResponse{
{Name: "Torch", Kind: "lost", From: "party", SourceRefs: responseRefs(1, 1)},
2026-07-25 21:49:24 +00:00
}}}
2026-08-05 19:55:34 +00:00
req := extractionRequest()
req.References = itemRegistryReferences(t)
result, err := newExtractor(t, client, req.References).Extract(context.Background(), req)
2026-07-25 21:49:24 +00:00
if err != nil {
2026-08-05 19:55:34 +00:00
t.Fatal(err)
2026-07-25 21:49:24 +00:00
}
2026-08-05 19:55:34 +00:00
if len(result.Value.Occurrences) != 1 || result.Value.Occurrences[0].ItemID != id || result.Value.Occurrences[0].Name != "Torch" {
t.Fatalf("occurrences = %#v", result.Value.Occurrences)
2026-07-25 21:49:24 +00:00
}
2026-08-05 19:55:34 +00:00
input := client.requests[0].Inputs[ItemRegistryReferenceSlot]
if input.Name != ItemRegistryReferenceSlot || string(input.Content) != `{"items":[{"name":"Torch"}]}` || strings.Contains(string(input.Content), "item:sha256:") {
t.Fatalf("registry prompt input = %#v, want names-only projection", input)
2026-07-25 21:49:24 +00:00
}
}
func TestExtractCanonicalizesComparisonEquivalentNames(t *testing.T) {
2026-08-06 13:37:21 +00:00
id := itemidentity.DeriveID("Torch")
client := &fakeItemOccurrencesLLMClient{response: extractionResponse{Occurrences: []itemOccurrenceResponse{
{Name: " tORCH ", Kind: "lost", From: "party", SourceRefs: responseRefs(1, 1)},
}}}
req := extractionRequest()
req.References = itemRegistryReferences(t)
result, err := newExtractor(t, client, req.References).Extract(context.Background(), req)
if err != nil {
t.Fatal(err)
}
occurrence := result.Value.Occurrences[0]
if occurrence.ItemID != id || occurrence.Name != "Torch" {
t.Fatalf("canonical occurrence = %#v", occurrence)
2026-08-06 13:37:21 +00:00
}
}
func TestExtractRejectsUnknownNameAfterValidOccurrence(t *testing.T) {
2026-08-06 13:37:21 +00:00
client := &fakeItemOccurrencesLLMClient{response: extractionResponse{Occurrences: []itemOccurrenceResponse{
{Name: "Torch", Kind: "lost", From: "party", SourceRefs: responseRefs(1, 1)},
{Name: "Unknown", Kind: "lost", From: "party", SourceRefs: responseRefs(2, 2)},
2026-08-06 13:37:21 +00:00
}}}
req := extractionRequest()
req.References = itemRegistryReferences(t)
result, err := newExtractor(t, client, req.References).Extract(context.Background(), req)
if err == nil || !strings.Contains(err.Error(), "occurrences[1].name is not in the item registry") {
t.Fatalf("Extract() error = %v, want occurrence index and unknown name", err)
2026-08-06 13:37:21 +00:00
}
if len(result.Value.Occurrences) != 0 {
t.Fatalf("Extract() returned partial occurrences: %#v", result.Value.Occurrences)
}
}
2026-08-05 19:55:34 +00:00
func TestExtractRequiresItemRegistry(t *testing.T) {
_, err := newExtractor(t, &fakeItemOccurrencesLLMClient{}).Extract(context.Background(), extractionRequest())
if err == nil {
t.Fatal("Extract() error = nil, want required registry error")
}
}
func TestExtractAcceptsOnlyEmptyResponseForEmptyRegistry(t *testing.T) {
references := emptyItemRegistryReferences(t)
for _, test := range []struct {
name string
response extractionResponse
wantErr bool
}{
{name: "empty", response: extractionResponse{Occurrences: []itemOccurrenceResponse{}}},
{name: "selection", response: extractionResponse{Occurrences: []itemOccurrenceResponse{{Name: "Torch", Kind: "lost", From: "party", SourceRefs: responseRefs(1, 1)}}}, wantErr: true},
} {
t.Run(test.name, func(t *testing.T) {
client := &fakeItemOccurrencesLLMClient{response: test.response}
req := extractionRequest()
req.References = references
result, err := newExtractor(t, client, references).Extract(context.Background(), req)
if test.wantErr {
if err == nil || !strings.Contains(err.Error(), "name is not in the item registry") || len(result.Value.Occurrences) != 0 {
t.Fatalf("Extract() = %#v, %v; want no accepted occurrences", result, err)
}
return
}
if err != nil || result.Value.Occurrences == nil || len(result.Value.Occurrences) != 0 {
t.Fatalf("Extract() = %#v, %v; want present empty occurrences", result, err)
}
})
}
}
2026-08-05 19:55:34 +00:00
func TestExtractPreservesNullableFields(t *testing.T) {
client := &fakeItemOccurrencesLLMClient{content: []byte(`{"occurrences":[{"name":"Torch","kind":"discovered","quantity":null,"from":null,"to":null,"source_refs":[{"start_segment":1,"end_segment":1}]}]}`)}
2026-07-25 21:49:24 +00:00
req := extractionRequest()
2026-08-05 19:55:34 +00:00
req.References = itemRegistryReferences(t)
result, err := newExtractor(t, client, req.References).Extract(context.Background(), req)
if err != nil || len(result.Value.Occurrences) != 1 || result.Value.Occurrences[0].Quantity != nil || result.Value.Occurrences[0].From != "" || result.Value.Occurrences[0].To != "" {
t.Fatalf("result = %#v, %v", result, err)
2026-07-25 21:49:24 +00:00
}
}
func TestExtractUsesOnlySupportedPromptInputs(t *testing.T) {
2026-08-05 19:55:34 +00:00
client := &fakeItemOccurrencesLLMClient{response: extractionResponse{Occurrences: []itemOccurrenceResponse{}}}
2026-07-25 21:49:24 +00:00
req := extractionRequest()
2026-08-05 19:55:34 +00:00
req.References = itemRegistryReferences(t)
req.References.Slots["unrelated"] = contracts.ResolvedReferenceSlot{Slot: contracts.ReferenceSlot{Name: "unrelated"}, Items: []contracts.ReferenceItem{{SlotName: "unrelated", Content: []byte("ignored")}}}
if _, err := newExtractor(t, client, req.References).Extract(context.Background(), req); err != nil {
2026-07-25 21:49:24 +00:00
t.Fatal(err)
}
2026-08-05 19:55:34 +00:00
if _, ok := client.requests[0].Inputs["unrelated"]; ok {
t.Fatalf("unexpected prompt input: %#v", client.requests[0].Inputs)
2026-07-25 21:49:24 +00:00
}
}