aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/ln-pay-double-wake.test.js39
-rw-r--r--tools/nwc-sw-test.js5
-rw-r--r--tools/sw-push-quota.test.js27
3 files changed, 68 insertions, 3 deletions
diff --git a/tools/ln-pay-double-wake.test.js b/tools/ln-pay-double-wake.test.js
index 18a1943..c29d625 100644
--- a/tools/ln-pay-double-wake.test.js
+++ b/tools/ln-pay-double-wake.test.js
@@ -18,7 +18,7 @@ const asp = { initiate: null, check: null, revoked: 0, checks: 0 };
mock.module('../src/ark/lightning.js', () => ({
...realLn,
initiateLightningPayment: async () => { if (asp.initiate) throw asp.initiate; },
- checkLightningPayment: async () => { asp.checks++; if (asp.check instanceof Error) throw asp.check; return asp.check; },
+ checkLightningPayment: async (_u, _h, wait) => { asp.checks++; (asp.waits = asp.waits || []).push(!!wait); if (asp.check instanceof Error) throw asp.check; return asp.check; },
requestLightningPayHtlcRevocation: async () => { asp.revoked++; throw new Error('revocation should not be asked for'); },
}));
mock.module('../src/ark/proto.js', () => ({ ...realProto, decodeVtxo: () => ({ id: 'htlc1', point: { raw: new Uint8Array(36) } }) }));
@@ -72,3 +72,40 @@ test("a send the ASP never saw (not found) is still revoked", async () => {
await m._driveLnPay(action).catch(() => {});
expect(asp.revoked).toBe(1);
});
+
+// A zap's wait is made of round trips (398ja, 2026-11-10: NWC zaps "a lot more
+// slower"). A send just handed to the ASP used to ask its status at once
+// ("pending"), fetch the chain tip to go with that, hold a call until it
+// settled, then ask once more for the preimage it had just been given: four
+// round trips where the one held call does.
+test('a fresh send: initiate, then the one held call settles it', async () => {
+ Object.assign(asp, { initiate: null, check: { status: 'success', preimage: PRE }, revoked: 0, checks: 0, waits: [] });
+ const { m, action } = mgrWith('cosigned');
+ let tips = 0;
+ m.chain = { tipHeight: async () => { tips++; return 900; } };
+ await m._driveLnPay(action);
+ expect(action.step).toBe('initiated'); // handed over, nothing asked yet
+ expect(asp.checks).toBe(0);
+ expect(tips).toBe(0);
+ const a = await m.awaitLnPay(action.id, 5000);
+ expect(a.step).toBe('done');
+ expect(a.preimage).toBe(PRE);
+ expect(asp.waits).toEqual([true]); // the held call alone
+ expect(m.state.movements.filter((x) => x.type === 'ln-send' && x.status === 'complete').length).toBe(1);
+});
+
+test('a held call that comes back failed is checked the ordinary way (and revoked)', async () => {
+ Object.assign(asp, { initiate: null, check: { status: 'failed' }, revoked: 0, checks: 0, waits: [] });
+ const { m, action } = mgrWith('initiated');
+ m._revokeLnPay = async (x) => { x.step = 'failed'; };
+ const a = await m.awaitLnPay(action.id, 5000);
+ expect(asp.waits[0]).toBe(true);
+ expect(a.step).toBe('failed');
+});
+
+test('a held call with a preimage that does not match the hash settles nothing', async () => {
+ Object.assign(asp, { initiate: null, check: { status: 'success', preimage: 'cd'.repeat(32) }, revoked: 0, checks: 0, waits: [] });
+ const { m, action } = mgrWith('initiated');
+ const a = await m.awaitLnPay(action.id, 5000);
+ expect(a.step).not.toBe('done');
+});
diff --git a/tools/nwc-sw-test.js b/tools/nwc-sw-test.js
index 7e85ee1..18528a2 100644
--- a/tools/nwc-sw-test.js
+++ b/tools/nwc-sw-test.js
@@ -58,7 +58,10 @@ function harness({ rec = baseRec(), recs = null, answered = false } = {}) {
return { ok: true, json: async () => ({ ok: true, scheduled: true }) };
}
if (url.includes('/claim')) {
- return { ok: true, json: async () => ({ ok: true }) };
+ // as the notifier does: a request already replied to can't be claimed
+ const was = typeof answered === 'function' ? answered() : answered;
+ if (JSON.parse(opts.body).release) return { ok: true, json: async () => ({ ok: true, released: true }) };
+ return { ok: true, json: async () => (was ? { ok: false, answered: true } : { ok: true }) };
}
if (url.includes('/answered')) {
return { ok: true, json: async () => ({ answered: typeof answered === 'function' ? answered() : answered }) };
diff --git a/tools/sw-push-quota.test.js b/tools/sw-push-quota.test.js
index 544c72e..798b913 100644
--- a/tools/sw-push-quota.test.js
+++ b/tools/sw-push-quota.test.js
@@ -82,8 +82,33 @@ test('elsewhere: quiet stays quiet', async () => {
let asked = 0;
respond = async () => { asked++; return true; };
expect(await push(nwc)).toEqual([]);
- expect(asked).toBe(0); // the open (if hidden) window answers on Android
+ expect(asked).toBe(0); // on a computer the open (if hidden) window answers
windows = [];
respond = async () => false;
expect(await push(nwc)).toEqual(['Spend request']);
});
+
+// An Android phone freezes an app behind another one, minutes in: its relay
+// sockets are dead and the worker's nudge sits unread until it is opened, so
+// a zap sent from another app spun until then (wake to payment: 15 s at the
+// median on Android, 2026-11-10). On a phone only a window on screen is
+// handed the request; behind another app, the worker answers (and nudges the
+// window anyway — the request's lock decides if it is still awake).
+test('an Android phone: the app behind another one does not swallow the request', async () => {
+ endpoint = 'https://fcm.googleapis.com/fcm/send/abc';
+ self.navigator = { userAgent: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/155.0.0.0 Mobile Safari/537.36' };
+ try {
+ let asked = 0, nudged = 0;
+ respond = async () => { asked++; return true; };
+ windows = [{ visibilityState: 'hidden', postMessage: () => { nudged++; } }];
+ expect(await push(nwc)).toEqual([]);
+ expect(asked).toBe(1);
+ expect(nudged).toBe(1);
+ // on screen, it answers itself with everything it knows
+ asked = 0; nudged = 0;
+ windows = [{ visibilityState: 'visible', postMessage: () => { nudged++; } }];
+ expect(await push(nwc)).toEqual([]);
+ expect(asked).toBe(0);
+ expect(nudged).toBe(1);
+ } finally { delete self.navigator; }
+});