92 lines
4.9 KiB
JavaScript
92 lines
4.9 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
clearOfflineMutations,
|
|
enqueueTenderMutation,
|
|
listOfflineMutations,
|
|
offlineMutationCount,
|
|
replayOfflineMutations,
|
|
} from '../src/services/offlineMutationQueue.js';
|
|
|
|
const storage = new Map();
|
|
globalThis.localStorage = {
|
|
getItem: key => storage.has(key) ? storage.get(key) : null,
|
|
setItem: (key, value) => storage.set(key, String(value)),
|
|
removeItem: key => storage.delete(key),
|
|
};
|
|
|
|
test('offline tender mutations are account scoped and merged by tender', async () => {
|
|
clearOfflineMutations('qa@example.test');
|
|
enqueueTenderMutation('qa@example.test', { key: '2026/1', patch: { takipte: true }, snapshot: { ikn: '2026/1' }, version: 2 });
|
|
enqueueTenderMutation('qa@example.test', { key: '2026/1', patch: { kanbanStatus: 'teklif_hazirlaniyor' }, snapshot: { ikn: '2026/1' }, version: 2 });
|
|
enqueueTenderMutation('other@example.test', { key: '2026/1', patch: { takipte: true }, snapshot: { ikn: '2026/1' }, version: 0 });
|
|
assert.equal(offlineMutationCount('qa@example.test'), 1);
|
|
assert.deepEqual(listOfflineMutations('qa@example.test')[0].patch, { takipte: true, kanbanStatus: 'teklif_hazirlaniyor' });
|
|
assert.equal(offlineMutationCount('other@example.test'), 1);
|
|
clearOfflineMutations('qa@example.test'); clearOfflineMutations('other@example.test');
|
|
});
|
|
|
|
test('replay removes acknowledged rows and retains the first failed row', async () => {
|
|
clearOfflineMutations('replay@example.test');
|
|
enqueueTenderMutation('replay@example.test', { key: 'a', patch: { takipte: true }, snapshot: {}, version: 0 });
|
|
enqueueTenderMutation('replay@example.test', { key: 'b', patch: { takipte: true }, snapshot: {}, version: 0 });
|
|
const sent = [];
|
|
const first = await replayOfflineMutations('replay@example.test', async item => { sent.push(item.key); });
|
|
assert.deepEqual(sent, ['a', 'b']);
|
|
assert.equal(first.pending, 0);
|
|
enqueueTenderMutation('replay@example.test', { key: 'c', patch: { takipte: true }, snapshot: {}, version: 0 });
|
|
enqueueTenderMutation('replay@example.test', { key: 'd', patch: { takipte: true }, snapshot: {}, version: 0 });
|
|
const second = await replayOfflineMutations('replay@example.test', async item => { if (item.key === 'c') throw new Error('network'); });
|
|
assert.equal(second.sent, 0);
|
|
assert.equal(listOfflineMutations('replay@example.test')[0].key, 'c');
|
|
assert.equal(listOfflineMutations('replay@example.test')[0].attempts, 1);
|
|
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');
|
|
});
|