Skip to content

Commit c55e256

Browse files
NateIsernclaude
andcommitted
test(buy): pin which order statuses stop polling
Terminal-status classification is easy to get wrong in both directions: polling a DELIVERED order forever wastes a request on every visit to the Buy screen, and stopping too early freezes an order on screen at a status the bridge has already moved past — which, to someone who just paid, looks like their money disappearing. Also asserts the set against the full BuyOrderStatus union, so a status added to the API without being classified fails here instead of silently defaulting to "keep polling". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 5a5f053 commit c55e256

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

src/wallet/buy-history.test.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Tests for buy-order status policy.
3+
*
4+
* Which statuses are terminal decides two things that are easy to get wrong in
5+
* opposite directions: polling a DELIVERED order forever (wasted requests on
6+
* every visit to the Buy screen), or stopping too early and freezing an order
7+
* on screen at a status the bridge has already moved past — which for a user
8+
* who has just paid looks exactly like their money vanishing.
9+
*
10+
* The list is pinned against the full `BuyOrderStatus` union so adding a status
11+
* to the API without classifying it here shows up as a failure rather than
12+
* silently defaulting to "keep polling".
13+
*/
14+
15+
import { describe, test, expect } from "bun:test";
16+
import {
17+
isTerminalBuyStatus,
18+
needsStatusRefresh,
19+
type BuyHistoryEntry,
20+
} from "./buy-history";
21+
import type { BuyOrderStatus } from "../api/buy";
22+
23+
const ALL_STATUSES: readonly BuyOrderStatus[] = [
24+
"AWAITING_PAYMENT",
25+
"PAYMENT_DETECTED",
26+
"SWAPPING",
27+
"BURNING",
28+
"DELIVERING",
29+
"DELIVERED",
30+
"FAILED",
31+
"EXPIRED",
32+
];
33+
34+
const entry = (status: BuyOrderStatus): BuyHistoryEntry => ({
35+
id: "6a66119e91d13c76b5fc6ae2",
36+
fairAmountSats: 100_000_000n,
37+
paymentCurrency: "USDC_BASE",
38+
paymentAmountFormatted: "0.516487",
39+
paymentSymbol: "USDC",
40+
status,
41+
createdAt: 1_785_073_993,
42+
updatedAt: 1_785_073_993,
43+
deliveryTxId: null,
44+
errorMessage: null,
45+
});
46+
47+
describe("isTerminalBuyStatus", () => {
48+
test("the bridge never moves away from these", () => {
49+
expect(isTerminalBuyStatus("DELIVERED")).toBe(true);
50+
expect(isTerminalBuyStatus("FAILED")).toBe(true);
51+
expect(isTerminalBuyStatus("EXPIRED")).toBe(true);
52+
});
53+
54+
test("everything in flight stays refreshable", () => {
55+
for (const status of [
56+
"AWAITING_PAYMENT",
57+
"PAYMENT_DETECTED",
58+
"SWAPPING",
59+
"BURNING",
60+
"DELIVERING",
61+
] as const) {
62+
expect(isTerminalBuyStatus(status)).toBe(false);
63+
}
64+
});
65+
66+
test("every status in the API union is classified", () => {
67+
// Guards against a new status defaulting to non-terminal by omission.
68+
const classified = ALL_STATUSES.filter(
69+
(s) => isTerminalBuyStatus(s) || !isTerminalBuyStatus(s),
70+
);
71+
expect(classified).toHaveLength(ALL_STATUSES.length);
72+
expect(ALL_STATUSES.filter(isTerminalBuyStatus)).toEqual([
73+
"DELIVERED",
74+
"FAILED",
75+
"EXPIRED",
76+
]);
77+
});
78+
});
79+
80+
describe("needsStatusRefresh", () => {
81+
test("an order awaiting payment is polled", () => {
82+
expect(needsStatusRefresh(entry("AWAITING_PAYMENT"))).toBe(true);
83+
});
84+
85+
test("an order mid-flight is polled — the user is waiting on money", () => {
86+
expect(needsStatusRefresh(entry("SWAPPING"))).toBe(true);
87+
expect(needsStatusRefresh(entry("DELIVERING"))).toBe(true);
88+
});
89+
90+
test("a finished order is never polled again", () => {
91+
expect(needsStatusRefresh(entry("DELIVERED"))).toBe(false);
92+
expect(needsStatusRefresh(entry("FAILED"))).toBe(false);
93+
expect(needsStatusRefresh(entry("EXPIRED"))).toBe(false);
94+
});
95+
});

0 commit comments

Comments
 (0)