202 lines
6.1 KiB
TypeScript
202 lines
6.1 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { SqliteAdapter } from './sqlite.js';
|
|
|
|
describe('SqliteAdapter', () => {
|
|
let adapter: SqliteAdapter;
|
|
|
|
beforeEach(async () => {
|
|
adapter = new SqliteAdapter({ type: 'sqlite', path: ':memory:' });
|
|
await adapter.migrate();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await adapter.close();
|
|
});
|
|
|
|
describe('CRUD', () => {
|
|
it('creates and reads a record', async () => {
|
|
const created = await adapter.create('users', { name: 'Alice', email: 'alice@test.com' });
|
|
expect(created.id).toBeDefined();
|
|
expect(created.name).toBe('Alice');
|
|
|
|
const read = await adapter.read('users', created.id);
|
|
expect(read).not.toBeNull();
|
|
expect(read!.name).toBe('Alice');
|
|
expect(read!.email).toBe('alice@test.com');
|
|
});
|
|
|
|
it('returns null for non-existent record', async () => {
|
|
const result = await adapter.read('users', 'does-not-exist');
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('updates a record', async () => {
|
|
const created = await adapter.create('users', { name: 'Alice' });
|
|
const updated = await adapter.update('users', created.id, { name: 'Bob' });
|
|
expect(updated).toBe(true);
|
|
|
|
const read = await adapter.read('users', created.id);
|
|
expect(read!.name).toBe('Bob');
|
|
});
|
|
|
|
it('update returns false for non-existent record', async () => {
|
|
const result = await adapter.update('users', 'does-not-exist', { name: 'X' });
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('deletes a record', async () => {
|
|
const created = await adapter.create('users', { name: 'Alice' });
|
|
const deleted = await adapter.delete('users', created.id);
|
|
expect(deleted).toBe(true);
|
|
|
|
const read = await adapter.read('users', created.id);
|
|
expect(read).toBeNull();
|
|
});
|
|
|
|
it('delete returns false for non-existent record', async () => {
|
|
const result = await adapter.delete('users', 'does-not-exist');
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('find', () => {
|
|
it('finds records with filter', async () => {
|
|
await adapter.create('users', { name: 'Alice', role: 'admin' });
|
|
await adapter.create('users', { name: 'Bob', role: 'user' });
|
|
await adapter.create('users', { name: 'Charlie', role: 'admin' });
|
|
|
|
const admins = await adapter.find('users', { role: 'admin' });
|
|
expect(admins).toHaveLength(2);
|
|
expect(admins.map((u) => u.name).sort()).toEqual(['Alice', 'Charlie']);
|
|
});
|
|
|
|
it('finds all records without filter', async () => {
|
|
await adapter.create('users', { name: 'Alice' });
|
|
await adapter.create('users', { name: 'Bob' });
|
|
|
|
const all = await adapter.find('users');
|
|
expect(all).toHaveLength(2);
|
|
});
|
|
|
|
it('supports limit and offset', async () => {
|
|
for (let i = 0; i < 5; i++) {
|
|
await adapter.create('users', { name: `User${i}`, idx: i });
|
|
}
|
|
|
|
const page = await adapter.find('users', undefined, {
|
|
limit: 2,
|
|
offset: 1,
|
|
orderBy: 'created_at',
|
|
});
|
|
expect(page).toHaveLength(2);
|
|
});
|
|
|
|
it('findOne returns first match', async () => {
|
|
await adapter.create('users', { name: 'Alice', role: 'admin' });
|
|
await adapter.create('users', { name: 'Bob', role: 'user' });
|
|
|
|
const found = await adapter.findOne('users', { role: 'user' });
|
|
expect(found).not.toBeNull();
|
|
expect(found!.name).toBe('Bob');
|
|
});
|
|
|
|
it('findOne returns null when no match', async () => {
|
|
const result = await adapter.findOne('users', { role: 'nonexistent' });
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('count', () => {
|
|
it('counts all records', async () => {
|
|
await adapter.create('users', { name: 'Alice' });
|
|
await adapter.create('users', { name: 'Bob' });
|
|
|
|
const total = await adapter.count('users');
|
|
expect(total).toBe(2);
|
|
});
|
|
|
|
it('counts with filter', async () => {
|
|
await adapter.create('users', { name: 'Alice', role: 'admin' });
|
|
await adapter.create('users', { name: 'Bob', role: 'user' });
|
|
await adapter.create('users', { name: 'Charlie', role: 'admin' });
|
|
|
|
const adminCount = await adapter.count('users', { role: 'admin' });
|
|
expect(adminCount).toBe(2);
|
|
});
|
|
|
|
it('returns 0 for empty collection', async () => {
|
|
const count = await adapter.count('users');
|
|
expect(count).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('transaction', () => {
|
|
it('commits on success', async () => {
|
|
await adapter.transaction(async (tx) => {
|
|
await tx.create('users', { name: 'Alice' });
|
|
await tx.create('users', { name: 'Bob' });
|
|
});
|
|
|
|
const count = await adapter.count('users');
|
|
expect(count).toBe(2);
|
|
});
|
|
|
|
it('rolls back on error', async () => {
|
|
await expect(
|
|
adapter.transaction(async (tx) => {
|
|
await tx.create('users', { name: 'Alice' });
|
|
throw new Error('rollback test');
|
|
}),
|
|
).rejects.toThrow('rollback test');
|
|
|
|
const count = await adapter.count('users');
|
|
expect(count).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('migrate', () => {
|
|
it('creates all tables', async () => {
|
|
// migrate() was already called in beforeEach — verify tables exist
|
|
const collections = [
|
|
'users',
|
|
'sessions',
|
|
'accounts',
|
|
'projects',
|
|
'missions',
|
|
'tasks',
|
|
'agents',
|
|
'conversations',
|
|
'messages',
|
|
'preferences',
|
|
'insights',
|
|
'skills',
|
|
'events',
|
|
'routing_rules',
|
|
'provider_credentials',
|
|
'agent_logs',
|
|
'teams',
|
|
'team_members',
|
|
'mission_tasks',
|
|
'tickets',
|
|
'summarization_jobs',
|
|
'appreciations',
|
|
'verifications',
|
|
];
|
|
|
|
for (const collection of collections) {
|
|
// Should not throw
|
|
const count = await adapter.count(collection);
|
|
expect(count).toBe(0);
|
|
}
|
|
});
|
|
|
|
it('is idempotent', async () => {
|
|
await adapter.migrate();
|
|
await adapter.migrate();
|
|
// Should not throw
|
|
const count = await adapter.count('users');
|
|
expect(count).toBe(0);
|
|
});
|
|
});
|
|
});
|