aboutsummaryrefslogtreecommitdiff
path: root/internal/validate/hardening_craft_validation_test.go
blob: 5e5d83f89d97d101e6d67d4c30533c4b66626c89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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)
			}
		})
	}
}