diff options
Diffstat (limited to 'internal/validate/hardening_craft_validation_test.go')
| -rw-r--r-- | internal/validate/hardening_craft_validation_test.go | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/internal/validate/hardening_craft_validation_test.go b/internal/validate/hardening_craft_validation_test.go new file mode 100644 index 0000000..5e5d83f --- /dev/null +++ b/internal/validate/hardening_craft_validation_test.go @@ -0,0 +1,130 @@ +package validate + +import ( + "path/filepath" + "testing" + + "thehouseoficarus/internal/item" +) + +// Regression: validateCraftBlocks must validate byproducts, the required tool, +// and success/fail coherence. A typo in a byproduct previously passed startup +// validation and the runtime wrote a phantom inventory slot with no item +// definition; a tool that names no item was also unchecked; and a fail product +// or fail_message on a recipe with no success formula can never be reached +// (the engine hardcodes a 100% success chance). + +func writeCraftItem(t *testing.T, dir, name, body string) { + t.Helper() + writeFile(t, filepath.Join(dir, "items", name+".yaml"), body) +} + +func TestCraftByproductMustExist(t *testing.T) { + dir := t.TempDir() + writeCraftItem(t, dir, "test_in", "id: test_in\nname: test in\n") + writeCraftItem(t, dir, "test_out", `id: test_out +name: test out +craft: + - type: crafting + ingredients: + - items: [test_in] + quantity: 1 + byproducts: [missing_byproduct] +`) + issues := validateCraftBlocks(newSource(dir)) + if !containsMsg(issues, "nonexistent byproduct") { + t.Fatalf("expected nonexistent-byproduct error, got: %+v", issues) + } +} + +func TestCraftToolMustExist(t *testing.T) { + dir := t.TempDir() + writeCraftItem(t, dir, "test_in", "id: test_in\nname: test in\n") + writeCraftItem(t, dir, "test_out", `id: test_out +name: test out +craft: + - type: crafting + tool: missing_tool + ingredients: + - items: [test_in] + quantity: 1 +`) + issues := validateCraftBlocks(newSource(dir)) + if !containsMsg(issues, "nonexistent tool") { + t.Fatalf("expected nonexistent-tool error, got: %+v", issues) + } +} + +func TestCraftFailWithoutSuccessFormulaIsWarned(t *testing.T) { + dir := t.TempDir() + writeCraftItem(t, dir, "test_in", "id: test_in\nname: test in\n") + writeCraftItem(t, dir, "burnt", "id: burnt\nname: burnt\n") + writeCraftItem(t, dir, "test_out", `id: test_out +name: test out +craft: + - type: cooking + fail: burnt + fail_message: You burn it. + ingredients: + - items: [test_in] + quantity: 1 +`) + issues := validateCraftBlocks(newSource(dir)) + if !containsMsg(issues, "unreachable") { + t.Fatalf("expected unreachable fail warning, got: %+v", issues) + } + for _, issue := range issues { + if issue.Level == "ERROR" { + t.Fatalf("dead fail data must not fail startup validation: %+v", issue) + } + } +} + +func TestCraftReachableFailProducesNoIssue(t *testing.T) { + dir := t.TempDir() + writeCraftItem(t, dir, "test_in", "id: test_in\nname: test in\n") + writeCraftItem(t, dir, "burnt", "id: burnt\nname: burnt\n") + writeCraftItem(t, dir, "test_out", `id: test_out +name: test out +craft: + - type: cooking + fail: burnt + fail_message: You burn it. + success: + base: 0.5 + cap: 0.95 + per_level: 0.02 + ingredients: + - items: [test_in] + quantity: 1 +`) + if issues := validateCraftBlocks(newSource(dir)); len(issues) != 0 { + t.Fatalf("reachable fail branch must validate cleanly, got: %+v", issues) + } +} + +func TestCraftFailureReachableFormula(t *testing.T) { + cases := []struct { + name string + base float64 + perLvl float64 + cap float64 + nilF bool + want bool + }{ + {name: "no formula", nilF: true, want: false}, + {name: "50% base", base: 0.5, perLvl: 0.02, cap: 0.95, want: true}, + {name: "always succeeds", base: 1.0, perLvl: 0, cap: 1.0, want: false}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + craft := &item.CraftDef{Level: 1} + if !c.nilF { + craft.Success = &item.SuccessFormula{Base: c.base, PerLevel: c.perLvl, Cap: c.cap} + } + if got := craftFailureReachable(craft); got != c.want { + t.Errorf("craftFailureReachable = %v, want %v", got, c.want) + } + }) + } +} |
