Skip to content

Commit 354c87d

Browse files
committed
Stop syncing more reliably in tests
1 parent b1d0603 commit 354c87d

1 file changed

Lines changed: 88 additions & 95 deletions

File tree

packages/node/tests/attachments.test.ts

Lines changed: 88 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
1+
import { describe, expect, it, vi, beforeEach, afterEach, onTestFinished } from 'vitest';
22
import {
33
PowerSyncDatabase,
44
Schema,
@@ -261,6 +261,7 @@ describe('attachment queue', () => {
261261
syncIntervalMs: INTERVAL_MILLISECONDS,
262262
archivedCacheLimit: 0
263263
});
264+
onTestFinished(() => queue.stopSync());
264265

265266
await queue.startSync();
266267

@@ -280,8 +281,6 @@ describe('attachment queue', () => {
280281
const attachmentRecord = attachments.find((r) => r.id === id);
281282
expect(attachmentRecord?.mediaType).toBe('image/jpeg');
282283
expect(mockDownloadFile).toHaveBeenCalledWith(expect.objectContaining({ mediaType: 'image/jpeg' }));
283-
284-
await queue.stopSync();
285284
}
286285
);
287286

@@ -538,6 +537,7 @@ describe('attachment queue', () => {
538537
syncIntervalMs: INTERVAL_MILLISECONDS,
539538
errorHandler
540539
});
540+
onTestFinished(() => localeQueue.stopSync());
541541

542542
const id = await localeQueue.generateAttachmentId();
543543

@@ -578,6 +578,7 @@ describe('attachment queue', () => {
578578
syncIntervalMs: INTERVAL_MILLISECONDS,
579579
archivedCacheLimit: 0
580580
});
581+
onTestFinished(() => slowQueue.stopSync());
581582

582583
await slowQueue.startSync();
583584

@@ -606,112 +607,103 @@ describe('attachment queue', () => {
606607
}
607608
);
608609

609-
it(
610-
'attachments queue should commit per-attachment, not at end of batch',
611-
{ timeout: 10000 },
612-
async () => {
613-
const gates: Array<ReturnType<typeof deferred<ArrayBuffer>>> = [deferred<ArrayBuffer>(), deferred<ArrayBuffer>()];
614-
const downloadIds: string[] = [];
615-
const slowDownload = vi.fn().mockImplementation((attachment: AttachmentRecord) => {
616-
const idx = downloadIds.length;
617-
downloadIds.push(attachment.id);
618-
return gates[idx]?.promise ?? Promise.resolve(createMockJpegBuffer());
619-
});
610+
it('attachments queue should commit per-attachment, not at end of batch', { timeout: 10000 }, async () => {
611+
const gates: Array<ReturnType<typeof deferred<ArrayBuffer>>> = [deferred<ArrayBuffer>(), deferred<ArrayBuffer>()];
612+
const downloadIds: string[] = [];
613+
const slowDownload = vi.fn().mockImplementation((attachment: AttachmentRecord) => {
614+
const idx = downloadIds.length;
615+
downloadIds.push(attachment.id);
616+
return gates[idx]?.promise ?? Promise.resolve(createMockJpegBuffer());
617+
});
620618

621-
const slowQueue = new AttachmentQueue({
622-
db,
623-
watchAttachments,
624-
remoteStorage: { downloadFile: slowDownload, uploadFile: mockUploadFile, deleteFile: mockDeleteFile },
625-
localStorage: mockLocalStorage,
626-
syncIntervalMs: INTERVAL_MILLISECONDS,
627-
archivedCacheLimit: 0
628-
});
619+
const slowQueue = new AttachmentQueue({
620+
db,
621+
watchAttachments,
622+
remoteStorage: { downloadFile: slowDownload, uploadFile: mockUploadFile, deleteFile: mockDeleteFile },
623+
localStorage: mockLocalStorage,
624+
syncIntervalMs: INTERVAL_MILLISECONDS,
625+
archivedCacheLimit: 0
626+
});
627+
onTestFinished(() => slowQueue.stopSync());
629628

630-
await slowQueue.startSync();
629+
await slowQueue.startSync();
631630

632-
// Queue two downloads.
633-
for (let i = 0; i < 2; i++) {
634-
await db.execute('INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())', [
635-
`user${i}`,
636-
`user${i}@example.com`
637-
]);
638-
}
631+
// Queue two downloads.
632+
for (let i = 0; i < 2; i++) {
633+
await db.execute('INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())', [
634+
`user${i}`,
635+
`user${i}@example.com`
636+
]);
637+
}
639638

640-
// Wait until the first download is in flight.
641-
await waitForMatchCondition(
642-
() => watchAttachmentsTable(),
643-
() => slowDownload.mock.calls.length >= 1,
644-
5
645-
);
639+
// Wait until the first download is in flight.
640+
await waitForMatchCondition(
641+
() => watchAttachmentsTable(),
642+
() => slowDownload.mock.calls.length >= 1,
643+
5
644+
);
646645

647-
// Resolve only the first download. The second is still pending.
648-
gates[0].resolve(createMockJpegBuffer());
646+
// Resolve only the first download. The second is still pending.
647+
gates[0].resolve(createMockJpegBuffer());
649648

650-
// The first attachment must transition to SYNCED while the second is still
651-
// QUEUED_DOWNLOAD — i.e. the commit happened mid-batch, not at the end.
652-
await waitForMatchCondition(
653-
() => watchAttachmentsTable(),
654-
(rows) => {
655-
const first = rows.find((r) => r.id === downloadIds[0]);
656-
const others = rows.filter((r) => r.id !== downloadIds[0]);
657-
return (
658-
first?.state === AttachmentState.SYNCED &&
659-
others.some((r) => r.state === AttachmentState.QUEUED_DOWNLOAD)
660-
);
661-
},
662-
5
663-
);
649+
// The first attachment must transition to SYNCED while the second is still
650+
// QUEUED_DOWNLOAD — i.e. the commit happened mid-batch, not at the end.
651+
await waitForMatchCondition(
652+
() => watchAttachmentsTable(),
653+
(rows) => {
654+
const first = rows.find((r) => r.id === downloadIds[0]);
655+
const others = rows.filter((r) => r.id !== downloadIds[0]);
656+
return (
657+
first?.state === AttachmentState.SYNCED && others.some((r) => r.state === AttachmentState.QUEUED_DOWNLOAD)
658+
);
659+
},
660+
5
661+
);
664662

665-
// Clean up: release the second so the worker drains and stopSync finishes.
666-
gates[1].resolve(createMockJpegBuffer());
667-
await slowQueue.stopSync();
668-
}
669-
);
663+
// Clean up: release the second so the worker drains and stopSync finishes.
664+
gates[1].resolve(createMockJpegBuffer());
665+
});
670666

671-
it(
672-
'saveFile should not be blocked by an in-flight sync batch',
673-
{ timeout: 10000 },
674-
async () => {
675-
const downloadStarted = deferred();
676-
const downloadGate = deferred<ArrayBuffer>();
677-
const slowDownload = vi.fn().mockImplementation(() => {
678-
downloadStarted.resolve();
679-
return downloadGate.promise;
680-
});
667+
it('saveFile should not be blocked by an in-flight sync batch', { timeout: 10000 }, async () => {
668+
const downloadStarted = deferred();
669+
const downloadGate = deferred<ArrayBuffer>();
670+
const slowDownload = vi.fn().mockImplementation(() => {
671+
downloadStarted.resolve();
672+
return downloadGate.promise;
673+
});
681674

682-
const slowQueue = new AttachmentQueue({
683-
db,
684-
watchAttachments,
685-
remoteStorage: { downloadFile: slowDownload, uploadFile: mockUploadFile, deleteFile: mockDeleteFile },
686-
localStorage: mockLocalStorage,
687-
syncIntervalMs: INTERVAL_MILLISECONDS,
688-
archivedCacheLimit: 0
689-
});
675+
const slowQueue = new AttachmentQueue({
676+
db,
677+
watchAttachments,
678+
remoteStorage: { downloadFile: slowDownload, uploadFile: mockUploadFile, deleteFile: mockDeleteFile },
679+
localStorage: mockLocalStorage,
680+
syncIntervalMs: INTERVAL_MILLISECONDS,
681+
archivedCacheLimit: 0
682+
});
683+
onTestFinished(() => slowQueue.stopSync());
690684

691-
await slowQueue.startSync();
685+
await slowQueue.startSync();
692686

693-
// Queue a download that will hang.
694-
await db.execute('INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())', [
695-
'downloader',
696-
'downloader@example.com'
697-
]);
687+
// Queue a download that will hang.
688+
await db.execute('INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())', [
689+
'downloader',
690+
'downloader@example.com'
691+
]);
698692

699-
await downloadStarted.promise;
693+
await downloadStarted.promise;
700694

701-
// saveFile must complete promptly even while a download is in flight.
702-
const saveStart = Date.now();
703-
const saved = await slowQueue.saveFile({
704-
data: new Uint8Array(10).fill(7).buffer,
705-
fileExtension: 'jpg'
706-
});
707-
expect(Date.now() - saveStart).toBeLessThan(500);
708-
expect(saved.state).toBe(AttachmentState.QUEUED_UPLOAD);
695+
// saveFile must complete promptly even while a download is in flight.
696+
const saveStart = Date.now();
697+
const saved = await slowQueue.saveFile({
698+
data: new Uint8Array(10).fill(7).buffer,
699+
fileExtension: 'jpg'
700+
});
701+
expect(Date.now() - saveStart).toBeLessThan(500);
702+
expect(saved.state).toBe(AttachmentState.QUEUED_UPLOAD);
709703

710-
// Clean up.
711-
downloadGate.resolve(createMockJpegBuffer());
712-
await slowQueue.stopSync();
713-
}
714-
);
704+
// Clean up.
705+
downloadGate.resolve(createMockJpegBuffer());
706+
});
715707

716708
it(
717709
'stopSync should release a sync call queued behind a stalled batch instead of leaving it hanging',
@@ -733,6 +725,7 @@ describe('attachment queue', () => {
733725
syncIntervalMs: INTERVAL_MILLISECONDS,
734726
archivedCacheLimit: 0
735727
});
728+
onTestFinished(() => slowQueue.stopSync());
736729

737730
await slowQueue.startSync();
738731

0 commit comments

Comments
 (0)