Files
Pete/internal/storage/orders_test.go
T
prosolis 6b0aae9f4a adventure: let a player act from the web, not just read about it
The equip queue proved the reverse pipe works. This gives it verbs that
play the game: pull out of a run from the adventurer page, take today's
bout from the war room.

Its own table and its own poll, not more actions on equip_orders. Every
column of that table is equip vocabulary (item, slot, tier) and these
verbs act on the character rather than on something it is carrying.

Nothing in a request names an adventurer. The session maps to one
localpart and a localpart to one adventurer, so Pete resolves the
character itself and there is no id on the wire to forge.

The panel's copy is kept honest by the verdict: an applied action hides
the offer it has just spent, a refusal puts the button back. Watching it
run is what put that there, along with the strip's layout — gogobee
answers a bout with a whole sentence of damage, which the equip strip's
two-column row squeezed into a column and wrapped the verb.

Claude-Session: https://claude.ai/code/session_012bxpQQJDjC1mTtLN3VVtBQ
2026-07-24 18:55:42 -07:00

114 lines
3.6 KiB
Go

package storage
import (
"errors"
"testing"
)
func TestAdvOrderRoundTrip(t *testing.T) {
setupTestDB(t)
o, err := InsertAdvOrder("sub-1", "josie", "tok-josie", "Josie", AdvActionExtract)
if err != nil {
t.Fatalf("insert: %v", err)
}
if o.GUID == "" || o.Status != AdvOrderPending {
t.Fatalf("order = %+v, want a guid and pending", o)
}
pending, err := PendingAdvOrders(10)
if err != nil {
t.Fatalf("pending: %v", err)
}
if len(pending) != 1 || pending[0].GUID != o.GUID || pending[0].Action != AdvActionExtract {
t.Fatalf("pending = %+v", pending)
}
got, err := ResolveAdvOrder(o.GUID, AdvOrderApplied, "out on day 3")
if err != nil {
t.Fatalf("resolve: %v", err)
}
if got.Status != AdvOrderApplied || got.Detail != "out on day 3" {
t.Fatalf("resolved = %+v", got)
}
if left, _ := PendingAdvOrders(10); len(left) != 0 {
t.Fatalf("a resolved order is still pending: %+v", left)
}
}
// TestAdvOrderVerdictOnlyMovesAPendingRow is the idempotency mechanic: gogobee
// retries its verdict push, so the second one must be a read, not a write.
func TestAdvOrderVerdictOnlyMovesAPendingRow(t *testing.T) {
setupTestDB(t)
o, _ := InsertAdvOrder("sub-1", "josie", "tok-josie", "Josie", AdvActionSiegeJoin)
if _, err := ResolveAdvOrder(o.GUID, AdvOrderApplied, "first"); err != nil {
t.Fatalf("first verdict: %v", err)
}
got, err := ResolveAdvOrder(o.GUID, AdvRejectedNoSiege, "second")
if err != nil {
t.Fatalf("second verdict: %v", err)
}
if got.Status != AdvOrderApplied || got.Detail != "first" {
t.Fatalf("order = %q/%q, want the first verdict to stand", got.Status, got.Detail)
}
}
func TestAdvOrderRejectsBadInput(t *testing.T) {
setupTestDB(t)
if _, err := InsertAdvOrder("sub-1", "josie", "tok", "Josie", "sell_house"); err == nil {
t.Fatal("an unknown action was accepted")
}
o, _ := InsertAdvOrder("sub-1", "josie", "tok", "Josie", AdvActionExtract)
if _, err := ResolveAdvOrder(o.GUID, "exploded", ""); err == nil {
t.Fatal("an unknown verdict was accepted")
}
if _, err := AdvOrderByGUID("nope"); !errors.Is(err, ErrNoSuchAdvOrder) {
t.Fatalf("unknown guid err = %v, want ErrNoSuchAdvOrder", err)
}
}
// TestHasPendingAdvOrderIsPerVerb: the guard stops a double-click on one button,
// not the other button.
func TestHasPendingAdvOrderIsPerVerb(t *testing.T) {
setupTestDB(t)
o, _ := InsertAdvOrder("sub-1", "josie", "tok", "Josie", AdvActionExtract)
if got, _ := HasPendingAdvOrder("sub-1", AdvActionExtract); !got {
t.Fatal("a pending extract wasn't seen")
}
if got, _ := HasPendingAdvOrder("sub-1", AdvActionSiegeJoin); got {
t.Fatal("a pending extract blocked a bout")
}
if got, _ := HasPendingAdvOrder("sub-2", AdvActionExtract); got {
t.Fatal("one owner's pending order was seen for another")
}
// A resolved order stops holding the verb.
if _, err := ResolveAdvOrder(o.GUID, AdvOrderApplied, ""); err != nil {
t.Fatalf("resolve: %v", err)
}
if got, _ := HasPendingAdvOrder("sub-1", AdvActionExtract); got {
t.Fatal("a resolved order still holds its verb")
}
}
func TestAdvOrdersByOwnerScopes(t *testing.T) {
setupTestDB(t)
if _, err := InsertAdvOrder("sub-A", "alice", "tok-a", "Alice", AdvActionExtract); err != nil {
t.Fatalf("insert: %v", err)
}
if _, err := InsertAdvOrder("sub-B", "bob", "tok-b", "Bob", AdvActionExtract); err != nil {
t.Fatalf("insert: %v", err)
}
got, err := AdvOrdersByOwner("sub-A", 10)
if err != nil {
t.Fatalf("by owner: %v", err)
}
if len(got) != 1 || got[0].OwnerLocalpart != "alice" {
t.Fatalf("orders = %+v, want only alice's", got)
}
if n, _ := CountAdvOrdersSince("sub-A", 0); n != 1 {
t.Fatalf("count = %d, want 1", n)
}
}