fix: prevent offline queue replay data loss

This commit is contained in:
2026-09-14 21:46:52 +03:00
parent b578c2d3ba
commit 4e94a77019
3 changed files with 88 additions and 7 deletions
+46
View File
@@ -43,3 +43,49 @@ test('replay removes acknowledged rows and retains the first failed row', async
clearOfflineMutations('replay@example.test');
});
test('concurrent reconnect signals send each queued mutation once', async () => {
clearOfflineMutations('concurrent@example.test');
enqueueTenderMutation('concurrent@example.test', { key: 'a', patch: { takipte: true }, snapshot: {}, version: 0 });
let sends = 0;
let release;
const gate = new Promise(resolve => { release = resolve; });
const send = async () => { sends += 1; await gate; return { version: 1 }; };
const first = replayOfflineMutations('concurrent@example.test', send);
const second = replayOfflineMutations('concurrent@example.test', send);
release();
const [a, b] = await Promise.all([first, second]);
assert.equal(sends, 1);
assert.deepEqual(a, b);
assert.equal(offlineMutationCount('concurrent@example.test'), 0);
});
test('a mutation added during replay is retained with the acknowledged server version', async () => {
clearOfflineMutations('during-replay@example.test');
enqueueTenderMutation('during-replay@example.test', { key: 'a', patch: { takipte: true }, snapshot: {}, version: 3 });
const first = await replayOfflineMutations('during-replay@example.test', async item => {
enqueueTenderMutation('during-replay@example.test', { key: 'a', patch: { kanbanStatus: 'teklif_hazirlaniyor' }, snapshot: {}, version: item.version });
return { version: 4 };
});
assert.equal(first.pending, 1);
const retained = listOfflineMutations('during-replay@example.test')[0];
assert.equal(retained.version, 4);
assert.deepEqual(retained.patch, { takipte: true, kanbanStatus: 'teklif_hazirlaniyor' });
const versions = [];
await replayOfflineMutations('during-replay@example.test', async item => { versions.push(item.version); return { version: 5 }; });
assert.deepEqual(versions, [4]);
assert.equal(offlineMutationCount('during-replay@example.test'), 0);
});
test('a full queue rejects a new tender instead of silently discarding an older change', () => {
clearOfflineMutations('full@example.test');
for (let index = 0; index < 100; index += 1) {
enqueueTenderMutation('full@example.test', { key: String(index), patch: { takipte: true }, snapshot: {}, version: 0 });
}
assert.throws(
() => enqueueTenderMutation('full@example.test', { key: 'overflow', patch: { takipte: true }, snapshot: {}, version: 0 }),
/kuyruğu dolu/,
);
assert.equal(offlineMutationCount('full@example.test'), 100);
assert.equal(listOfflineMutations('full@example.test')[0].key, '0');
clearOfflineMutations('full@example.test');
});