import { test } from 'node:test'; import assert from 'node:assert/strict'; import { getKeyUploadWatch, isKeyUploadUrl, isOtkConflict, recordKeyUpload, resetKeyUploadWatch, STUCK_AFTER, subscribeKeyUploadWatch, WINDOW_MS, watchKeyUploads, } from './keyUploadWatch'; const CONFLICT = JSON.stringify({ errcode: 'M_UNKNOWN', error: 'One time key signed_curve25519:AAAAAAAAAA0 already exists. Old key: {...}; new key: {...}', }); test('[#201] recognises the upload endpoint and the conflict body', () => { assert.equal(isKeyUploadUrl('https://hs.example/_matrix/client/v3/keys/upload'), true); assert.equal(isKeyUploadUrl('https://hs.example/_matrix/client/v3/keys/upload/'), true); assert.equal(isKeyUploadUrl('https://hs.example/_matrix/client/v3/keys/query'), false); assert.equal(isKeyUploadUrl('https://hs.example/_matrix/client/v3/room_keys/keys'), false); assert.equal(isOtkConflict(400, CONFLICT), true); assert.equal(isOtkConflict(400, '{"errcode":"M_BAD_JSON"}'), false); assert.equal(isOtkConflict(500, CONFLICT), false); }); test('[#201] stuck only after repeated conflicts inside the window', () => { resetKeyUploadWatch(); const t0 = 1_000_000; for (let i = 0; i < STUCK_AFTER - 1; i += 1) recordKeyUpload(400, CONFLICT, t0 + i * 1000); assert.equal(getKeyUploadWatch().stuck, false, 'a couple of races is not divergence'); recordKeyUpload(400, CONFLICT, t0 + 5000); assert.equal(getKeyUploadWatch().stuck, true); assert.match(getKeyUploadWatch().lastError ?? '', /already exists/); }); test('[#201] old conflicts age out; other 400s are ignored', () => { resetKeyUploadWatch(); recordKeyUpload(400, CONFLICT, 0); recordKeyUpload(400, CONFLICT, 1000); recordKeyUpload(400, CONFLICT, WINDOW_MS + 60_000); assert.equal(getKeyUploadWatch().stuck, false); recordKeyUpload(400, '{"errcode":"M_BAD_JSON"}', WINDOW_MS + 61_000); assert.equal(getKeyUploadWatch().conflicts, 1); }); test('[#201] a successful upload clears it and notifies', () => { resetKeyUploadWatch(); let calls = 0; const off = subscribeKeyUploadWatch(() => { calls += 1; }); for (let i = 0; i < STUCK_AFTER; i += 1) recordKeyUpload(400, CONFLICT, i); assert.equal(getKeyUploadWatch().stuck, true); recordKeyUpload(200, ''); assert.equal(getKeyUploadWatch().stuck, false); assert.ok(calls >= 2); off(); }); test('[#201] the fetch wrapper observes without changing the response', async () => { resetKeyUploadWatch(); const fake: typeof fetch = async (input) => String(input).endsWith('/keys/upload') ? new Response(CONFLICT, { status: 400 }) : new Response('{}', { status: 200 }); const f = watchKeyUploads(fake); const responses = await Promise.all( Array.from({ length: STUCK_AFTER }, () => f('https://hs.example/_matrix/client/v3/keys/upload', { method: 'POST' }), ), ); const bodies = await Promise.all(responses.map((res) => res.text())); responses.forEach((res) => assert.equal(res.status, 400)); bodies.forEach((b) => assert.match(b, /already exists/, 'the SDK can still read the body')); await new Promise((r) => { setTimeout(r, 10); }); assert.equal(getKeyUploadWatch().stuck, true); await f('https://hs.example/_matrix/client/v3/sync'); assert.equal(getKeyUploadWatch().stuck, true, 'unrelated requests change nothing'); });