Files
readest/apps/readest-app/src/__tests__/database/suites/base-tests.ts
T

176 lines
7.1 KiB
TypeScript

import { it, expect } from 'vitest';
import { DatabaseService } from '@/types/database';
/**
* Shared base database operation tests exercised against any DatabaseService.
* Covers CRUD, batch, data types, error handling, and result contract.
*
* Call inside a describe() block:
* describe('base ops', () => { baseTests(() => db); });
*/
export function baseTests(getDb: () => DatabaseService) {
// ---------------------------------------------------------------------------
// Schema & basic operations
// ---------------------------------------------------------------------------
it('creates a table and inserts a row', async () => {
const db = getDb();
await db.execute('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)');
const result = await db.execute('INSERT INTO items (name) VALUES (?)', ['apple']);
expect(result.rowsAffected).toBe(1);
expect(result.lastInsertId).toBe(1);
});
it('inserts multiple rows with auto-incrementing ids', async () => {
const db = getDb();
await db.execute('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)');
const r1 = await db.execute('INSERT INTO items (name) VALUES (?)', ['a']);
const r2 = await db.execute('INSERT INTO items (name) VALUES (?)', ['b']);
const r3 = await db.execute('INSERT INTO items (name) VALUES (?)', ['c']);
expect(r1.lastInsertId).toBe(1);
expect(r2.lastInsertId).toBe(2);
expect(r3.lastInsertId).toBe(3);
});
// ---------------------------------------------------------------------------
// SELECT queries
// ---------------------------------------------------------------------------
it('select returns typed rows', async () => {
const db = getDb();
await db.execute('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, qty INTEGER)');
await db.execute('INSERT INTO items (name, qty) VALUES (?, ?)', ['apple', 10]);
await db.execute('INSERT INTO items (name, qty) VALUES (?, ?)', ['banana', 20]);
const rows = await db.select<{ id: number; name: string; qty: number }>(
'SELECT * FROM items ORDER BY id',
);
expect(rows).toHaveLength(2);
expect(rows[0]!.name).toBe('apple');
expect(rows[0]!.qty).toBe(10);
expect(rows[1]!.name).toBe('banana');
expect(rows[1]!.qty).toBe(20);
});
it('select with WHERE and params', async () => {
const db = getDb();
await db.execute('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)');
await db.execute('INSERT INTO items (name) VALUES (?)', ['apple']);
await db.execute('INSERT INTO items (name) VALUES (?)', ['banana']);
await db.execute('INSERT INTO items (name) VALUES (?)', ['cherry']);
const rows = await db.select<{ id: number; name: string }>(
'SELECT * FROM items WHERE name = ?',
['banana'],
);
expect(rows).toHaveLength(1);
expect(rows[0]!.name).toBe('banana');
});
it('select returns empty array for no matching rows', async () => {
const db = getDb();
await db.execute('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)');
const rows = await db.select('SELECT * FROM items');
expect(rows).toEqual([]);
});
// ---------------------------------------------------------------------------
// UPDATE & DELETE
// ---------------------------------------------------------------------------
it('UPDATE returns rowsAffected', async () => {
const db = getDb();
await db.execute('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)');
await db.execute('INSERT INTO items (name) VALUES (?)', ['old']);
await db.execute('INSERT INTO items (name) VALUES (?)', ['old']);
const result = await db.execute('UPDATE items SET name = ? WHERE name = ?', ['new', 'old']);
expect(result.rowsAffected).toBe(2);
});
it('DELETE returns rowsAffected', async () => {
const db = getDb();
await db.execute('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)');
await db.execute('INSERT INTO items (name) VALUES (?)', ['a']);
await db.execute('INSERT INTO items (name) VALUES (?)', ['b']);
await db.execute('INSERT INTO items (name) VALUES (?)', ['c']);
const result = await db.execute('DELETE FROM items WHERE name IN (?, ?)', ['a', 'c']);
expect(result.rowsAffected).toBe(2);
const remaining = await db.select<{ name: string }>('SELECT name FROM items');
expect(remaining).toHaveLength(1);
expect(remaining[0]!.name).toBe('b');
});
// ---------------------------------------------------------------------------
// Batch execution
// ---------------------------------------------------------------------------
it('batch executes multiple statements atomically', async () => {
const db = getDb();
await db.batch([
'CREATE TABLE t1 (id INTEGER PRIMARY KEY, val TEXT)',
"INSERT INTO t1 (val) VALUES ('one')",
"INSERT INTO t1 (val) VALUES ('two')",
"INSERT INTO t1 (val) VALUES ('three')",
]);
const rows = await db.select<{ val: string }>('SELECT val FROM t1 ORDER BY id');
expect(rows).toHaveLength(3);
expect(rows.map((r) => r.val)).toEqual(['one', 'two', 'three']);
});
// ---------------------------------------------------------------------------
// Data types
// ---------------------------------------------------------------------------
it('handles NULL values correctly', async () => {
const db = getDb();
await db.execute('CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT)');
await db.execute('INSERT INTO t (val) VALUES (?)', [null]);
const rows = await db.select<{ id: number; val: string | null }>('SELECT * FROM t');
expect(rows).toHaveLength(1);
expect(rows[0]!.val).toBeNull();
});
it('handles integer and real types', async () => {
const db = getDb();
await db.execute('CREATE TABLE nums (i INTEGER, r REAL)');
await db.execute('INSERT INTO nums (i, r) VALUES (?, ?)', [42, 3.14]);
const rows = await db.select<{ i: number; r: number }>('SELECT * FROM nums');
expect(rows[0]!.i).toBe(42);
expect(rows[0]!.r).toBeCloseTo(3.14);
});
// ---------------------------------------------------------------------------
// Error handling
// ---------------------------------------------------------------------------
it('throws on invalid SQL', async () => {
const db = getDb();
await expect(db.execute('INVALID SQL STATEMENT')).rejects.toThrow();
});
it('throws on constraint violation', async () => {
const db = getDb();
await db.execute('CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT UNIQUE)');
await db.execute('INSERT INTO t (val) VALUES (?)', ['unique_val']);
await expect(db.execute('INSERT INTO t (val) VALUES (?)', ['unique_val'])).rejects.toThrow();
});
// ---------------------------------------------------------------------------
// Result contract
// ---------------------------------------------------------------------------
it('execute result always has rowsAffected and lastInsertId as numbers', async () => {
const db = getDb();
await db.execute('CREATE TABLE t (id INTEGER PRIMARY KEY)');
const result = await db.execute('INSERT INTO t DEFAULT VALUES');
expect(typeof result.rowsAffected).toBe('number');
expect(typeof result.lastInsertId).toBe('number');
});
}