Close out the D&D combat turn extraction roadmap

This commit is contained in:
2026-07-21 07:49:57 -05:00
parent a1f5dce405
commit 110593ece1
15 changed files with 318 additions and 804 deletions

View File

@@ -37,6 +37,73 @@ func TestLoadResponseSchemaUsesPrivateCombatShape(t *testing.T) {
}
}
func TestResponseSchemaLeavesSemanticConstraintsToDeterministicValidators(t *testing.T) {
schema, err := loadResponseSchema()
if err != nil {
t.Fatal(err)
}
semanticCandidate := validCombatResponse()
turn := semanticCandidate["combat_turns"].([]any)[0].(map[string]any)
turn["actor"] = ""
turn["turn_kind"] = "unsupported"
turn["round"] = -1
turn["summary"] = ""
action := turn["actions"].([]any)[0].(map[string]any)
action["category"] = "unsupported"
action["declaration"] = ""
action["targets"] = []any{""}
action["resolution"] = ""
ref := turn["source_refs"].([]any)[0].(map[string]any)
ref["start_unit_id"] = 0
ref["end_unit_id"] = -1
content, err := json.Marshal(semanticCandidate)
if err != nil {
t.Fatal(err)
}
if err := validateJSONSchema(content, schema.JSONSchema); err != nil {
t.Fatalf("private schema rejected validator-owned semantics: %v", err)
}
turn["actions"] = []any{}
turn["source_refs"] = []any{}
content, err = json.Marshal(semanticCandidate)
if err != nil {
t.Fatal(err)
}
if err := validateJSONSchema(content, schema.JSONSchema); err != nil {
t.Fatalf("private schema rejected empty validator-owned collections: %v", err)
}
}
func TestResponseSchemaRetainsStructuralBoundary(t *testing.T) {
schema, err := loadResponseSchema()
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
name string
mutate func(map[string]any)
}{
{name: "missing nullable round", mutate: func(turn map[string]any) { delete(turn, "round") }},
{name: "wrong round type", mutate: func(turn map[string]any) { turn["round"] = "one" }},
{name: "unknown field", mutate: func(turn map[string]any) { turn["unexpected"] = true }},
{name: "missing nullable resolution", mutate: func(turn map[string]any) {
delete(turn["actions"].([]any)[0].(map[string]any), "resolution")
}},
} {
t.Run(test.name, func(t *testing.T) {
candidate := validCombatResponse()
test.mutate(candidate["combat_turns"].([]any)[0].(map[string]any))
content, err := json.Marshal(candidate)
if err != nil {
t.Fatal(err)
}
if err := validateJSONSchema(content, schema.JSONSchema); err == nil {
t.Fatal("private schema accepted structurally invalid response")
}
})
}
}
func TestResponseSchemaJSONIsMutationSafe(t *testing.T) {
first, err := loadResponseSchema()
if err != nil {