mirror of
https://github.com/prosolis/gogobee.git
synced 2026-09-14 10:51:09 +00:00
adventure: run the web's three new verbs through the game's own paths
The game-side half of setting out, going back in, and hiring the sitter from the web. Each is the existing command minus its framing: performExpeditionStart, performResume and performBabysitPurchase now hold the guards and the money, and !expedition start, !resume and !adventure babysit are what is left over. So a departure booked from a phone is the same departure - same eligibility chain, same supply freebies, same opening log line - rather than a second one that drifts. Refusals travel as advRefusal, which wraps a sentinel AND carries the finished sentence. That is what lets the commands keep the exact copy they always sent while the web gets a machine-readable verdict. All three spend coins on a retrying wire, so the debit is keyed to the order guid and a re-offer cannot charge twice. The subtle half is what a re-offer should ANSWER: a settled debit plus an already-started expedition means the order worked and lost its ack, not that the player is busy, so it reports applied instead of refusing the thing it did. Nothing refunds-then-retries - after a refund the keyed debit will not charge again, so a retry would hand over the goods for free, and every failure past the debit is therefore permanent. Also fixes a deadlock that predates all of this: !expedition extract and !expedition resume are aliases for two commands that take the per-user lock themselves, and the alias dispatcher already held it. Since it is a plain sync.Mutex the handler blocked forever and, because the deferred unlock never ran, every later adventure command from that player wedged too. It does not fail loudly on regression - it hangs - so the new test asserts with a timeout. Claude-Session: https://claude.ai/code/session_012bxpQQJDjC1mTtLN3VVtBQ
This commit is contained in:
@@ -175,3 +175,28 @@ func TestResume_WindowExpired(t *testing.T) {
|
||||
time.Since(*got.CompletedAt), extractResumeWindow)
|
||||
}
|
||||
}
|
||||
|
||||
// `!expedition extract` and `!expedition resume` are aliases for two top-level
|
||||
// commands that take the per-user lock themselves. If the alias dispatcher takes
|
||||
// that lock first the handler blocks on it forever and, because the deferred
|
||||
// unlock never runs, every later adventure command from that player wedges too.
|
||||
// This does not fail on regression — it hangs — so the timeout is the assertion.
|
||||
func TestExpeditionAliasesDoNotWedgeTheUserLock(t *testing.T) {
|
||||
setupEmptyTestDB(t)
|
||||
uid := id.UserID("@exp-alias-lock:example")
|
||||
t.Cleanup(func() { cleanupExpeditions(uid) })
|
||||
|
||||
for _, sub := range []string{"extract", "resume"} {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
p := &AdventurePlugin{euro: &EuroPlugin{}}
|
||||
_ = p.handleDnDExpeditionCmd(MessageContext{Sender: uid}, sub)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("!expedition %s never returned: the alias re-took advUserLock", sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user