mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
Each walked room gets one auto-harvest pass across every applicable
action (forage, scavenge, mine, fish in water zones, plus the class-
restricted essence/commune). Class/kill/event gates are inherited
from the manual harvest path.
Stop rules:
- Rare+ nodes are NOT auto-attempted; they pause the walk via
stopRareNode so the player spends the attempt deliberately.
- Standard/Elite/Patrol interrupts hard-stop the walk (stopEnded on
death, new stopHarvestCombat on survive).
- Noise interrupts apply threat+2 and continue, matching manual
!harvest behavior.
Display:
- Per-room compact footer ("+2 Scrap Iron, +1 Shadow Herb · 1 fail")
streamed inline as autopilot walks.
- Walk-end cumulative tally appended to whatever final block fires.
No SU surcharge — parity with manual !harvest.
Pure helpers unit-tested (rarity classification, footer rendering,
walk tally sort order, rare-pending dedup). Full autoHarvestRoom
path needs prod DB and follows the existing setupAuditTestDB gating
pattern.
111 lines
3.6 KiB
Go
111 lines
3.6 KiB
Go
package plugin
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Pure-logic tests for the Phase 2 auto-harvest helpers. The full
|
|
// autoHarvestRoom path needs the prod DB (loads zone runs / expeditions /
|
|
// region state) and lives behind the same setupAuditTestDB gate as the
|
|
// other expedition tests.
|
|
|
|
func TestIsRarePlus(t *testing.T) {
|
|
cases := []struct {
|
|
in DnDRarity
|
|
want bool
|
|
}{
|
|
{RarityCommon, false},
|
|
{RarityUncommon, false},
|
|
{RarityRare, true},
|
|
{RarityEpic, true},
|
|
{RarityVeryRare, true},
|
|
{RarityLegendary, true},
|
|
{"", false},
|
|
}
|
|
for _, c := range cases {
|
|
if got := isRarePlus(c.in); got != c.want {
|
|
t.Errorf("isRarePlus(%q) = %v, want %v", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRenderAutoHarvestFooter_EmptyIsEmpty(t *testing.T) {
|
|
s := autoHarvestSummary{Yields: map[string]int{}, Names: map[string]string{}}
|
|
if got := renderAutoHarvestFooter(s); got != "" {
|
|
t.Errorf("expected empty footer for empty summary, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestRenderAutoHarvestFooter_YieldsAndFailsAndNoise(t *testing.T) {
|
|
s := autoHarvestSummary{
|
|
Yields: map[string]int{"iron": 2, "herb": 1},
|
|
Names: map[string]string{"iron": "Scrap Iron", "herb": "Shadow Herb"},
|
|
Fails: 1,
|
|
NoiseInts: 2,
|
|
}
|
|
got := renderAutoHarvestFooter(s)
|
|
// Deterministic ordering: yield keys sorted alphabetically.
|
|
for _, want := range []string{"+1 Shadow Herb", "+2 Scrap Iron", "1 fail", "2 close calls"} {
|
|
if !strings.Contains(got, want) {
|
|
t.Errorf("footer %q missing %q", got, want)
|
|
}
|
|
}
|
|
// Singular "close call" when noise=1.
|
|
s.NoiseInts = 1
|
|
got = renderAutoHarvestFooter(s)
|
|
if !strings.Contains(got, "1 close call") || strings.Contains(got, "close calls") {
|
|
t.Errorf("expected singular 'close call', got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestRenderWalkTally_EmptyIsEmpty(t *testing.T) {
|
|
if got := renderWalkTally(map[string]int{}, map[string]string{}); got != "" {
|
|
t.Errorf("expected empty tally, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestRenderWalkTally_SortsByID(t *testing.T) {
|
|
got := renderWalkTally(
|
|
map[string]int{"zinc": 1, "alpha": 2, "mid": 3},
|
|
map[string]string{"zinc": "Zinc", "alpha": "Alphite", "mid": "Mid Ore"},
|
|
)
|
|
// "alpha" < "mid" < "zinc" so the rendered order is Alphite, Mid Ore, Zinc.
|
|
if !strings.HasPrefix(got, "**Walk haul:** 2 Alphite") {
|
|
t.Errorf("tally not sorted by id: %q", got)
|
|
}
|
|
if iAlpha, iZinc := strings.Index(got, "Alphite"), strings.Index(got, "Zinc"); iAlpha >= iZinc {
|
|
t.Errorf("Alphite should precede Zinc: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestRenderRarePendingFooter_EmptyIsEmpty(t *testing.T) {
|
|
if got := renderRarePendingFooter(nil); got != "" {
|
|
t.Errorf("expected empty rare-pending footer, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestRenderRarePendingFooter_DedupsByID(t *testing.T) {
|
|
r := ZoneResource{ID: "war_standard", Name: "Hobgoblin War Standard", Rarity: RarityRare, Action: HarvestScavenge}
|
|
got := renderRarePendingFooter([]ZoneResource{r, r, r})
|
|
// Single mention even with 3 copies in the pending slice.
|
|
if strings.Count(got, "Hobgoblin War Standard") != 1 {
|
|
t.Errorf("expected single mention, got %q", got)
|
|
}
|
|
if !strings.Contains(got, "!scavenge") {
|
|
t.Errorf("expected action hint '!scavenge' in footer, got %q", got)
|
|
}
|
|
if !strings.Contains(got, "Rare") {
|
|
t.Errorf("expected rarity label in footer, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestRenderRarePendingFooter_MultiplePlural(t *testing.T) {
|
|
a := ZoneResource{ID: "a", Name: "Aaa", Rarity: RarityRare, Action: HarvestForage}
|
|
b := ZoneResource{ID: "b", Name: "Bbb", Rarity: RarityVeryRare, Action: HarvestForage}
|
|
got := renderRarePendingFooter([]ZoneResource{a, b})
|
|
if !strings.Contains(got, "are sitting in this room") {
|
|
t.Errorf("expected plural verb for two items, got %q", got)
|
|
}
|
|
}
|