mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
120 lines
4.1 KiB
JavaScript
120 lines
4.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import {
|
|
enqueueSerializedWrite,
|
|
optimisticAttempt,
|
|
rollbackOptimistic,
|
|
} from '../test-build/serialized-write-queue.js';
|
|
|
|
function deferred() {
|
|
let resolve;
|
|
const promise = new Promise((done) => { resolve = done; });
|
|
return { promise, resolve };
|
|
}
|
|
|
|
test('a queued config write reads edits made while the previous write is in flight (#224)', async () => {
|
|
const firstStarted = deferred();
|
|
const releaseFirst = deferred();
|
|
let localConfig = 'first';
|
|
const observed = [];
|
|
let chain = Promise.resolve();
|
|
|
|
const enqueue = () => {
|
|
chain = enqueueSerializedWrite(chain, async () => {
|
|
observed.push(localConfig);
|
|
if (observed.length === 1) {
|
|
firstStarted.resolve();
|
|
await releaseFirst.promise;
|
|
}
|
|
});
|
|
return chain;
|
|
};
|
|
|
|
const first = enqueue();
|
|
await firstStarted.promise;
|
|
localConfig = 'second';
|
|
const second = enqueue();
|
|
localConfig = 'latest edit during await';
|
|
|
|
assert.deepEqual(observed, ['first'], 'the second write waits its turn');
|
|
releaseFirst.resolve();
|
|
await Promise.all([first, second]);
|
|
assert.deepEqual(observed, ['first', 'latest edit during await']);
|
|
});
|
|
|
|
test('a failed write does not poison the next queued edit (#224)', async () => {
|
|
const seen = [];
|
|
const failed = enqueueSerializedWrite(Promise.resolve(), async () => {
|
|
seen.push('failed');
|
|
throw new Error('offline');
|
|
});
|
|
const recovered = enqueueSerializedWrite(failed, async () => { seen.push('recovered'); });
|
|
|
|
await assert.rejects(failed, /offline/);
|
|
await recovered;
|
|
assert.deepEqual(seen, ['failed', 'recovered']);
|
|
});
|
|
|
|
test('a rejected optimistic write restores only its own candidate (#439)', () => {
|
|
const fingerprint = (value) => JSON.stringify(value);
|
|
const previous = { settings: { enabled: true } };
|
|
const attempted = { settings: { enabled: false } };
|
|
const attempt = optimisticAttempt(previous, attempted, 'server-fingerprint', 7, fingerprint);
|
|
let updates = 0;
|
|
const host = {
|
|
_serverCfg: attempted,
|
|
_cfgRev: 7,
|
|
_cfgContentFingerprint: fingerprint(attempted),
|
|
requestUpdate: () => { updates += 1; },
|
|
};
|
|
|
|
assert.equal(rollbackOptimistic(host, attempt, fingerprint), true);
|
|
assert.deepEqual(host._serverCfg, previous);
|
|
assert.notEqual(host._serverCfg, previous, 'the rollback snapshot is isolated from later mutations');
|
|
assert.equal(host._cfgContentFingerprint, 'server-fingerprint');
|
|
assert.equal(updates, 1);
|
|
});
|
|
|
|
test('a conflict reload or newer mutation wins over a rejected candidate (#439)', () => {
|
|
const fingerprint = (value) => JSON.stringify(value);
|
|
const previous = { settings: { value: 'server-before' } };
|
|
const attempted = { settings: { value: 'draft' } };
|
|
const attempt = optimisticAttempt(previous, attempted, 'before', 3, fingerprint);
|
|
const authoritative = { settings: { value: 'server-after' } };
|
|
let updates = 0;
|
|
const host = {
|
|
_serverCfg: authoritative,
|
|
_cfgRev: 4,
|
|
_cfgContentFingerprint: fingerprint(authoritative),
|
|
requestUpdate: () => { updates += 1; },
|
|
};
|
|
|
|
assert.equal(rollbackOptimistic(host, attempt, fingerprint), false);
|
|
assert.equal(host._serverCfg, authoritative);
|
|
assert.equal(updates, 0);
|
|
|
|
host._cfgRev = 3;
|
|
host._serverCfg = { settings: { value: 'newer-local-edit' } };
|
|
assert.equal(rollbackOptimistic(host, attempt, fingerprint), false);
|
|
assert.equal(host._serverCfg.settings.value, 'newer-local-edit');
|
|
assert.equal(updates, 0);
|
|
});
|
|
|
|
test('newer in-place content on the attempted root also wins (#442)', () => {
|
|
const fingerprint = (value) => JSON.stringify(value);
|
|
const previous = { settings: { value: 'server-before' } };
|
|
const attempted = { settings: { value: 'draft' } };
|
|
const attempt = optimisticAttempt(previous, attempted, 'before', 3, fingerprint);
|
|
const host = {
|
|
_serverCfg: attempted,
|
|
_cfgRev: 3,
|
|
_cfgContentFingerprint: fingerprint(attempted),
|
|
requestUpdate: () => assert.fail('newer content must not be rolled back'),
|
|
};
|
|
|
|
attempted.settings.newer = true;
|
|
assert.equal(rollbackOptimistic(host, attempt, fingerprint), false);
|
|
assert.equal(host._serverCfg, attempted);
|
|
});
|