Skip to content

Commit cbfb683

Browse files
cleanup test code
1 parent 07231cb commit cbfb683

File tree

6 files changed

+55
-145
lines changed

6 files changed

+55
-145
lines changed

packages/web/src/db/adapters/AsyncDatabaseConnection.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export type ProxiedQueryResult = Omit<QueryResult, 'rows'> & {
1717
*/
1818
export type OnTableChangeCallback = (event: BatchedUpdateNotification) => void;
1919

20+
/**
21+
* Thrown when an underlying database connection is closed.
22+
* This is particularly relevant when worker connections are marked as closed while
23+
* operations are still in progress.
24+
*/
2025
export class ConnectionClosedError extends Error {
2126
constructor(message: string) {
2227
super(message);

packages/web/src/db/adapters/LockedAsyncDatabaseAdapter.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ export class LockedAsyncDatabaseAdapter
5757
private _config: ResolvedWebSQLOpenOptions | null = null;
5858
protected pendingAbortControllers: Set<AbortController>;
5959
protected requiresHolds: boolean | null;
60-
protected requiresReOpen: boolean;
6160
protected databaseOpenPromise: Promise<void> | null = null;
6261

6362
closing: boolean;
@@ -71,7 +70,6 @@ export class LockedAsyncDatabaseAdapter
7170
this.closed = false;
7271
this.closing = false;
7372
this.requiresHolds = null;
74-
this.requiresReOpen = false;
7573
// Set the name if provided. We can query for the name if not available yet
7674
this.debugMode = options.debugMode ?? false;
7775
if (this.debugMode) {

packages/web/src/worker/sync/MockSyncServiceWorker.ts

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ export class MockSyncService {
1717
private nextId = 0;
1818
private automaticResponse: AutomaticResponseConfig | null = null;
1919

20+
/**
21+
* A Static instance of the mock sync service.
22+
* This can be used directly for non-worker environments.
23+
* A proxy is required for worker environments.
24+
*/
25+
static readonly GLOBAL_INSTANCE = new MockSyncService();
26+
2027
/**
2128
* Register a new pending request (called by WebRemote when a sync stream is requested).
2229
* Returns a promise that resolves when a client creates a response for this request.
@@ -253,43 +260,19 @@ export class MockSyncService {
253260
}
254261
}
255262

256-
/**
257-
* Global mock service instance (only available in shared worker context)
258-
*/
259-
let globalMockService: MockSyncService | null = null;
260-
261-
/**
262-
* Get or create the global mock service instance
263-
*/
264-
export function getMockSyncService(): MockSyncService | null {
265-
// Only available in shared worker context
266-
if (typeof SharedWorkerGlobalScope === 'undefined') {
267-
return null;
268-
}
269-
270-
if (!globalMockService) {
271-
globalMockService = new MockSyncService();
272-
}
273-
274-
return globalMockService;
275-
}
276-
277263
/**
278264
* Set up message handler for the mock service on a MessagePort
279265
*/
280266
export function setupMockServiceMessageHandler(port: MessagePort) {
281-
const service = getMockSyncService();
282-
if (!service) {
283-
return;
284-
}
285-
286267
port.addEventListener('message', (event: MessageEvent<MockSyncServiceMessage>) => {
287268
const message = event.data;
288269

289270
if (!message || typeof message !== 'object' || !('type' in message)) {
290271
return;
291272
}
292273

274+
const service = MockSyncService.GLOBAL_INSTANCE;
275+
293276
try {
294277
switch (message.type) {
295278
case 'getPendingRequests': {

packages/web/tests/mocks/MockWebRemote.ts

Lines changed: 40 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,7 @@ import {
1111
SocketSyncStreamOptions
1212
} from '@powersync/common';
1313
import { serialize, type BSON } from 'bson';
14-
import { getMockSyncService, setupMockServiceMessageHandler } from '../utils/MockSyncService';
15-
16-
/**
17-
* Check if we're running in a shared worker context
18-
*/
19-
function isSharedWorkerContext(): boolean {
20-
const isSharedWorker =
21-
typeof SharedWorkerGlobalScope !== 'undefined' &&
22-
typeof self !== 'undefined' &&
23-
(self as any).constructor?.name === 'SharedWorkerGlobalScope';
24-
return isSharedWorker;
25-
}
26-
27-
/**
28-
* Mock fetch provider that returns 401 for non-stream requests
29-
*/
30-
class MockFetchProvider extends FetchImplementationProvider {
31-
getFetch(): FetchImplementation {
32-
// Return a mock fetch that always returns 401
33-
return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
34-
const response = new Response(null, {
35-
status: 401,
36-
statusText: 'Unauthorized'
37-
});
38-
return response;
39-
};
40-
}
41-
}
14+
import { MockSyncService, setupMockServiceMessageHandler } from '../../src/worker/sync/MockSyncServiceWorker';
4215

4316
/**
4417
* Mock fetch provider that intercepts all requests and routes them to the mock sync service.
@@ -53,96 +26,61 @@ class MockSyncServiceFetchProvider extends FetchImplementationProvider {
5326
getFetch(): FetchImplementation {
5427
return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
5528
const request = new Request(input, init);
56-
const mockService = getMockSyncService();
5729

58-
if (mockService) {
59-
// Read the request body (if any)
60-
let body: any = null;
61-
try {
62-
if (request.body) {
63-
const clonedRequest = request.clone();
64-
body = await clonedRequest.json().catch(() => {
65-
// If JSON parsing fails, try text
66-
return clonedRequest.text().catch(() => null);
67-
});
68-
}
69-
} catch (e) {
70-
// Body might not be readable, that's okay
30+
const mockService = MockSyncService.GLOBAL_INSTANCE;
31+
32+
// Read the request body (if any)
33+
let body: any = null;
34+
try {
35+
if (request.body) {
36+
const clonedRequest = request.clone();
37+
body = await clonedRequest.json().catch(() => {
38+
// If JSON parsing fails, try text
39+
return clonedRequest.text().catch(() => null);
40+
});
7141
}
72-
73-
// Extract headers from the request
74-
const headers: Record<string, string> = {};
75-
request.headers.forEach((value, key) => {
76-
headers[key] = value;
77-
});
78-
79-
// Register as a pending request and wait for client to create response
80-
return await mockService.registerPendingRequest(request.url, request.method, headers, body, request.signal);
42+
} catch (e) {
43+
// Body might not be readable, that's okay
8144
}
8245

83-
// Fallback if mock service is not available
84-
return new Response(null, {
85-
status: 401,
86-
statusText: 'Unauthorized'
46+
// Extract headers from the request
47+
const headers: Record<string, string> = {};
48+
request.headers.forEach((value, key) => {
49+
headers[key] = value;
8750
});
51+
52+
// Register as a pending request and wait for client to create response
53+
return await mockService.registerPendingRequest(request.url, request.method, headers, body, request.signal);
8854
};
8955
}
9056
}
9157

92-
// Track if we've already set up the onconnect handler
93-
let onconnectHandlerSetup = false;
94-
9558
/**
96-
* Create the mock sync service globally upfront when in shared worker context.
97-
* This ensures the service is available before any requests come in.
59+
* Check if we're running in a shared worker context
9860
*/
99-
function initializeMockSyncService() {
100-
if (!isSharedWorkerContext()) {
101-
return;
102-
}
103-
104-
// Create the mock service upfront (getMockSyncService creates it if it doesn't exist)
105-
getMockSyncService();
61+
function isSharedWorkerContext(): boolean {
62+
const isSharedWorker =
63+
typeof SharedWorkerGlobalScope !== 'undefined' &&
64+
typeof self !== 'undefined' &&
65+
(self as any).constructor?.name === 'SharedWorkerGlobalScope';
66+
return isSharedWorker;
10667
}
10768

108-
/**
109-
* Set up the onconnect listener lazily when the first WebRemote is created.
110-
* This ensures deterministic initialization order regardless of import order.
111-
* The mock service is created upfront, but message handlers are only set up on connect.
112-
*/
113-
function setupOnconnectHandlerIfNeeded() {
114-
if (onconnectHandlerSetup || !isSharedWorkerContext()) {
115-
return;
116-
}
117-
118-
// Create the mock sync service globally upfront
119-
initializeMockSyncService();
120-
69+
if (isSharedWorkerContext()) {
12170
const _self: SharedWorkerGlobalScope = self as any;
71+
console.log('MockWebRemote: setting up connect listener');
12272

123-
// Store the original onconnect if it exists (may be set by SharedSyncImplementation.worker.ts)
124-
const originalOnConnect = _self.onconnect;
125-
126-
_self.onconnect = async function (event: MessageEvent) {
73+
/**
74+
* This listener should be called in tandem with the shared sync worker's listener.
75+
*/
76+
_self.addEventListener('connect', async function (event: MessageEvent) {
77+
console.log('MockWebRemote: connect listener called');
12778
const port = event.ports[0];
12879

129-
// Get the mock service (already created upfront) and set up message handler lazily
130-
const mockService = getMockSyncService();
131-
if (mockService) {
132-
// Set up message handler for the mock service on this port
133-
// Tests can create a separate SharedWorker connection to access this
134-
setupMockServiceMessageHandler(port);
135-
}
136-
137-
// Call the original handler if it exists (this will set up WorkerClient)
138-
// Note: The mock service message handler and WorkerClient can coexist
139-
// since they use different message types
140-
if (originalOnConnect) {
141-
await originalOnConnect.call(this, event);
142-
}
143-
};
144-
145-
onconnectHandlerSetup = true;
80+
// Set up message handler for the mock service on this port
81+
// Tests can create a separate SharedWorker connection to access this
82+
setupMockServiceMessageHandler(port);
83+
});
14684
}
14785

14886
export class WebRemote extends AbstractRemote {
@@ -153,12 +91,8 @@ export class WebRemote extends AbstractRemote {
15391
protected logger: ILogger = DEFAULT_REMOTE_LOGGER,
15492
options?: Partial<AbstractRemoteOptions>
15593
) {
156-
// Lazy initialize the onconnect handler when the first WebRemote is created
157-
// This ensures deterministic initialization order regardless of import order
158-
setupOnconnectHandlerIfNeeded();
159-
16094
// Use mock service fetch provider if we're in a shared worker context
161-
const fetchProvider = isSharedWorkerContext() ? new MockSyncServiceFetchProvider() : new MockFetchProvider();
95+
const fetchProvider = new MockSyncServiceFetchProvider();
16296

16397
super(connector, logger, {
16498
...(options ?? {}),

packages/web/tests/multiple_instances.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { beforeAll, describe, expect, it, onTestFinished, vi } from 'vitest';
55
import { LockedAsyncDatabaseAdapter } from '../src/db/adapters/LockedAsyncDatabaseAdapter';
66
import { WebDBAdapter } from '../src/db/adapters/WebDBAdapter';
77
import { WorkerWrappedAsyncDatabaseConnection } from '../src/db/adapters/WorkerWrappedAsyncDatabaseConnection';
8-
import { getMockSyncServiceFromWorker } from './utils/MockSyncService';
8+
import { getMockSyncServiceFromWorker } from './utils/MockSyncServiceClient';
99
import { createTestConnector, sharedMockSyncServiceTest } from './utils/mockSyncServiceTest';
1010
import { generateTestDb, testSchema } from './utils/testDb';
1111

packages/web/tests/utils/MockSyncService.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)