aboutsummaryrefslogtreecommitdiff
path: root/internal/game/correct_audit_fixes_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-05 17:05:14 -0400
committerhistoria <[not public]>2026-10-05 17:05:14 -0400
commite42952469dadfb3535633c66c881050ac128703c (patch)
treea78f7396b72cdbd6fa4f6c454c830f40cf2ad0cb /internal/game/correct_audit_fixes_test.go
parent90ad8c0c33b519d5830c69d4128bdc984b5e9de8 (diff)
downloadthehouseoficarus-e42952469dadfb3535633c66c881050ac128703c.tar.gz
fix: gather verbs, walk oom, admin path bypass, roulette refund
Diffstat (limited to 'internal/game/correct_audit_fixes_test.go')
-rw-r--r--internal/game/correct_audit_fixes_test.go104
1 files changed, 104 insertions, 0 deletions
diff --git a/internal/game/correct_audit_fixes_test.go b/internal/game/correct_audit_fixes_test.go
new file mode 100644
index 0000000..90fdc53
--- /dev/null
+++ b/internal/game/correct_audit_fixes_test.go
@@ -0,0 +1,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)
+ }
+ }
+}