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
|
package game
import (
"testing"
"thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/casino"
"thehouseoficarus/internal/item"
"thehouseoficarus/internal/prediction"
)
// Regression: gather verbs must normalize to the gather action type, otherwise
// startAction's bhType != verb gate rejects mine/chop/fish/cut outright.
func TestGatherVerbsNormalizeToGatherAction(t *testing.T) {
for _, verb := range []string{"mine", "chop", "fish", "cut", "shear"} {
if got := normalizeVerb(verb); got != string(behavior.TypeGather) {
t.Errorf("normalizeVerb(%q) = %q, want %q", verb, got, behavior.TypeGather)
}
}
}
// Regression: an unbounded step count must be rejected before it allocates.
func TestParseWalkDirectionsRejectsHugeCount(t *testing.T) {
if _, err := parseWalkDirections("999999999n"); err == nil {
t.Fatal("expected an error for an unbounded step count")
}
dirs, err := parseWalkDirections("3n2e")
if err != nil {
t.Fatalf("parseWalkDirections: %v", err)
}
if len(dirs) != 5 {
t.Fatalf("len(dirs) = %d, want 5", len(dirs))
}
}
// Regression: collectPredictions must not mark resolved contracts collected
// when the credit cap blocks the payout, or the winnings are lost forever.
func TestCollectPredictionsKeepsContractsWhenCreditCapBlocks(t *testing.T) {
h := newHarness(t)
h.seedAccount("alice")
h.seedCharacter("alice", "alice")
sess, _ := h.connect("alice")
sess.Player.PredictionContracts = []prediction.Contract{
{MarketID: "m1", Status: "resolved", Proceeds: 1000},
}
sess.Player.Credits = casino.MaxCredits
h.g.RunOnOwner(func() { h.g.collectPredictions(sess) })
if got := sess.Player.PredictionContracts[0].Status; got != "resolved" {
t.Fatalf("credit-cap collect changed status to %q; the payout would be lost", got)
}
// With room under the cap, the same contract must still be collectable.
sess.Player.Credits = 0
h.g.RunOnOwner(func() { h.g.collectPredictions(sess) })
if got := sess.Player.PredictionContracts[0].Status; got != "collected" {
t.Fatalf("status = %q, want collected", got)
}
if sess.Player.Credits != 1000 {
t.Fatalf("credits = %d, want 1000", sess.Player.Credits)
}
}
// Regression: a craft with no ingredient entries must be unsatisfiable, not
// "always satisfied" (which would loop producing output for free).
func TestPlanIngredientsRejectsEmptyCraft(t *testing.T) {
c := &item.CraftDef{Type: "fletch"}
if plan := planIngredients(c, func(string) int { return 999 }); plan != nil {
t.Fatalf("empty craft produced a plan: %+v", plan)
}
}
// Regression: a command reached through a prefix/abbreviation must not have its
// arguments lowercased (global/symbol/prompt/addlocalobj read rawInput).
func TestGlobalCommandPreservesArgumentCase(t *testing.T) {
h := newHarness(t)
h.seedAccount("alice")
h.seedCharacter("alice", "alice")
h.seedAccount("bob")
h.seedCharacter("bob", "bob")
alice, _ := h.connect("alice")
_, bobConn := h.connect("bob")
h.g.RunOnOwner(func() { h.g.HandleSessionOnOwner(alice, "glo Hello World") })
if !bobConn.Contains("Hello World") {
t.Fatalf("global broadcast lost argument case: %s", bobConn.Bytes())
}
}
// Regression: the player-file classifier must canonicalize repeated "/" and
// "data/" prefixes so a raw-editor path cannot dodge the online check.
func TestPlayerDataFileNameCanonicalizesPrefixes(t *testing.T) {
for _, in := range []string{
"players/characters/alice.yaml",
"//players/characters/alice.yaml",
"data/data/players/characters/alice.yaml",
"/data/players/characters/alice.yaml",
} {
name, kind, ok := playerDataFileName(in)
if !ok || name != "alice" || kind != "characters" {
t.Errorf("playerDataFileName(%q) = (%q, %q, %v), want (alice, characters, true)", in, name, kind, ok)
}
}
}
|