aboutsummaryrefslogtreecommitdiff
path: root/internal/game/hardening_harvest_yield_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/hardening_harvest_yield_test.go')
-rw-r--r--internal/game/hardening_harvest_yield_test.go168
1 files changed, 168 insertions, 0 deletions
diff --git a/internal/game/hardening_harvest_yield_test.go b/internal/game/hardening_harvest_yield_test.go
new file mode 100644
index 0000000..8bfdc6a
--- /dev/null
+++ b/internal/game/hardening_harvest_yield_test.go
@@ -0,0 +1,168 @@
+package game
+
+// Regression: a harvest that cannot be stored must not consume the crop or
+// report a yield that was never delivered. Stackable products merge into an
+// existing stack (so they fit a full inventory) but need their own slot
+// otherwise; non-stackable products need one slot per unit, and the reported
+// yield must match what was actually placed.
+
+import (
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+const (
+ hardeningCropPrefix = "farm_allot_1"
+ hardeningCropSeed = "test_seed"
+ hardeningCropID = "test_potato"
+)
+
+// readyHarvest installs the patch flags a ready crop has and an in-flight
+// harvest action for hardeningCropPrefix, matching what cmd_farm.go queues.
+func readyHarvest(p *player.Player, minYield, maxYield int) {
+ p.EnsureFlags()
+ p.Flags[hardeningCropPrefix+"_weeds"] = false
+ p.Flags[hardeningCropPrefix+"_seed"] = hardeningCropSeed
+ p.Flags[hardeningCropPrefix+"_stage"] = 4
+ p.Flags[hardeningCropPrefix+"_watered"] = false
+ p.Flags[hardeningCropPrefix+"_diseased"] = false
+ p.Flags[hardeningCropPrefix+"_dead"] = false
+ p.Flags[hardeningCropPrefix+"_ready"] = true
+ p.Action = &behavior.Action{
+ Type: "harvest",
+ Data: &behavior.FarmData{
+ Prefix: hardeningCropPrefix,
+ SeedID: hardeningCropSeed,
+ Product: hardeningCropID,
+ MinYield: float64(minYield),
+ MaxYield: float64(maxYield),
+ },
+ }
+}
+
+func TestHarvestStackableFullInventoryKeepsCrop(t *testing.T) {
+ h := newHarness(t)
+ h.seedAccount("alice")
+ p := h.seedCharacter("alice", "alice")
+ writeSeedFile(t, filepath.Join(h.dir, "items", hardeningCropID+".yaml"),
+ []byte("id: test_potato\nname: test potato\nstackable: true\n"))
+ sess := &net.Session{Conn: newChanConn(), Player: p}
+ sess.SetState(net.StateGame)
+ h.g.RunOnOwner(func() {
+ h.g.ItemStore.Reload(h.dir)
+ for i := 0; i < 28; i++ {
+ p.SetInvSlot(i, &player.InventorySlot{ItemID: "filler", Quantity: 1})
+ }
+ readyHarvest(p, 1, 1)
+
+ h.g.advanceHarvest(sess, p)
+
+ if got := p.CountItem(hardeningCropID); got != 0 {
+ t.Errorf("harvest stored %d potatoes with no room", got)
+ }
+ if ready, _ := p.Flags[hardeningCropPrefix+"_ready"].(bool); !ready {
+ t.Error("crop was consumed even though nothing could be stored")
+ }
+ if seed, _ := p.Flags[hardeningCropPrefix+"_seed"].(string); seed != hardeningCropSeed {
+ t.Errorf("patch seed = %q, want it left planted", seed)
+ }
+ out := string(sess.Conn.(*chanConn).Bytes())
+ if strings.Contains(out, "You harvest") {
+ t.Errorf("reported a yield that was not delivered: %q", out)
+ }
+ if !strings.Contains(out, "too full") {
+ t.Errorf("expected an inventory-full message, got %q", out)
+ }
+ })
+}
+
+func TestHarvestStackableMergesIntoExistingStackWhenFull(t *testing.T) {
+ h := newHarness(t)
+ h.seedAccount("alice")
+ p := h.seedCharacter("alice", "alice")
+ writeSeedFile(t, filepath.Join(h.dir, "items", hardeningCropID+".yaml"),
+ []byte("id: test_potato\nname: test potato\nstackable: true\n"))
+ sess := &net.Session{Conn: newChanConn(), Player: p}
+ sess.SetState(net.StateGame)
+ h.g.RunOnOwner(func() {
+ h.g.ItemStore.Reload(h.dir)
+ for i := 0; i < 28; i++ {
+ p.SetInvSlot(i, &player.InventorySlot{ItemID: "filler", Quantity: 1})
+ }
+ p.SetInvSlot(5, &player.InventorySlot{ItemID: hardeningCropID, Quantity: 4})
+ readyHarvest(p, 1, 1)
+
+ h.g.advanceHarvest(sess, p)
+
+ if got := p.CountItem(hardeningCropID); got != 5 {
+ t.Errorf("stackable harvest did not merge into the existing stack: have %d, want 5", got)
+ }
+ if ready, _ := p.Flags[hardeningCropPrefix+"_ready"].(bool); ready {
+ t.Error("patch still ready after a successful harvest")
+ }
+ if out := string(sess.Conn.(*chanConn).Bytes()); !strings.Contains(out, "You harvest 1") {
+ t.Errorf("expected the delivered yield in the message, got %q", out)
+ }
+ })
+}
+
+func TestHarvestNonStackableReportsPlacedYield(t *testing.T) {
+ h := newHarness(t)
+ h.seedAccount("alice")
+ p := h.seedCharacter("alice", "alice")
+ writeSeedFile(t, filepath.Join(h.dir, "items", hardeningCropID+".yaml"),
+ []byte("id: test_potato\nname: test potato\n"))
+ sess := &net.Session{Conn: newChanConn(), Player: p}
+ sess.SetState(net.StateGame)
+ h.g.RunOnOwner(func() {
+ h.g.ItemStore.Reload(h.dir)
+ for i := 0; i < 26; i++ {
+ p.SetInvSlot(i, &player.InventorySlot{ItemID: "filler", Quantity: 1})
+ }
+ readyHarvest(p, 5, 5)
+
+ h.g.advanceHarvest(sess, p)
+
+ if got := p.CountItem(hardeningCropID); got != 2 {
+ t.Fatalf("placed %d potatoes, want 2 (two free slots)", got)
+ }
+ out := string(sess.Conn.(*chanConn).Bytes())
+ if !strings.Contains(out, "You harvest 2 ") {
+ t.Errorf("reported yield must match what was placed: %q", out)
+ }
+ })
+}
+
+func TestHarvestNonStackableFullInventoryKeepsCrop(t *testing.T) {
+ h := newHarness(t)
+ h.seedAccount("alice")
+ p := h.seedCharacter("alice", "alice")
+ writeSeedFile(t, filepath.Join(h.dir, "items", hardeningCropID+".yaml"),
+ []byte("id: test_potato\nname: test potato\n"))
+ sess := &net.Session{Conn: newChanConn(), Player: p}
+ sess.SetState(net.StateGame)
+ h.g.RunOnOwner(func() {
+ h.g.ItemStore.Reload(h.dir)
+ for i := 0; i < 28; i++ {
+ p.SetInvSlot(i, &player.InventorySlot{ItemID: "filler", Quantity: 1})
+ }
+ readyHarvest(p, 5, 5)
+
+ h.g.advanceHarvest(sess, p)
+
+ if got := p.CountItem(hardeningCropID); got != 0 {
+ t.Errorf("harvest stored %d potatoes with no room", got)
+ }
+ if ready, _ := p.Flags[hardeningCropPrefix+"_ready"].(bool); !ready {
+ t.Error("crop was consumed even though nothing could be stored")
+ }
+ if out := string(sess.Conn.(*chanConn).Bytes()); strings.Contains(out, "You harvest") {
+ t.Errorf("reported a yield that was not delivered: %q", out)
+ }
+ })
+}