Files
stack/packages/storage/src/adapters/sqlite.test.ts
2026-04-02 20:51:13 -05:00

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);
});
});
});