test: actualiza pruebas unitarias de BD a nuevas tablas y añade tests

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
pull/1/head
borja 2 months ago
parent 61add46ede
commit e7d3596005

@ -21,11 +21,13 @@ describe('Database', () => {
});
beforeEach(() => {
// Reset database schema between tests by dropping tables and re-initializing
// Reset database schema between tests by dropping tables and re-initializing (respect FKs)
testDb.exec('DROP TABLE IF EXISTS task_assignments'); // Drop dependent tables first
testDb.exec('DROP TABLE IF EXISTS tasks');
testDb.exec('DROP TABLE IF EXISTS users');
testDb.exec('DROP TABLE IF EXISTS response_queue');
testDb.exec('DROP TABLE IF EXISTS group_members');
testDb.exec('DROP TABLE IF EXISTS groups');
testDb.exec('DROP TABLE IF EXISTS users');
// Initialize schema on the test database instance
initializeDatabase(testDb);
});
@ -38,7 +40,7 @@ describe('Database', () => {
.map((t: any) => t.name);
// Order matters if foreign keys are involved during creation, though SQLite is flexible
const expectedTables = ['users', 'groups', 'tasks', 'task_assignments', 'response_queue'];
const expectedTables = ['users', 'groups', 'group_members', 'tasks', 'task_assignments', 'response_queue'];
const userTables = tables.filter(t => !t.startsWith('sqlite_'));
// Check if all expected tables exist, order might vary slightly depending on execution
expect(userTables).toHaveLength(expectedTables.length);
@ -83,6 +85,22 @@ describe('Database', () => {
expect(group.active).toBe(1); // SQLite uses 1 for TRUE
});
test('group_members table should have required columns', () => {
const columns = testDb
.query("PRAGMA table_info(group_members)")
.all()
.map((c: any) => c.name);
expect(columns).toEqual([
'group_id',
'user_id',
'is_admin',
'is_active',
'first_seen_at',
'last_seen_at',
'last_role_change_at'
]);
});
test('response_queue table should have required columns (at least base set)', () => {
const columns = testDb
.query("PRAGMA table_info(response_queue)")
@ -138,6 +156,49 @@ describe('Database', () => {
expect(assignedByFk.on_delete).toBe('CASCADE');
});
test('group_members should reference groups and users via FKs', () => {
const fkInfo = testDb.query("PRAGMA foreign_key_list(group_members)").all();
const groupFk = fkInfo.find((fk: any) => fk.from === 'group_id');
const userFk = fkInfo.find((fk: any) => fk.from === 'user_id');
expect(groupFk).toBeDefined();
expect(groupFk.table).toBe('groups');
expect(groupFk.to).toBe('id');
expect(groupFk.on_delete).toBe('CASCADE');
expect(userFk).toBeDefined();
expect(userFk.table).toBe('users');
expect(userFk.to).toBe('id');
expect(userFk.on_delete).toBe('CASCADE');
});
test('should prevent inserting group_members with non-existent FKs', () => {
expect(() => {
testDb.prepare(`
INSERT INTO group_members (group_id, user_id, is_admin)
VALUES ('nonexistent-group', 'nonexistent-user', 0)
`).run();
}).toThrow();
});
test('deleting a group cascades to group_members', () => {
// Arrange: create user and group and membership
testDb.exec(`INSERT INTO users (id) VALUES ('user-x')`);
testDb.exec(`INSERT INTO groups (id, community_id, name) VALUES ('group-x', 'comm', 'Group X')`);
testDb.exec(`INSERT INTO group_members (group_id, user_id, is_admin) VALUES ('group-x', 'user-x', 0)`);
// Ensure membership exists
let count = testDb.query(`SELECT COUNT(*) as c FROM group_members WHERE group_id='group-x' AND user_id='user-x'`).get() as any;
expect(Number(count.c)).toBe(1);
// Act: delete group
testDb.exec(`DELETE FROM groups WHERE id='group-x'`);
// Assert: membership is gone due to ON DELETE CASCADE
count = testDb.query(`SELECT COUNT(*) as c FROM group_members WHERE group_id='group-x' AND user_id='user-x'`).get() as any;
expect(Number(count.c)).toBe(0);
});
test('should prevent inserting task with non-existent user', () => {
expect(() => {
testDb.prepare(`

@ -0,0 +1,108 @@
import { Database } from 'bun:sqlite';
import { beforeAll, beforeEach, afterAll, describe, expect, test } from 'bun:test';
import { initializeDatabase } from '../../../src/db';
import { GroupSyncService } from '../../../src/services/group-sync';
describe('GroupSyncService - reconcileGroupMembers', () => {
let memdb: Database;
beforeAll(() => {
memdb = new Database(':memory:');
memdb.exec('PRAGMA foreign_keys = ON;');
initializeDatabase(memdb);
// Inyectar DB en el servicio
GroupSyncService.dbInstance = memdb as any;
});
afterAll(() => {
memdb.close();
});
beforeEach(() => {
// Limpiar tablas relevantes entre tests
memdb.exec('DELETE FROM group_members');
memdb.exec('DELETE FROM users');
memdb.exec('DELETE FROM groups');
// Crear grupo base activo
memdb.prepare(`INSERT INTO groups (id, community_id, name) VALUES (?, ?, ?)`)
.run('123@g.us', 'community-1', 'Grupo 123');
});
function getMember(userId: string) {
return memdb.prepare(`
SELECT group_id, user_id, is_admin, is_active, first_seen_at, last_seen_at, last_role_change_at
FROM group_members
WHERE group_id = ? AND user_id = ?
`).get('123@g.us', userId) as any;
}
test('inserta miembros y marca activos en la primera reconciliación', () => {
const res = GroupSyncService.reconcileGroupMembers('123@g.us', [
{ userId: '111', isAdmin: true },
{ userId: '222', isAdmin: false }
], '2025-01-01 00:00:00.000');
expect(res).toEqual({ added: 2, updated: 0, deactivated: 0 });
const m1 = getMember('111');
const m2 = getMember('222');
expect(m1).toBeDefined();
expect(m1.is_active).toBe(1);
expect(m1.is_admin).toBe(1);
expect(m1.first_seen_at).toBe('2025-01-01 00:00:00.000');
expect(m1.last_seen_at).toBe('2025-01-01 00:00:00.000');
expect(m2).toBeDefined();
expect(m2.is_active).toBe(1);
expect(m2.is_admin).toBe(0);
});
test('actualiza roles, desactiva ausentes y añade nuevos en reconciliaciones posteriores', () => {
// Primera pasada
GroupSyncService.reconcileGroupMembers('123@g.us', [
{ userId: '111', isAdmin: true },
{ userId: '222', isAdmin: false }
], '2025-01-01 00:00:00.000');
// Segunda pasada con cambios: 111 pierde admin, 222 desaparece, 333 aparece
const res2 = GroupSyncService.reconcileGroupMembers('123@g.us', [
{ userId: '111', isAdmin: false },
{ userId: '333', isAdmin: false }
], '2025-01-02 00:00:00.000');
expect(res2).toEqual({ added: 1, updated: 1, deactivated: 1 });
const m111 = getMember('111');
const m222 = getMember('222');
const m333 = getMember('333');
expect(m111.is_active).toBe(1);
expect(m111.is_admin).toBe(0);
expect(m111.last_role_change_at).toBe('2025-01-02 00:00:00.000');
expect(m222.is_active).toBe(0);
expect(m333.is_active).toBe(1);
expect(m333.is_admin).toBe(0);
});
test('idempotencia: aplicar mismo snapshot no altera contadores y actualiza last_seen_at', () => {
GroupSyncService.reconcileGroupMembers('123@g.us', [
{ userId: '111', isAdmin: false },
{ userId: '333', isAdmin: false }
], '2025-01-02 00:00:00.000');
const res3 = GroupSyncService.reconcileGroupMembers('123@g.us', [
{ userId: '111', isAdmin: false },
{ userId: '333', isAdmin: false }
], '2025-01-03 00:00:00.000');
expect(res3).toEqual({ added: 0, updated: 0, deactivated: 0 });
const m111 = getMember('111');
const m333 = getMember('333');
expect(m111.last_seen_at).toBe('2025-01-03 00:00:00.000');
expect(m333.last_seen_at).toBe('2025-01-03 00:00:00.000');
});
});
Loading…
Cancel
Save