Skip to content

Commit efb8121

Browse files
committed
Fix tests failing on v2 branch (#1026)
1 parent f7f0fc1 commit efb8121

7 files changed

Lines changed: 115 additions & 112 deletions

File tree

packages/common/src/attachments/AttachmentQueue.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,8 @@ export class AttachmentQueue {
348348
*/
349349
async syncStorage(): Promise<void> {
350350
const signal = this.syncAbortController?.signal;
351-
if (signal?.aborted) return;
351+
// We have a signal from startSync() to stopSync(), so treat the absence of one like an aborted sync.
352+
if (signal == null || signal?.aborted) return;
352353

353354
try {
354355
await this.syncLoopMutex.runExclusive(async () => {
@@ -357,13 +358,13 @@ export class AttachmentQueue {
357358

358359
await this.syncingService.processAttachments(activeAttachments, { signal });
359360

360-
if (signal?.aborted) return;
361+
if (signal.aborted) return;
361362

362363
await this.attachmentService.withContext((ctx) => this.syncingService.deleteArchivedAttachments(ctx));
363364
}, signal);
364365
} catch (error) {
365366
// A queued batch's acquire rejects when `stopSync` aborts — expected, not an error.
366-
if (signal?.aborted) return;
367+
if (signal.aborted) return;
367368
throw error;
368369
}
369370
}

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

packages/node/tests/setup.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// In database-related test failures, worker communication takes up almost all
2+
// of the default stack trace (length 10). Increase this to aid in debugging failed tests.
3+
Error.stackTraceLimit = 50;

packages/node/vitest.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export default defineConfig({
55
test: {
66
silent: false,
77
// This doesn't make the tests considerably slower. It may improve reliability for GH actions.
8-
fileParallelism: false
8+
fileParallelism: false,
9+
setupFiles: ['./tests/setup.ts']
910
}
1011
});

packages/tanstack-react-query/tests/usePowerSyncQueries.test.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { cleanup, renderHook, waitFor } from '@testing-library/react';
22
import { beforeEach, describe, expect, it, vi } from 'vitest';
33
import React from 'react';
4-
import { AbstractPowerSyncDatabase, SyncStatus } from '@powersync/common';
54
import { PowerSyncContext } from '@powersync/react';
65
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
76
import { openPowerSync } from './utils';
87
import * as Tanstack from '@tanstack/react-query';
98
import { usePowerSyncQueries } from '../src/hooks/usePowerSyncQueries';
9+
import { BasePowerSyncDatabase, SyncStatusSnapshot } from '@powersync/shared-internals';
1010

1111
describe('usePowerSyncQueries bug fixes', () => {
12-
let db: AbstractPowerSyncDatabase;
12+
let db: BasePowerSyncDatabase;
1313
let queryClient = new QueryClient({
1414
defaultOptions: {
1515
queries: {
@@ -19,7 +19,7 @@ describe('usePowerSyncQueries bug fixes', () => {
1919
});
2020

2121
beforeEach(() => {
22-
db = openPowerSync();
22+
db = openPowerSync() as BasePowerSyncDatabase;
2323
queryClient.clear();
2424
vi.clearAllMocks();
2525
cleanup();
@@ -32,9 +32,13 @@ describe('usePowerSyncQueries bug fixes', () => {
3232
);
3333

3434
const syncedStatus = () =>
35-
new SyncStatus({
36-
dataFlow: {
37-
internalStreamSubscriptions: [
35+
new SyncStatusSnapshot(
36+
{
37+
connecting: false,
38+
connected: true,
39+
downloading: null,
40+
priority_status: [],
41+
streams: [
3842
{
3943
name: 'a',
4044
parameters: null,
@@ -47,8 +51,9 @@ describe('usePowerSyncQueries bug fixes', () => {
4751
priority: 1
4852
}
4953
]
50-
}
51-
});
54+
},
55+
{}
56+
);
5257

5358
describe('usePowerSyncQueries ', () => {
5459
it('updated returned streamsHaveSynced once a waitForStream stream syncs', async () => {

packages/tanstack-react-query/vitest.config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ const config: ViteUserConfig = {
1919
shuffle: false, // Disable shuffling of test files
2020
concurrent: false // Run test files sequentially
2121
},
22+
/**
23+
* Starts each test in a new iFrame
24+
*/
25+
isolate: true,
2226
browser: {
2327
enabled: true,
24-
/**
25-
* Starts each test in a new iFrame
26-
*/
27-
isolate: true,
2828
provider: playwright(),
2929
headless: true,
3030
instances: [

packages/web/tests/utils/mockSyncServiceTest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export const sharedMockSyncServiceTest = test.extend<{
122122
() => {
123123
expect(database.connecting).toBe(true);
124124
},
125-
{ timeout: 1000 }
125+
{ timeout: 1500 }
126126
);
127127

128128
let _syncRequestId: string;

0 commit comments

Comments
 (0)