108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import type { DbHandle } from '@mosaicstack/db';
|
|
|
|
// Mock @mosaicstack/db before importing the adapter
|
|
vi.mock('@mosaicstack/db', async (importOriginal) => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const actual = await importOriginal<Record<string, any>>();
|
|
return {
|
|
...actual,
|
|
createDb: vi.fn(),
|
|
runMigrations: vi.fn().mockResolvedValue(undefined),
|
|
};
|
|
});
|
|
|
|
import { createDb, runMigrations } from '@mosaicstack/db';
|
|
import { PostgresAdapter } from './postgres.js';
|
|
|
|
describe('PostgresAdapter — vector extension gating', () => {
|
|
let mockExecute: ReturnType<typeof vi.fn>;
|
|
let mockDb: { execute: ReturnType<typeof vi.fn> };
|
|
let mockHandle: Pick<DbHandle, 'close'> & { db: typeof mockDb };
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockExecute = vi.fn().mockResolvedValue(undefined);
|
|
mockDb = { execute: mockExecute };
|
|
mockHandle = { db: mockDb, close: vi.fn().mockResolvedValue(undefined) };
|
|
vi.mocked(createDb).mockReturnValue(mockHandle as unknown as DbHandle);
|
|
});
|
|
|
|
it('calls db.execute with CREATE EXTENSION IF NOT EXISTS vector when enableVector=true', async () => {
|
|
const adapter = new PostgresAdapter({
|
|
type: 'postgres',
|
|
url: 'postgresql://test:test@localhost:5432/test',
|
|
enableVector: true,
|
|
});
|
|
|
|
await adapter.migrate();
|
|
|
|
// Should have called execute
|
|
expect(mockExecute).toHaveBeenCalledTimes(1);
|
|
|
|
// Verify the SQL contains the extension creation statement.
|
|
// Prefer Drizzle's public toSQL() API; fall back to queryChunks if unavailable.
|
|
// NOTE: queryChunks is an undocumented Drizzle internal (drizzle-orm ^0.45.x).
|
|
// toSQL() was not present on the raw sql`` result in this version — if a future
|
|
// Drizzle upgrade adds it, remove the fallback path and delete this comment.
|
|
const sqlObj = mockExecute.mock.calls[0]![0] as {
|
|
toSQL?: () => { sql: string; params: unknown[] };
|
|
queryChunks?: Array<{ value: string[] }>;
|
|
};
|
|
const sqlText = sqlObj.toSQL
|
|
? sqlObj.toSQL().sql.toLowerCase()
|
|
: (sqlObj.queryChunks ?? [])
|
|
.flatMap((chunk) => chunk.value)
|
|
.join('')
|
|
.toLowerCase();
|
|
expect(sqlText).toContain('create extension if not exists vector');
|
|
});
|
|
|
|
it('does NOT call db.execute for extension when enableVector is false', async () => {
|
|
const adapter = new PostgresAdapter({
|
|
type: 'postgres',
|
|
url: 'postgresql://test:test@localhost:5432/test',
|
|
enableVector: false,
|
|
});
|
|
|
|
await adapter.migrate();
|
|
|
|
expect(mockExecute).not.toHaveBeenCalled();
|
|
expect(vi.mocked(runMigrations)).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('does NOT call db.execute for extension when enableVector is unset', async () => {
|
|
const adapter = new PostgresAdapter({
|
|
type: 'postgres',
|
|
url: 'postgresql://test:test@localhost:5432/test',
|
|
});
|
|
|
|
await adapter.migrate();
|
|
|
|
expect(mockExecute).not.toHaveBeenCalled();
|
|
expect(vi.mocked(runMigrations)).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('calls runMigrations after the extension is created', async () => {
|
|
const callOrder: string[] = [];
|
|
mockExecute.mockImplementation(() => {
|
|
callOrder.push('execute');
|
|
return Promise.resolve(undefined);
|
|
});
|
|
vi.mocked(runMigrations).mockImplementation(() => {
|
|
callOrder.push('runMigrations');
|
|
return Promise.resolve();
|
|
});
|
|
|
|
const adapter = new PostgresAdapter({
|
|
type: 'postgres',
|
|
url: 'postgresql://test:test@localhost:5432/test',
|
|
enableVector: true,
|
|
});
|
|
|
|
await adapter.migrate();
|
|
|
|
expect(callOrder).toEqual(['execute', 'runMigrations']);
|
|
});
|
|
});
|