aboutsummaryrefslogtreecommitdiff
path: root/internal/game/correct_audit_fixes_test.go
blob: 90fdc535e8d1e503ba2c4b9966c92debdc1654f2 (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
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)
		}
	}
}