-
Notifications
You must be signed in to change notification settings - Fork 60
Expand file tree
/
Copy pathconfirmationPriceSnapshot.test.ts
More file actions
175 lines (147 loc) · 5.68 KB
/
Copy pathconfirmationPriceSnapshot.test.ts
File metadata and controls
175 lines (147 loc) · 5.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import * as ApiInternal from "@shared/api/internal";
import { ApiTokenPrices } from "@shared/api/types";
import { TESTNET_NETWORK_DETAILS } from "@shared/constants/stellar";
import { startConfirmationPriceSnapshot } from "./confirmationPriceSnapshot";
const flushMicrotasks = () => new Promise((resolve) => setTimeout(resolve, 0));
describe("startConfirmationPriceSnapshot", () => {
afterEach(() => {
jest.restoreAllMocks();
});
it("uses the freshly fetched prices once the fetch has settled (confirmation_fetch)", async () => {
jest
.spyOn(ApiInternal, "getTokenPrices")
.mockResolvedValue({ native: { currentPrice: "0.5" } });
const handle = startConfirmationPriceSnapshot({
canonicalIds: ["native"],
networkDetails: TESTNET_NETWORK_DETAILS,
useV2: true,
cachedDisplayPrices: { native: { currentPrice: "0.1" } },
});
await flushMicrotasks();
expect(handle.resolve()).toEqual({
pricesById: { native: { currentPrice: "0.5" } },
freshness: "confirmation_fetch",
source: "token_prices_v2",
});
});
it("falls back to the cached display prices when the fetch hasn't settled yet (cached_display)", () => {
// Never resolves within this test — resolve() is called before any await.
jest
.spyOn(ApiInternal, "getTokenPrices")
.mockImplementation(() => new Promise(() => {}));
const handle = startConfirmationPriceSnapshot({
canonicalIds: ["native"],
networkDetails: TESTNET_NETWORK_DETAILS,
useV2: false,
cachedDisplayPrices: { native: { currentPrice: "0.1" } },
});
expect(handle.resolve()).toEqual({
pricesById: { native: { currentPrice: "0.1" } },
freshness: "cached_display",
source: "token_prices_v1",
});
});
it("falls back to the cached display prices when the fetch rejects", async () => {
jest
.spyOn(ApiInternal, "getTokenPrices")
.mockRejectedValue(new Error("network down"));
const handle = startConfirmationPriceSnapshot({
canonicalIds: ["native"],
networkDetails: TESTNET_NETWORK_DETAILS,
useV2: true,
cachedDisplayPrices: { native: { currentPrice: "0.1" } },
});
await flushMicrotasks();
// A rejected fetch degrades exactly like a still-pending one: coverage
// takes priority over freshness, and the degradation is visible via
// `cached_display` rather than reported as unpriced legs.
expect(handle.resolve()).toEqual({
pricesById: { native: { currentPrice: "0.1" } },
freshness: "cached_display",
source: "token_prices_v2",
});
});
it("degrades to a null snapshot (not a throw) when the fetch rejects and no display price is cached", async () => {
jest
.spyOn(ApiInternal, "getTokenPrices")
.mockRejectedValue(new Error("network down"));
const handle = startConfirmationPriceSnapshot({
canonicalIds: ["native"],
networkDetails: TESTNET_NETWORK_DETAILS,
useV2: true,
cachedDisplayPrices: null,
});
await flushMicrotasks();
expect(handle.resolve()).toEqual({
pricesById: null,
freshness: "cached_display",
source: "token_prices_v2",
});
});
it("aborts a still-pending fetch at resolve() so the request cannot outlive the flow", () => {
let capturedSignal: AbortSignal | undefined;
jest
.spyOn(ApiInternal, "getTokenPrices")
.mockImplementation((_tokens, _network, _useV2, signal) => {
capturedSignal = signal;
return new Promise(() => {});
});
const handle = startConfirmationPriceSnapshot({
canonicalIds: ["native"],
networkDetails: TESTNET_NETWORK_DETAILS,
useV2: false,
cachedDisplayPrices: null,
});
expect(capturedSignal?.aborted).toBe(false);
handle.resolve();
expect(capturedSignal?.aborted).toBe(true);
});
it("cancel() aborts the fetch without producing a snapshot (pre-submission failure)", () => {
let capturedSignal: AbortSignal | undefined;
jest
.spyOn(ApiInternal, "getTokenPrices")
.mockImplementation((_tokens, _network, _useV2, signal) => {
capturedSignal = signal;
return new Promise(() => {});
});
const handle = startConfirmationPriceSnapshot({
canonicalIds: ["native"],
networkDetails: TESTNET_NETWORK_DETAILS,
useV2: false,
cachedDisplayPrices: null,
});
handle.cancel();
expect(capturedSignal?.aborted).toBe(true);
// Idempotent, and safe to combine with a later resolve().
handle.cancel();
expect(handle.resolve().freshness).toBe("cached_display");
});
it("never consults a late-arriving result after resolve() already ran", async () => {
let resolveFetch!: (value: ApiTokenPrices) => void;
jest.spyOn(ApiInternal, "getTokenPrices").mockImplementation(
() =>
new Promise((resolve) => {
resolveFetch = resolve;
}),
);
const handle = startConfirmationPriceSnapshot({
canonicalIds: ["native"],
networkDetails: TESTNET_NETWORK_DETAILS,
useV2: true,
cachedDisplayPrices: { native: { currentPrice: "0.2" } },
});
// Not settled yet — this is the snapshot the terminal event uses.
const frozen = handle.resolve();
expect(frozen.freshness).toBe("cached_display");
// The fetch resolves only after the snapshot was already frozen.
resolveFetch({ native: { currentPrice: "999" } });
await flushMicrotasks();
// Calling resolve() again would now see it as settled — proving the
// *first* frozen snapshot (already returned above) never changes.
expect(frozen).toEqual({
pricesById: { native: { currentPrice: "0.2" } },
freshness: "cached_display",
source: "token_prices_v2",
});
});
});