diff --git a/packages/db/adapters/TestDB.test.ts b/packages/db/adapters/TestDB.test.ts new file mode 100644 index 00000000..f2eb67c5 --- /dev/null +++ b/packages/db/adapters/TestDB.test.ts @@ -0,0 +1,25 @@ +import { DittoConf } from '@ditto/conf'; +import { NPostgres } from '@nostrify/db'; +import { genEvent } from '@nostrify/nostrify/test'; +import { assertEquals } from '@std/assert'; + +import { DittoPolyPg } from './DittoPolyPg.ts'; +import { TestDB } from './TestDB.ts'; + +Deno.test('TestDB', async () => { + const conf = new DittoConf(Deno.env); + const orig = new DittoPolyPg(conf.databaseUrl); + + await using db = new TestDB(orig); + await db.migrate(); + await db.clear(); + + const store = new NPostgres(orig.kysely); + await store.event(genEvent()); + + assertEquals((await store.count([{}])).count, 1); + + await db.clear(); + + assertEquals((await store.count([{}])).count, 0); +}); diff --git a/packages/db/adapters/TestDB.ts b/packages/db/adapters/TestDB.ts new file mode 100644 index 00000000..49f45a5f --- /dev/null +++ b/packages/db/adapters/TestDB.ts @@ -0,0 +1,49 @@ +import { type Kysely, sql } from 'kysely'; + +import type { DittoDB } from '../DittoDB.ts'; +import type { DittoTables } from '../DittoTables.ts'; + +/** Wraps another DittoDB implementation to clear all data when disposed. */ +export class TestDB implements DittoDB { + constructor(private db: DittoDB) {} + + get kysely(): Kysely { + return this.db.kysely; + } + + get poolSize(): number { + return this.db.poolSize; + } + + get availableConnections(): number { + return this.db.availableConnections; + } + + migrate(): Promise { + return this.db.migrate(); + } + + listen(channel: string, callback: (payload: string) => void): void { + return this.db.listen(channel, callback); + } + + /** Truncate all tables. */ + async clear(): Promise { + const query = sql<{ tablename: string }>`select tablename from pg_tables where schemaname = current_schema()`; + + const { rows } = await query.execute(this.db.kysely); + + for (const { tablename } of rows) { + if (tablename.startsWith('kysely_')) { + continue; // Skip Kysely's internal tables + } else { + await sql`truncate table ${sql.ref(tablename)} cascade`.execute(this.db.kysely); + } + } + } + + async [Symbol.asyncDispose](): Promise { + await this.clear(); + await this.db[Symbol.asyncDispose](); + } +} diff --git a/packages/db/mod.ts b/packages/db/mod.ts index 2766e524..ae50fff7 100644 --- a/packages/db/mod.ts +++ b/packages/db/mod.ts @@ -2,6 +2,7 @@ export { DittoPglite } from './adapters/DittoPglite.ts'; export { DittoPolyPg } from './adapters/DittoPolyPg.ts'; export { DittoPostgres } from './adapters/DittoPostgres.ts'; export { DummyDB } from './adapters/DummyDB.ts'; +export { TestDB } from './adapters/TestDB.ts'; export type { DittoDB } from './DittoDB.ts'; export type { DittoTables } from './DittoTables.ts';