Spaces:
Runtime error
Runtime error
| import { describe, it, expect, vi, beforeEach } from 'vitest' | |
| // Use vi.hoisted() so mock variables are available inside vi.mock() factories | |
| const { mockBroadcast, mockRun, mockGet, mockPrepare } = vi.hoisted(() => { | |
| const mockRun = vi.fn(() => ({ lastInsertRowid: 1, changes: 1 })) | |
| const mockGet = vi.fn((): any => ({ count: 1 })) | |
| const mockPrepare = vi.fn(() => ({ | |
| run: mockRun, | |
| get: mockGet, | |
| all: vi.fn(() => []), | |
| })) | |
| const mockBroadcast = vi.fn() | |
| return { mockBroadcast, mockRun, mockGet, mockPrepare } | |
| }) | |
| // Mock better-sqlite3 native module to avoid needing compiled bindings | |
| vi.mock('better-sqlite3', () => { | |
| return { | |
| default: vi.fn(() => ({ | |
| prepare: mockPrepare, | |
| pragma: vi.fn(), | |
| exec: vi.fn(), | |
| close: vi.fn(), | |
| })), | |
| } | |
| }) | |
| vi.mock('@/lib/config', () => ({ | |
| config: { dbPath: ':memory:' }, | |
| ensureDirExists: vi.fn(), | |
| })) | |
| vi.mock('@/lib/migrations', () => ({ | |
| runMigrations: vi.fn(), | |
| })) | |
| vi.mock('@/lib/password', () => ({ | |
| hashPassword: vi.fn((p: string) => `hashed:${p}`), | |
| verifyPassword: vi.fn(() => false), | |
| })) | |
| vi.mock('@/lib/logger', () => ({ | |
| logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() }, | |
| })) | |
| vi.mock('@/lib/event-bus', () => ({ | |
| eventBus: { broadcast: mockBroadcast, on: vi.fn(), emit: vi.fn(), setMaxListeners: vi.fn() }, | |
| })) | |
| // Import after mocks — the real db_helpers will use our mocked getDatabase | |
| import { db_helpers } from '@/lib/db' | |
| describe('parseMentions', () => { | |
| it('extracts multiple mentions', () => { | |
| expect(db_helpers.parseMentions('@alice hello @bob')).toEqual(['alice', 'bob']) | |
| }) | |
| it('returns empty array when no mentions', () => { | |
| expect(db_helpers.parseMentions('no mentions here')).toEqual([]) | |
| }) | |
| it('extracts single mention', () => { | |
| expect(db_helpers.parseMentions('hey @alice')).toEqual(['alice']) | |
| }) | |
| it('handles @@double — captures word chars after @', () => { | |
| const result = db_helpers.parseMentions('@@double') | |
| expect(result).toContain('double') | |
| }) | |
| it('handles mentions at start and end of string', () => { | |
| expect(db_helpers.parseMentions('@start and @end')).toEqual(['start', 'end']) | |
| }) | |
| it('supports hyphen, underscore, and dots in handles', () => { | |
| expect(db_helpers.parseMentions('ping @agent-code_reviewer.v2 now')).toEqual(['agent-code_reviewer.v2']) | |
| }) | |
| it('deduplicates repeated mentions case-insensitively', () => { | |
| expect(db_helpers.parseMentions('@Alice please sync with @alice')).toEqual(['Alice']) | |
| }) | |
| it('returns empty array for empty string', () => { | |
| expect(db_helpers.parseMentions('')).toEqual([]) | |
| }) | |
| }) | |
| describe('logActivity', () => { | |
| beforeEach(() => { | |
| vi.clearAllMocks() | |
| }) | |
| it('inserts activity into database and broadcasts event', () => { | |
| db_helpers.logActivity('task_created', 'task', 1, 'alice', 'Created task') | |
| expect(mockPrepare).toHaveBeenCalled() | |
| expect(mockRun).toHaveBeenCalledWith( | |
| 'task_created', 'task', 1, 'alice', 'Created task', null, 1, | |
| ) | |
| expect(mockBroadcast).toHaveBeenCalledWith( | |
| 'activity.created', | |
| expect.objectContaining({ | |
| type: 'task_created', | |
| entity_type: 'task', | |
| entity_id: 1, | |
| actor: 'alice', | |
| }), | |
| ) | |
| }) | |
| it('stringifies data when provided', () => { | |
| const data = { key: 'value' } | |
| db_helpers.logActivity('update', 'agent', 2, 'bob', 'Updated agent', data) | |
| expect(mockRun).toHaveBeenCalledWith( | |
| 'update', 'agent', 2, 'bob', 'Updated agent', JSON.stringify(data), 1, | |
| ) | |
| }) | |
| }) | |
| describe('createNotification', () => { | |
| beforeEach(() => { | |
| vi.clearAllMocks() | |
| }) | |
| it('inserts notification and broadcasts event', () => { | |
| db_helpers.createNotification('alice', 'mention', 'Mentioned', 'You were mentioned') | |
| expect(mockRun).toHaveBeenCalledWith( | |
| 'alice', 'mention', 'Mentioned', 'You were mentioned', undefined, undefined, 1, | |
| ) | |
| expect(mockBroadcast).toHaveBeenCalledWith( | |
| 'notification.created', | |
| expect.objectContaining({ | |
| recipient: 'alice', | |
| type: 'mention', | |
| title: 'Mentioned', | |
| }), | |
| ) | |
| }) | |
| it('passes source_type and source_id when provided', () => { | |
| db_helpers.createNotification('bob', 'alert', 'Alert', 'CPU high', 'agent', 5) | |
| expect(mockRun).toHaveBeenCalledWith( | |
| 'bob', 'alert', 'Alert', 'CPU high', 'agent', 5, 1, | |
| ) | |
| }) | |
| }) | |
| describe('updateAgentStatus', () => { | |
| beforeEach(() => { | |
| vi.clearAllMocks() | |
| mockGet.mockReturnValue({ id: 42 }) | |
| }) | |
| it('updates agent status in database and broadcasts', () => { | |
| db_helpers.updateAgentStatus('worker-1', 'busy', 'Processing task') | |
| expect(mockPrepare).toHaveBeenCalled() | |
| expect(mockBroadcast).toHaveBeenCalledWith( | |
| 'agent.status_changed', | |
| expect.objectContaining({ | |
| id: 42, | |
| name: 'worker-1', | |
| status: 'busy', | |
| }), | |
| ) | |
| }) | |
| }) | |