Document D&D spell normalization behavior

This commit is contained in:
2026-07-20 21:25:02 +00:00
parent ae97adb8b0
commit 3eb68baca6
10 changed files with 257 additions and 73 deletions

View File

@@ -0,0 +1,56 @@
package spells
import (
"context"
"encoding/json"
"os"
"reflect"
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
)
type normalizerFixtureSet struct {
Cases []normalizerFixtureCase `json:"cases"`
}
type normalizerFixtureCase struct {
Name string `json:"name"`
Input dnd.SpellList `json:"input"`
Output dnd.SpellList `json:"output"`
WarningReasonCodes []string `json:"warning_reason_codes"`
}
func TestNormalizeAcceptedFixtures(t *testing.T) {
data, err := os.ReadFile("testdata/normalizer_accepted_cases.json")
if err != nil {
t.Fatalf("read fixture: %v", err)
}
var fixtures normalizerFixtureSet
if err := json.Unmarshal(data, &fixtures); err != nil {
t.Fatalf("decode fixture: %v", err)
}
if len(fixtures.Cases) == 0 {
t.Fatal("fixture set is empty")
}
for _, fixture := range fixtures.Cases {
t.Run(fixture.Name, func(t *testing.T) {
result, err := newNormalizer(t).Normalize(context.Background(), normalizeRequestWithSource(fixture.Input, sourceDocument(2)))
if err != nil {
t.Fatalf("Normalize() error = %v, want nil", err)
}
if !reflect.DeepEqual(result.Value, fixture.Output) {
t.Fatalf("normalized value = %#v, want %#v", result.Value, fixture.Output)
}
gotReasonCodes := make([]string, 0, len(result.Warnings))
for _, warning := range result.Warnings {
gotReasonCodes = append(gotReasonCodes, warning.ReasonCode)
}
if !reflect.DeepEqual(gotReasonCodes, fixture.WarningReasonCodes) {
t.Fatalf("warning reason codes = %#v, want %#v", gotReasonCodes, fixture.WarningReasonCodes)
}
})
}
}

View File

@@ -0,0 +1,99 @@
{
"cases": [
{
"name": "canonicalizes references and collapses exact duplicate",
"input": {
"spell_casts": [
{
"caster": " Aria ",
"spell": " cure wounds ",
"effect": "first effect",
"narrative_description": "first narrative",
"source_refs": [
{"source_id": "source", "start_unit_id": 2, "end_unit_id": 2},
{"source_id": "source", "start_unit_id": 1, "end_unit_id": 1},
{"source_id": "source", "start_unit_id": 1, "end_unit_id": 1}
]
},
{
"caster": "aria",
"spell": "Cure Wounds",
"effect": "duplicate effect",
"narrative_description": "duplicate narrative",
"source_refs": [
{"source_id": "source", "start_unit_id": 1, "end_unit_id": 1},
{"source_id": "source", "start_unit_id": 2, "end_unit_id": 2}
]
},
{
"caster": "aria",
"spell": "Cure Wounds",
"effect": "distinct effect",
"narrative_description": "distinct narrative",
"source_refs": [
{"source_id": "source", "start_unit_id": 2, "end_unit_id": 2}
]
}
]
},
"output": {
"spell_casts": [
{
"caster": " Aria ",
"spell": "Cure Wounds",
"effect": "first effect",
"narrative_description": "first narrative",
"source_refs": [
{"source_id": "source", "start_unit_id": 1, "end_unit_id": 1},
{"source_id": "source", "start_unit_id": 2, "end_unit_id": 2}
]
},
{
"caster": "aria",
"spell": "Cure Wounds",
"effect": "distinct effect",
"narrative_description": "distinct narrative",
"source_refs": [
{"source_id": "source", "start_unit_id": 2, "end_unit_id": 2}
]
}
]
},
"warning_reason_codes": [
"spell_name_canonicalized",
"source_references_normalized",
"duplicate_spell_cast_collapsed"
]
},
{
"name": "leaves already normalized output unchanged",
"input": {
"spell_casts": [
{
"caster": "Aria",
"spell": "Cure Wounds",
"effect": "heals an ally",
"narrative_description": "Aria restores an ally's wounds.",
"source_refs": [
{"source_id": "source", "start_unit_id": 1, "end_unit_id": 1}
]
}
]
},
"output": {
"spell_casts": [
{
"caster": "Aria",
"spell": "Cure Wounds",
"effect": "heals an ally",
"narrative_description": "Aria restores an ally's wounds.",
"source_refs": [
{"source_id": "source", "start_unit_id": 1, "end_unit_id": 1}
]
}
]
},
"warning_reason_codes": []
}
]
}