package storage import ( "errors" "testing" "time" ) func TestMischiefOrderLifecycle(t *testing.T) { setupTestDB(t) o, err := InsertMischiefOrder("sub-1", "reala", "tok-josie", "Josie", "elite", true) if err != nil { t.Fatal(err) } if o.Status != MischiefPending { t.Fatalf("fresh order status = %q, want pending", o.Status) } if !o.Signed { t.Error("signed flag lost through insert") } pending, err := PendingMischiefOrders(10) if err != nil { t.Fatal(err) } if len(pending) != 1 || pending[0].GUID != o.GUID { t.Fatalf("pending = %+v, want the one order we just placed", pending) } // gogobee places the contract. got, err := ResolveMischiefOrder(o.GUID, MischiefPlaced, "the word is out") if err != nil { t.Fatal(err) } if got.Status != MischiefPlaced || got.Detail != "the word is out" { t.Fatalf("resolved order = %+v, want placed with detail", got) } // It must leave the pending set. if pending, _ := PendingMischiefOrders(10); len(pending) != 0 { t.Fatalf("placed order still pending: %+v", pending) } } // TestMischiefResolveIsIdempotent is the whole reason the guid is an end-to-end // key: gogobee's poll loop retries, so a verdict can arrive twice, and the second // arrival must not overwrite the first or error. func TestMischiefResolveIsIdempotent(t *testing.T) { setupTestDB(t) o, err := InsertMischiefOrder("sub-1", "reala", "tok", "Josie", "grunt", false) if err != nil { t.Fatal(err) } if _, err := ResolveMischiefOrder(o.GUID, MischiefPlaced, "first"); err != nil { t.Fatal(err) } // A second, *different* verdict arrives. First one wins. got, err := ResolveMischiefOrder(o.GUID, MischiefBouncedFunds, "second") if err != nil { t.Fatalf("re-resolve errored: %v", err) } if got.Status != MischiefPlaced || got.Detail != "first" { t.Fatalf("idempotency broken: order became %+v", got) } } func TestMischiefResolveUnknownAndBadVerdict(t *testing.T) { setupTestDB(t) if _, err := ResolveMischiefOrder("nope", MischiefPlaced, ""); !errors.Is(err, ErrNoSuchOrder) { t.Fatalf("unknown guid err = %v, want ErrNoSuchOrder", err) } o, _ := InsertMischiefOrder("sub-1", "reala", "tok", "Josie", "grunt", false) if _, err := ResolveMischiefOrder(o.GUID, "exploded", ""); err == nil { t.Error("a bogus verdict status was accepted") } // The order must survive a rejected verdict as still-pending. if got, _ := MischiefOrderByGUID(o.GUID); got.Status != MischiefPending { t.Fatalf("order moved off pending on a bad verdict: %q", got.Status) } } func TestMischiefOrdersByBuyerAndCount(t *testing.T) { setupTestDB(t) for i := 0; i < 3; i++ { if _, err := InsertMischiefOrder("sub-A", "alice", "tok", "Josie", "grunt", false); err != nil { t.Fatal(err) } } if _, err := InsertMischiefOrder("sub-B", "bob", "tok", "Josie", "grunt", false); err != nil { t.Fatal(err) } mine, err := MischiefOrdersByBuyer("sub-A", 20) if err != nil { t.Fatal(err) } if len(mine) != 3 { t.Fatalf("alice sees %d orders, want 3 (and none of bob's)", len(mine)) } n, err := CountMischiefOrdersSince("sub-A", time.Now().Add(-time.Hour).Unix()) if err != nil { t.Fatal(err) } if n != 3 { t.Fatalf("count since an hour ago = %d, want 3", n) } if n, _ := CountMischiefOrdersSince("sub-A", time.Now().Add(time.Hour).Unix()); n != 0 { t.Fatalf("count since the future = %d, want 0", n) } } func TestMischiefTiersReplaceAndLookup(t *testing.T) { setupTestDB(t) tiers := []MischiefTier{ {Key: "grunt", Display: "Grunt", Fee: 40, SignedFee: 50, Blurb: "theatre"}, {Key: "boss", Display: "Boss", Fee: 1200, SignedFee: 1500}, } if err := ReplaceMischiefTiers(tiers); err != nil { t.Fatal(err) } got, err := MischiefTiers() if err != nil { t.Fatal(err) } if len(got) != 2 || got[0].Key != "grunt" || got[1].Key != "boss" { t.Fatalf("catalog order not preserved: %+v", got) } tier, ok, err := MischiefTierByKey("boss") if err != nil || !ok || tier.SignedFee != 1500 { t.Fatalf("lookup boss = %+v ok=%v err=%v", tier, ok, err) } if _, ok, _ := MischiefTierByKey("dragon"); ok { t.Error("lookup invented a tier that was never pushed") } // Replace, never merge: a dropped tier vanishes. if err := ReplaceMischiefTiers([]MischiefTier{{Key: "grunt", Display: "Grunt", Fee: 40, SignedFee: 50}}); err != nil { t.Fatal(err) } if _, ok, _ := MischiefTierByKey("boss"); ok { t.Error("a tier dropped from the push survived the replace") } } func TestUserEuroReplaceAndRead(t *testing.T) { setupTestDB(t) if err := ReplaceUserEuro([]MischiefBalance{{Username: "reala", Euro: 820.5}}, time.Now().Unix()); err != nil { t.Fatal(err) } euro, has, err := UserEuro("reala") if err != nil || !has || euro != 820.5 { t.Fatalf("UserEuro(reala) = %v has=%v err=%v", euro, has, err) } // A user gogobee has never reported reads as "unknown", not €0 — the // storefront shows no hint rather than a discouraging, wrong zero. if _, has, _ := UserEuro("stranger"); has { t.Error("UserEuro claimed to know a stranger's balance") } // Replace drops anyone the new snapshot omits. if err := ReplaceUserEuro([]MischiefBalance{{Username: "bob", Euro: 10}}, time.Now().Unix()); err != nil { t.Fatal(err) } if _, has, _ := UserEuro("reala"); has { t.Error("a balance that fell out of the snapshot survived the replace") } }