fix(data): 串行化 AppData 同 key 保存
This commit is contained in:
@@ -142,6 +142,87 @@ test('server data shares an in-flight load for the same AppData key', async () =
|
||||
}
|
||||
});
|
||||
|
||||
test('server data serializes same-key saves so later writes use the latest AppData version', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const saveCalls: Array<{ value: unknown; version?: string | null }> = [];
|
||||
let releaseFirstSave!: () => void;
|
||||
let markFirstSaveStarted!: () => void;
|
||||
const firstSaveStarted = new Promise<void>((resolve) => {
|
||||
markFirstSaveStarted = resolve;
|
||||
});
|
||||
const firstSaveCanFinish = new Promise<void>((resolve) => {
|
||||
releaseFirstSave = resolve;
|
||||
});
|
||||
__resetApiAvailabilityForTests();
|
||||
|
||||
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = String(input);
|
||||
if (url.endsWith('/config/ai')) {
|
||||
return new Response(JSON.stringify({}), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
if (url.endsWith('/data/requirements') && (!init?.method || init.method === 'GET')) {
|
||||
return new Response(JSON.stringify({
|
||||
key: 'requirements',
|
||||
value: { requirements: [] },
|
||||
version: 'requirements-version-1',
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
if (url.endsWith('/data/requirements') && init?.method === 'PUT') {
|
||||
const body = JSON.parse(String(init.body));
|
||||
saveCalls.push(body);
|
||||
if (body.value.marker === 'first') {
|
||||
markFirstSaveStarted();
|
||||
await firstSaveCanFinish;
|
||||
return new Response(JSON.stringify({
|
||||
key: 'requirements',
|
||||
value: body.value,
|
||||
version: 'requirements-version-2',
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
return new Response(JSON.stringify({
|
||||
key: 'requirements',
|
||||
value: body.value,
|
||||
version: 'requirements-version-3',
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
throw new Error(`Unexpected fetch ${init?.method ?? 'GET'} ${url}`);
|
||||
}) as typeof fetch;
|
||||
|
||||
try {
|
||||
await loadServerData('requirements', { force: true });
|
||||
|
||||
const firstSave = saveServerData('requirements', { marker: 'first' });
|
||||
await firstSaveStarted;
|
||||
const secondSave = saveServerData('requirements', { marker: 'second' });
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.equal(saveCalls.length, 1);
|
||||
|
||||
releaseFirstSave();
|
||||
await Promise.all([firstSave, secondSave]);
|
||||
|
||||
assert.deepEqual(saveCalls, [
|
||||
{ value: { marker: 'first' }, version: 'requirements-version-1' },
|
||||
{ value: { marker: 'second' }, version: 'requirements-version-2' },
|
||||
]);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
__resetApiAvailabilityForTests();
|
||||
}
|
||||
});
|
||||
|
||||
test('server data reuses a fresh cached load for repeated AppData reads', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let dataRequests = 0;
|
||||
|
||||
Reference in New Issue
Block a user