Restructure chat service, web routes, and add test suite
Split monolithic chat service into modular structure with Matrix client support, extract web server into per-resource route modules, introduce bootstrap/main-loop separation, and add comprehensive vitest coverage across services, repositories, and utilities. Adds project docs and CLAUDE.md agent instructions.
This commit is contained in:
1 parent
dfa0580859
commit
633bb0ec04
84 files changed
+19278
-3888
No files matched your search
@@ -0,0 +1,146 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import Database from 'better-sqlite3';
|
||||
import { createDatabase, runMigrations } from '../src/database/client.js';
|
||||
import { getTestConfig } from '../tests/testUtils.js';
|
||||
|
||||
describe('Database Client', () => {
|
||||
describe('createDatabase', () => {
|
||||
it('creates an in-memory database', () => {
|
||||
const config = getTestConfig({ database: { path: ':memory:' } });
|
||||
const db = createDatabase(config);
|
||||
expect(db).toBeDefined();
|
||||
db.close();
|
||||
});
|
||||
|
||||
it('enables WAL mode', () => {
|
||||
const config = getTestConfig({ database: { path: ':memory:' } });
|
||||
const db = createDatabase(config);
|
||||
const journalMode = db.pragma('journal_mode', { simple: true }) as string;
|
||||
expect(journalMode).toBe('wal');
|
||||
db.close();
|
||||
});
|
||||
|
||||
it('enables foreign keys', () => {
|
||||
const config = getTestConfig({ database: { path: ':memory:' } });
|
||||
const db = createDatabase(config);
|
||||
const fk = db.pragma('foreign_keys', { simple: true }) as number;
|
||||
expect(fk).toBe(1);
|
||||
db.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('runMigrations', () => {
|
||||
let db: Database.Database;
|
||||
|
||||
beforeEach(() => {
|
||||
db = new Database(':memory:');
|
||||
db.pragma('foreign_keys = ON');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
db.close();
|
||||
});
|
||||
|
||||
it('creates processed_posts table', () => {
|
||||
runMigrations(db);
|
||||
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as { name: string }[];
|
||||
const tableNames = tables.map(t => t.name);
|
||||
expect(tableNames).toContain('processed_posts');
|
||||
});
|
||||
|
||||
it('creates sent_messages table', () => {
|
||||
runMigrations(db);
|
||||
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as { name: string }[];
|
||||
const tableNames = tables.map(t => t.name);
|
||||
expect(tableNames).toContain('sent_messages');
|
||||
});
|
||||
|
||||
it('creates successful_messages table', () => {
|
||||
runMigrations(db);
|
||||
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as { name: string }[];
|
||||
const tableNames = tables.map(t => t.name);
|
||||
expect(tableNames).toContain('successful_messages');
|
||||
});
|
||||
|
||||
it('creates indexes', () => {
|
||||
runMigrations(db);
|
||||
const indexes = db.prepare("SELECT name FROM sqlite_master WHERE type='index'").all() as { name: string }[];
|
||||
const indexNames = indexes.map(i => i.name);
|
||||
expect(indexNames).toContain('idx_posts_subreddit');
|
||||
expect(indexNames).toContain('idx_posts_processed_at');
|
||||
expect(indexNames).toContain('idx_posts_message_sent');
|
||||
expect(indexNames).toContain('idx_sent_messages_response');
|
||||
expect(indexNames).toContain('idx_sent_messages_recipient');
|
||||
});
|
||||
|
||||
it('adds got_response column', () => {
|
||||
runMigrations(db);
|
||||
const columns = db.prepare('PRAGMA table_info(sent_messages)').all() as { name: string }[];
|
||||
const columnNames = columns.map(c => c.name);
|
||||
expect(columnNames).toContain('got_response');
|
||||
expect(columnNames).toContain('response_checked_at');
|
||||
});
|
||||
|
||||
it('adds strategy_id column', () => {
|
||||
runMigrations(db);
|
||||
const columns = db.prepare('PRAGMA table_info(sent_messages)').all() as { name: string }[];
|
||||
const columnNames = columns.map(c => c.name);
|
||||
expect(columnNames).toContain('strategy_id');
|
||||
});
|
||||
|
||||
it('adds subreddit column to successful_messages', () => {
|
||||
runMigrations(db);
|
||||
const columns = db.prepare('PRAGMA table_info(successful_messages)').all() as { name: string }[];
|
||||
const columnNames = columns.map(c => c.name);
|
||||
expect(columnNames).toContain('subreddit');
|
||||
});
|
||||
|
||||
it('is idempotent (safe to run multiple times)', () => {
|
||||
runMigrations(db);
|
||||
runMigrations(db);
|
||||
runMigrations(db);
|
||||
|
||||
const tables = db
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
|
||||
.all() as { name: string }[];
|
||||
// processed_posts, sent_messages, successful_messages, activity_log
|
||||
expect(tables.length).toBe(4);
|
||||
});
|
||||
|
||||
it('processed_posts has correct schema', () => {
|
||||
runMigrations(db);
|
||||
const columns = db.prepare('PRAGMA table_info(processed_posts)').all() as { name: string; notnull: number }[];
|
||||
const columnMap = new Map(columns.map(c => [c.name, c]));
|
||||
|
||||
expect(columnMap.get('id')?.notnull).toBe(0); // TEXT is not NOT NULL by default
|
||||
expect(columnMap.get('subreddit')?.notnull).toBe(1);
|
||||
expect(columnMap.get('author')?.notnull).toBe(1);
|
||||
expect(columnMap.get('title')?.notnull).toBe(1);
|
||||
expect(columnMap.get('processed_at')?.notnull).toBe(1);
|
||||
expect(columnMap.get('message_sent')).toBeDefined();
|
||||
expect(columnMap.get('error')).toBeDefined();
|
||||
});
|
||||
|
||||
it('sent_messages has correct schema', () => {
|
||||
runMigrations(db);
|
||||
const columns = db.prepare('PRAGMA table_info(sent_messages)').all() as { name: string; notnull: number }[];
|
||||
const columnMap = new Map(columns.map(c => [c.name, c]));
|
||||
|
||||
expect(columnMap.get('recipient')?.notnull).toBe(1);
|
||||
expect(columnMap.get('subject')?.notnull).toBe(1);
|
||||
expect(columnMap.get('message')?.notnull).toBe(1);
|
||||
expect(columnMap.get('sent_at')?.notnull).toBe(1);
|
||||
expect(columnMap.get('post_id')?.notnull).toBe(1);
|
||||
});
|
||||
|
||||
it('successful_messages has correct schema', () => {
|
||||
runMigrations(db);
|
||||
const columns = db.prepare('PRAGMA table_info(successful_messages)').all() as { name: string; notnull: number }[];
|
||||
const columnMap = new Map(columns.map(c => [c.name, c]));
|
||||
|
||||
expect(columnMap.get('original_message')?.notnull).toBe(1);
|
||||
expect(columnMap.get('created_at')?.notnull).toBe(1);
|
||||
expect(columnMap.get('sent_message_id')?.notnull).toBe(0); // nullable
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user