Files
gogobee/internal/plugin/dnd_zone_harvest_test.go
prosolis 4e412219f3 WIP: in-flight combat/expedition/flavor changes
Bundle of uncommitted working-tree edits across combat engine, expedition
cycle, flavor pools, and TwinBee/zone narration. Includes new files:
combat_debug.go, dnd_boss_consumables.go, dnd_dex_floor.go, plus
CHANGES_24H.md and REBALANCE_NOTES.md scratch notes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:59:19 -07:00

98 lines
3.4 KiB
Go

package plugin
import (
"strings"
"testing"
"gogobee/internal/db"
"maunium.net/go/mautrix/id"
)
// Standalone-zone-run harvest path: with no active expedition but an
// active !zone enter run, !forage / !mine / !fish should land yields
// against per-run node storage. Mirrors the bug Rurina hit where harvest
// commands silently bounced because the handler hard-required an
// expedition.
func TestStandaloneHarvest_RoutesToZoneRun(t *testing.T) {
setupAuditTestDB(t)
uid := id.UserID("@stand-harvest-route:example")
zoneCmdTestCharacter(t, uid, 4)
t.Cleanup(func() { cleanupZoneRuns(uid); cleanupExpeditions(uid) })
run, err := startZoneRun(uid, ZoneForestShadows, 4, nil)
if err != nil {
t.Fatal(err)
}
// Mark room 0 (entry) as cleared so harvest is allowed there.
if !currentRoomCleared(run) {
t.Fatalf("entry room should auto-clear")
}
p := &AdventurePlugin{}
// No expedition exists for this user — handler must fall through to
// the standalone path and not bounce with the expedition-required
// error message.
if err := p.handleHarvestCmd(MessageContext{Sender: uid}, HarvestForage); err != nil {
t.Fatalf("handleHarvestCmd: %v", err)
}
// Side-effect check: the run row should now have non-empty
// harvest_nodes_json since saveStandaloneHarvestNodes ran.
var raw string
row := db.Get().QueryRow(
`SELECT harvest_nodes_json FROM dnd_zone_run WHERE run_id = ?`, run.RunID)
if err := row.Scan(&raw); err != nil {
t.Fatalf("scan harvest_nodes_json: %v", err)
}
if raw == "" || raw == "{}" {
t.Errorf("expected harvest_nodes_json populated after standalone harvest, got %q", raw)
}
// Phase G: the entry node id comes from the zone graph (e.g.
// "forest_shadows.entry"), not the legacy ".r1" derivation. Use the
// production helper so this stays in sync with whatever node id
// saveStandaloneHarvestNodes actually wrote.
wantKey := "\"" + harvestNodeIDFor(run) + "\":"
if !strings.Contains(raw, wantKey) {
t.Errorf("expected entry node %s in harvest table, got %q", wantKey, raw)
}
}
func TestStandaloneHarvest_BouncesWhenNoActiveAnything(t *testing.T) {
setupAuditTestDB(t)
uid := id.UserID("@stand-harvest-noctx:example")
zoneCmdTestCharacter(t, uid, 1)
p := &AdventurePlugin{}
// No expedition, no zone run — should bounce cleanly with the new
// combined hint, not panic on a nil run.
if err := p.handleHarvestCmd(MessageContext{Sender: uid}, HarvestForage); err != nil {
t.Fatalf("handleHarvestCmd: %v", err)
}
}
func TestStandaloneHarvest_FishGatedToFishingZones(t *testing.T) {
setupAuditTestDB(t)
uid := id.UserID("@stand-harvest-fish:example")
zoneCmdTestCharacter(t, uid, 1)
t.Cleanup(func() { cleanupZoneRuns(uid) })
// Goblin Warrens isn't a fishing zone — !fish must reject.
if _, err := startZoneRun(uid, ZoneGoblinWarrens, 1, nil); err != nil {
t.Fatal(err)
}
p := &AdventurePlugin{}
if err := p.handleHarvestCmd(MessageContext{Sender: uid}, HarvestFish); err != nil {
t.Fatalf("handleHarvestCmd: %v", err)
}
// (no easy way to assert the rejection without capturing SendDM —
// the no-error return + no node persistence is the contract.)
var raw string
_ = db.Get().QueryRow(
`SELECT COALESCE(harvest_nodes_json,'') FROM dnd_zone_run
WHERE user_id = ? ORDER BY started_at DESC LIMIT 1`, string(uid)).Scan(&raw)
if raw != "" && raw != "{}" {
t.Errorf("fish in non-fishing zone should not seed nodes; got %q", raw)
}
}