119 lines
4.8 KiB
TypeScript
119 lines
4.8 KiB
TypeScript
/**
|
|
* Singleton lifecycle integration tests — exercise the real surrealdb singleton
|
|
* helpers against a live SurrealDB v3.0.4 server.
|
|
*
|
|
* Spec: openspec/changes/surrealdb-integration-tests/specs/database-surrealdb-integration/spec.md
|
|
* "Singleton lifecycle verified against the real engine".
|
|
*
|
|
* The spec is written against a generic `getSingleton(name, factory, _, _, registry)`
|
|
* shape; the real `@suaveplan/database/surrealdb` API is
|
|
* getSurrealSingleton(options, name, clientFactory) — using a module-level Map.
|
|
* These tests adapt the spec scenarios to that real shape: same-instance reuse,
|
|
* close-observable-via-NOT_CONNECTED, closeAll closes every registered name,
|
|
* and named-singleton isolation (closing "a" leaves "b" connected).
|
|
*
|
|
* @module
|
|
*/
|
|
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import { SURREAL_CODES } from "../../src/errors/index.js";
|
|
import {
|
|
closeAllSurrealSingletons,
|
|
closeSurrealSingleton,
|
|
getSurrealSingleton,
|
|
hasSurrealSingleton,
|
|
} from "../../src/singleton/index.js";
|
|
import { inject, surrealUnavailable } from "./integration-setup.js";
|
|
|
|
function adapterOptions(): {
|
|
url: string;
|
|
namespace: string;
|
|
database: string;
|
|
username: string;
|
|
password: string;
|
|
} {
|
|
const config = inject("surrealdb");
|
|
return {
|
|
url: config.url,
|
|
namespace: config.namespace,
|
|
database: config.database,
|
|
username: config.username,
|
|
password: config.password,
|
|
};
|
|
}
|
|
|
|
describe.skipIf(surrealUnavailable)(
|
|
"SurrealDB singleton lifecycle (real SurrealDB v3.0.4)",
|
|
() => {
|
|
it("getSurrealSingleton returns the same connected instance on second call", async () => {
|
|
const opts = adapterOptions();
|
|
const name = `singleton-same-${Date.now()}`;
|
|
try {
|
|
const a = await getSurrealSingleton(opts, name);
|
|
const b = await getSurrealSingleton(opts, name);
|
|
expect(b).toBe(a);
|
|
expect(a.isConnected).toBe(true);
|
|
const rows = await a.query<{ n: number }>("RETURN { n: 1 }");
|
|
expect(rows).toEqual([{ n: 1 }]);
|
|
} finally {
|
|
await closeSurrealSingleton(name);
|
|
}
|
|
}, 30000);
|
|
|
|
it("closeSurrealSingleton closes the underlying WebSocket — query throws NOT_CONNECTED on the previously-returned reference", async () => {
|
|
const opts = adapterOptions();
|
|
const name = `singleton-close-${Date.now()}`;
|
|
const adapter = await getSurrealSingleton(opts, name);
|
|
expect(adapter.isConnected).toBe(true);
|
|
await closeSurrealSingleton(name);
|
|
expect(hasSurrealSingleton(name)).toBe(false);
|
|
expect(adapter.isConnected).toBe(false);
|
|
await expect(adapter.query("SELECT 1")).rejects.toMatchObject({
|
|
code: SURREAL_CODES.NOT_CONNECTED,
|
|
});
|
|
}, 30000);
|
|
|
|
it("closeAllSurrealSingletons closes every registered singleton", async () => {
|
|
const opts = adapterOptions();
|
|
const stamp = Date.now();
|
|
const names = [
|
|
`closeall-a-${stamp}`,
|
|
`closeall-b-${stamp}`,
|
|
`closeall-c-${stamp}`,
|
|
];
|
|
const adapters = await Promise.all(
|
|
names.map((n) => getSurrealSingleton(opts, n))
|
|
);
|
|
for (const a of adapters) expect(a.isConnected).toBe(true);
|
|
await closeAllSurrealSingletons();
|
|
for (const n of names) expect(hasSurrealSingleton(n)).toBe(false);
|
|
for (const a of adapters) expect(a.isConnected).toBe(false);
|
|
for (const a of adapters) {
|
|
await expect(a.query("SELECT 1")).rejects.toMatchObject({
|
|
code: SURREAL_CODES.NOT_CONNECTED,
|
|
});
|
|
}
|
|
}, 60000);
|
|
|
|
it("named singletons are isolated — closing one leaves others connected", async () => {
|
|
const opts = adapterOptions();
|
|
const stamp = Date.now();
|
|
const nameA = `isolated-a-${stamp}`;
|
|
const nameB = `isolated-b-${stamp}`;
|
|
const a = await getSurrealSingleton(opts, nameA);
|
|
const b = await getSurrealSingleton(opts, nameB);
|
|
expect(b).not.toBe(a);
|
|
try {
|
|
await closeSurrealSingleton(nameA);
|
|
expect(hasSurrealSingleton(nameA)).toBe(false);
|
|
expect(hasSurrealSingleton(nameB)).toBe(true);
|
|
expect(a.isConnected).toBe(false);
|
|
expect(b.isConnected).toBe(true);
|
|
const rows = await b.query<{ n: number }>("RETURN { n: 1 }");
|
|
expect(rows).toEqual([{ n: 1 }]);
|
|
} finally {
|
|
await closeSurrealSingleton(nameB);
|
|
}
|
|
}, 30000);
|
|
}
|
|
);
|