import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import fs from 'fs'; import path from 'path'; import { ReactionStore } from '../src/reaction-store'; describe('ReactionStore', () => { const testDbPath = path.join('data', 'test', 'reactions-test.db'); let store: ReactionStore; beforeEach(async () => { try { fs.unlinkSync(testDbPath); } catch { /* ignore */ } try { fs.unlinkSync(testDbPath + '-wal'); } catch { /* ignore */ } try { fs.unlinkSync(testDbPath + '-shm'); } catch { /* ignore */ } store = new ReactionStore(testDbPath); await store.initialization; }); afterEach(async () => { await store.close(); try { fs.unlinkSync(testDbPath); } catch { /* ignore */ } try { fs.unlinkSync(testDbPath + '-wal'); } catch { /* ignore */ } try { fs.unlinkSync(testDbPath + '-shm'); } catch { /* ignore */ } }); it('records an add and exposes it via listUsers / countUsers', async () => { await store.recordAdd(100, 4001, '124', 1, 12345, 'u_abc', 1700000000); const users = await store.listUsers(100, 4001, '124'); expect(users).toEqual([{ operatorUin: 12345, operatorUid: 'u_abc', setAt: 1700000000 }]); expect(await store.countUsers(100, 4001, '124')).toBe(1); }); it('upserts on duplicate (group, msg, emoji, operator)', async () => { await store.recordAdd(100, 4001, '124', 1, 12345, 'u_old', 1700000000); await store.recordAdd(100, 4001, '124', 1, 12345, 'u_new', 1700000500); const users = await store.listUsers(100, 4001, '124'); expect(users).toHaveLength(1); expect(users[0]?.operatorUid).toBe('u_new'); expect(users[0]?.setAt).toBe(1700000500); }); it('removes the right row only', async () => { await store.recordAdd(100, 4001, '124', 1, 12345, 'u_a', 1700000000); await store.recordAdd(100, 4001, '124', 1, 67890, 'u_b', 1700000100); await store.recordRemove(100, 4001, '124', 12345); const users = await store.listUsers(100, 4001, '124'); expect(users).toHaveLength(1); expect(users[0]?.operatorUin).toBe(67890); }); it('isolates by group / msg / emoji', async () => { await store.recordAdd(100, 4001, '124', 1, 1, 'u', 1); await store.recordAdd(100, 4001, '76', 1, 1, 'u', 1); await store.recordAdd(100, 4002, '124', 1, 1, 'u', 1); await store.recordAdd(200, 4001, '124', 1, 1, 'u', 1); expect(await store.countUsers(100, 4001, '124')).toBe(1); expect(await store.countUsers(100, 4001, '76')).toBe(1); expect(await store.countUsers(100, 4002, '124')).toBe(1); expect(await store.countUsers(200, 4001, '124')).toBe(1); expect(await store.countUsers(100, 4001, '999')).toBe(0); }); it('paginates listUsers via limit + offset, ordering by set_at ASC', async () => { for (let i = 0; i < 5; i++) { await store.recordAdd(100, 4001, '124', 1, 1000 + i, `u_${i}`, 1700000000 + i); } const page1 = await store.listUsers(100, 4001, '124', 2, 0); const page2 = await store.listUsers(100, 4001, '124', 2, 2); expect(page1.map((u) => u.operatorUin)).toEqual([1000, 1001]); expect(page2.map((u) => u.operatorUin)).toEqual([1002, 1003]); }); it('summarizeMessage groups by emoji_id', async () => { await store.recordAdd(100, 4001, '124', 1, 1, 'u1', 1); await store.recordAdd(100, 4001, '124', 1, 2, 'u2', 2); await store.recordAdd(100, 4001, '76', 1, 3, 'u3', 10); const summary = (await store.summarizeMessage(100, 4001)).sort((a, b) => a.emojiId.localeCompare(b.emojiId), ); expect(summary).toEqual([ { emojiId: '124', emojiType: 1, count: 2, lastSetAt: 2 }, { emojiId: '76', emojiType: 1, count: 1, lastSetAt: 10 }, ]); }); it('ignores incomplete records (no-op instead of throwing)', async () => { await store.recordAdd(0, 4001, '124', 1, 1, 'u', 1); // group 0 await store.recordAdd(100, 0, '124', 1, 1, 'u', 1); // msg 0 await store.recordAdd(100, 4001, '', 1, 1, 'u', 1); // empty emoji await store.recordAdd(100, 4001, '124', 1, 0, 'u', 1); // uin 0 expect(await store.countUsers(100, 4001, '124')).toBe(0); }); });