import { describe, expect, it, vi } from "@suaveplan/testing/runner"; import { SqliteError } from "../errors/index.js"; import { createWasmDriver } from "./wasm-driver.js"; // Injectable worker factory avoids spawning real Workers in Node/Vitest environment function makeWorkerFactory( handler: ( msg: unknown, post: (data: unknown) => void ) => void | Promise ): () => Worker { return () => { let onmessage: ((ev: MessageEvent) => void) | null = null; let onerror: ((ev: ErrorEvent) => void) | null = null; const post = (data: unknown) => { if (onmessage) { onmessage({ data } as MessageEvent); } }; const worker = { get onmessage() { return onmessage; }, set onmessage(fn: ((ev: MessageEvent) => void) | null) { onmessage = fn; }, get onerror() { return onerror; }, set onerror(fn: ((ev: ErrorEvent) => void) | null) { onerror = fn; }, postMessage: (msg: unknown) => { void Promise.resolve().then(() => handler(msg, post)); }, terminate: vi.fn(), }; return worker as unknown as Worker; }; } function makeSuccessWorker(): () => Worker { return makeWorkerFactory((msg, post) => { const m = msg as { type: string; id: number; sql?: string; params?: unknown[]; }; if (m.type === "init") { post({ id: m.id, ok: true, result: { type: "ready" } }); } else if (m.type === "query") { post({ id: m.id, ok: true, result: [["value1"], ["value2"]] }); } else if (m.type === "execute") { post({ id: m.id, ok: true, result: { rowsAffected: 1 } }); } else if (m.type === "close") { post({ id: m.id, ok: true, result: { type: "closed" } }); } }); } describe("createWasmDriver", () => { it("creates driver and resolves on successful init", { timeout: 30000, }, async () => { const { db, close } = await createWasmDriver( undefined, makeSuccessWorker() ); expect(db).toBeDefined(); await close(); }); it("query returns rows from worker", { timeout: 30000 }, async () => { const { db, close } = await createWasmDriver( undefined, makeSuccessWorker() ); const rows = await db.query("SELECT * FROM t"); expect(rows).toEqual([["value1"], ["value2"]]); await close(); }); it("execute returns rowsAffected from worker", { timeout: 30000, }, async () => { const { db, close } = await createWasmDriver( undefined, makeSuccessWorker() ); const result = await db.execute("INSERT INTO t VALUES (?)", [1]); expect(result.rowsAffected).toBe(1); await close(); }); it("close() is idempotent", { timeout: 30000 }, async () => { const { close } = await createWasmDriver( undefined, makeSuccessWorker() ); await close(); await expect(close()).resolves.toBeUndefined(); }); it("rejects pending requests when close() is called", { timeout: 30000, }, async () => { let postFn: ((data: unknown) => void) | null = null; const slowWorker = makeWorkerFactory((msg, post) => { const m = msg as { type: string; id: number }; if (m.type === "init") { post({ id: m.id, ok: true, result: { type: "ready" } }); } else if (m.type === "close") { post({ id: m.id, ok: true, result: { type: "closed" } }); } // query messages hang — capture postFn for manual resolution if (m.type === "query") { postFn = post; } }); const { db, close } = await createWasmDriver(undefined, slowWorker); // Fire a query that will hang — capture the promise const queryPromise = db.query("SELECT 1"); // Ensure the query is actually sent before closing await Promise.resolve(); // close() should drain pending await close(); // The hanging query should now reject await expect(queryPromise).rejects.toBeInstanceOf(SqliteError); // Suppress unused variable warning void postFn; }); it("throws SqliteError on worker init failure", { timeout: 30000, }, async () => { const failWorker = makeWorkerFactory((msg, post) => { const m = msg as { type: string; id: number }; if (m.type === "init") { post({ id: m.id, ok: false, error: "sqlite-wasm init failed: load error", }); } else if (m.type === "close") { post({ id: m.id, ok: true, result: { type: "closed" } }); } }); await expect( createWasmDriver(undefined, failWorker) ).rejects.toBeInstanceOf(SqliteError); await expect( createWasmDriver(undefined, failWorker) ).rejects.toMatchObject({ code: "SQLITE.DRIVER_INIT_FAILED" }); }); it("worker error event rejects pending requests", { timeout: 30000, }, async () => { let fireError: ((ev: ErrorEvent) => void) | null = null; const hangingWorker = makeWorkerFactory((msg, post) => { const m = msg as { type: string; id: number }; if (m.type === "init") { post({ id: m.id, ok: true, result: { type: "ready" } }); } // query messages hang — worker "crashes" below }); const patchedFactory = () => { const worker = hangingWorker(); Object.defineProperty(worker, "onerror", { set: (fn: (ev: ErrorEvent) => void) => { fireError = fn; }, get: () => fireError, configurable: true, }); return worker; }; const { db } = await createWasmDriver(undefined, patchedFactory); const queryPromise = db.query("SELECT 1"); await Promise.resolve(); // Simulate worker crash — onerror should reject all pending promises fireError?.({ message: "Worker crashed" } as ErrorEvent); await expect(queryPromise).rejects.toBeInstanceOf(SqliteError); }); });