Files
gogobee/internal/plugin/expedition_autorun_digest_test.go
prosolis 5a6e395805 Long expeditions D4-a: autopilot DM bundling + EoD digest
Suppress per-tick auto-walk DMs in compact mode. tryAutoRun now only
surfaces for fork / death / run-complete / boss-safety camp / Night-camp
pitch. A Night-camp pitch flushes the day as an EoD digest (counts of
walk/harvest/interrupt plus threat/milestone/narrative bullets, built
from dnd_expedition_log) followed by the camp block. Each successful
background walk writes a `walk` log entry so the digest can count rooms
without persisting raw stream narration. maybeAutoCamp and
pitchBossSafetyCamp now return the autoCampDecision so callers can
branch on dec.Night.
2026-05-27 19:07:37 -07:00

125 lines
4.2 KiB
Go

package plugin
import (
"strings"
"testing"
"maunium.net/go/mautrix/id"
)
// Long-expedition D4-a — verifies the EoD digest renderer pulls only
// the prior day's structured entries and that buildAutoRunDM's surface
// rules suppress / surface the right ticks.
func TestRenderEndOfDayDigest_GroupsByType(t *testing.T) {
setupZoneRunTestDB(t)
uid := id.UserID("@digest:example")
campTestCharacter(t, uid, 1)
defer cleanupExpeditions(uid)
exp, err := startExpedition(uid, ZoneGoblinWarrens, "",
ExpeditionSupplies{Current: 5, Max: 5, DailyBurn: 1, HarshMod: 1})
if err != nil {
t.Fatal(err)
}
day := exp.CurrentDay
for _, e := range []struct{ kind, summary, flavor string }{
{"walk", "auto-walk: 3 room(s)", ""},
{"walk", "auto-walk: 2 room(s)", ""},
{"harvest", "autopilot harvest oak success", ""},
{"harvest", "autopilot harvest iron failure", ""}, // not counted
{"interrupt", "noise alarm total=1", ""},
{"threat", "drift", "The dungeon stirs awake."},
{"milestone", "milestone awarded: first-night", ""},
{"narrative", "autopilot broke camp (dwell elapsed)", ""}, // filtered
{"narrative", "voluntary extraction prompt", ""},
} {
if err := appendExpeditionLog(exp.ID, day, e.kind, e.summary, e.flavor); err != nil {
t.Fatal(err)
}
}
// Entry from a different day must NOT bleed in.
if err := appendExpeditionLog(exp.ID, day+1, "walk", "next day", ""); err != nil {
t.Fatal(err)
}
got := renderEndOfDayDigest(exp.ID, day)
if got == "" {
t.Fatal("expected non-empty digest")
}
wantSubs := []string{
"Walked **2**",
"Handled **1** interrupt",
"**1** harvest",
"dungeon stirs awake",
"milestone awarded: first-night",
"voluntary extraction prompt",
}
for _, s := range wantSubs {
if !strings.Contains(got, s) {
t.Errorf("digest missing %q\n--- got ---\n%s", s, got)
}
}
if strings.Contains(got, "autopilot broke camp") {
t.Errorf("digest should filter housekeeping narrative\n%s", got)
}
if strings.Contains(got, "next day") {
t.Errorf("digest leaked entry from a later day\n%s", got)
}
}
func TestRenderEndOfDayDigest_EmptyDayReturnsEmpty(t *testing.T) {
setupZoneRunTestDB(t)
uid := id.UserID("@digest-empty:example")
campTestCharacter(t, uid, 1)
defer cleanupExpeditions(uid)
exp, err := startExpedition(uid, ZoneGoblinWarrens, "",
ExpeditionSupplies{Current: 5, Max: 5, DailyBurn: 1, HarshMod: 1})
if err != nil {
t.Fatal(err)
}
if got := renderEndOfDayDigest(exp.ID, exp.CurrentDay); got != "" {
t.Errorf("expected empty digest for a day with no entries, got %q", got)
}
}
func TestBuildAutoRunDM_SuppressesQuietTick(t *testing.T) {
r := autopilotWalkResult{rooms: 3, reason: stopOK, stream: []string{"…walked…"}}
body, ok := buildAutoRunDM("expid", r, "", autoCampDecision{})
if ok || body != "" {
t.Errorf("quiet stopOK tick should be silent, got ok=%v body=%q", ok, body)
}
}
func TestBuildAutoRunDM_ForkSurfaces(t *testing.T) {
r := autopilotWalkResult{rooms: 1, reason: stopFork, finalMsg: "pick a path"}
body, ok := buildAutoRunDM("expid", r, "", autoCampDecision{})
if !ok || !strings.Contains(body, "pick a path") {
t.Errorf("fork should surface, got ok=%v body=%q", ok, body)
}
}
func TestBuildAutoRunDM_NonNightCampSurfaces(t *testing.T) {
r := autopilotWalkResult{rooms: 2, reason: stopPreflight, finalMsg: "low hp"}
camp := "\n\n⛺ **Autopilot camp**"
body, ok := buildAutoRunDM("expid", r, camp, autoCampDecision{Kind: CampTypeRough})
if !ok || !strings.Contains(body, "Autopilot camp") {
t.Errorf("mid-day camp pitch should surface camp block, got ok=%v body=%q", ok, body)
}
// Walk stream/final message dropped — D4-a suppresses the walk narration.
if strings.Contains(body, "low hp") {
t.Errorf("non-night camp DM should not carry walk finalMsg, got %q", body)
}
}
func TestBuildAutoRunDM_BossSafetyHasHoldHeader(t *testing.T) {
r := autopilotWalkResult{rooms: 0, reason: stopBossSafety}
camp := "\n\n⛺ **Autopilot camp — Standard.**"
dec := autoCampDecision{Kind: CampTypeStandard, Reason: "boss-safety hold — resting before re-engaging"}
body, ok := buildAutoRunDM("expid", r, camp, dec)
if !ok || !strings.Contains(body, "Holding before the boss") {
t.Errorf("boss-safety camp should prepend hold header, got ok=%v body=%q", ok, body)
}
}