Relax private D&D response schemas

This commit is contained in:
2026-07-22 14:38:35 +00:00
parent 7b2fb0880d
commit ab0b4e350c
19 changed files with 355 additions and 154 deletions

View File

@@ -11,7 +11,6 @@ import (
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
)
func TestExtractReturnsCanonicalNPCListFromPrivateResponse(t *testing.T) {
@@ -25,9 +24,9 @@ func TestExtractReturnsCanonicalNPCListFromPrivateResponse(t *testing.T) {
Name: "Mira Thorn", Aliases: []string{"The Greencloak"}, Description: "A guarded ranger.",
Relationships: []npcRelationshipResponse{{Target: "Captain Vale", Relationship: "commands"}},
SourceRefs: []npcSourceRefResponse{
{StartUnitID: sharedUnitRef(2), EndUnitID: sharedUnitRef(2)},
{StartUnitID: sharedUnitRef(1), EndUnitID: sharedUnitRef(2)},
{StartUnitID: sharedUnitRef(1), EndUnitID: sharedUnitRef(2)},
{StartUnitID: 2, EndUnitID: 2},
{StartUnitID: 1, EndUnitID: 2},
{StartUnitID: 1, EndUnitID: 2},
},
},
}}}
@@ -65,8 +64,8 @@ func TestExtractOrdersNPCsBySourcePositionRatherThanUnitID(t *testing.T) {
{
Name: "Earlier NPC", Aliases: []string{}, Description: "Appears first.", Relationships: []npcRelationshipResponse{},
SourceRefs: []npcSourceRefResponse{
{StartUnitID: sharedUnitRef(50), EndUnitID: sharedUnitRef(50)},
{StartUnitID: sharedUnitRef(100), EndUnitID: sharedUnitRef(100)},
{StartUnitID: 50, EndUnitID: 50},
{StartUnitID: 100, EndUnitID: 100},
},
},
}}}
@@ -111,7 +110,7 @@ func TestExtractPassesCampaignReferencesAsPromptInputs(t *testing.T) {
func TestExtractPreservesMalformedCandidatesForValidators(t *testing.T) {
client := &fakeNPCsLLMClient{response: extractionResponse{NPCs: []npcResponse{{
Name: "", Aliases: nil, Description: "", Relationships: nil,
SourceRefs: []npcSourceRefResponse{{StartUnitID: sharedUnitRef(99), EndUnitID: shared.UnitRefFromString("missing")}, {StartUnitID: sharedUnitRef(99), EndUnitID: shared.UnitRefFromInt(0)}},
SourceRefs: []npcSourceRefResponse{{StartUnitID: 99, EndUnitID: 0}},
}}}}
result, err := newExtractor(t, client).Extract(context.Background(), extractionRequest())
if err != nil {
@@ -125,6 +124,21 @@ func TestExtractPreservesMalformedCandidatesForValidators(t *testing.T) {
}
}
func TestExtractMapsRawSemanticCandidatesWithoutRepair(t *testing.T) {
client := &fakeNPCsLLMClient{content: []byte(`{"npcs":[{"name":"","aliases":[],"description":"","relationships":[],"source_refs":[{"start_unit_id":0,"end_unit_id":-1}]}]}`)}
result, err := newExtractor(t, client).Extract(context.Background(), extractionRequest())
if err != nil {
t.Fatalf("Extract() error = %v, want nil", err)
}
npc := result.Value.NPCs[0]
if npc.ID != "" || npc.Name != "" || npc.Description != "" {
t.Fatalf("NPC = %#v, want blank semantic values preserved", npc)
}
if refs := npc.SourceRefs; len(refs) != 1 || refs[0] != (source.SourceRef{SourceID: "session-alpha", StartUnitID: 0, EndUnitID: -1}) {
t.Fatalf("source refs = %#v, want raw nonpositive candidates preserved", refs)
}
}
func TestExtractHandlesCancellationAndProviderErrors(t *testing.T) {
request := extractionRequest()
extractor := newExtractor(t, &fakeNPCsLLMClient{response: extractionResponse{NPCs: []npcResponse{}}})
@@ -142,5 +156,3 @@ func TestExtractHandlesCancellationAndProviderErrors(t *testing.T) {
t.Fatalf("provider Extract() error = %v, want contextual provider error", err)
}
}
func sharedUnitRef(value int) shared.UnitRef { return shared.UnitRefFromInt(value) }