// tests/main/services/HistoryService.test.ts // HistoryService 단위 테스트 — DB 계층을 모킹하여 순수 로직만 검증 import { describe, it, expect, beforeEach, vi } from 'vitest' // drizzle 모킹: 체이너블 쿼리 빌더 패턴 function createMockQueryBuilder(data: unknown[] = []) { const builder: Record = {} builder.values = vi.fn(() => ({ run: vi.fn(() => ({ changes: 1 })) })) builder.from = vi.fn(() => builder) builder.where = vi.fn(() => builder) builder.orderBy = vi.fn(() => builder) builder.limit = vi.fn(() => builder) builder.offset = vi.fn(() => builder) builder.set = vi.fn(() => builder) builder.get = vi.fn(() => data[0] ?? null) builder.all = vi.fn(() => data) builder.run = vi.fn(() => ({ changes: 1 })) return builder } const mockDb = { insert: vi.fn(() => createMockQueryBuilder()), select: vi.fn(() => createMockQueryBuilder()), update: vi.fn(() => createMockQueryBuilder()), delete: vi.fn(() => createMockQueryBuilder()) } vi.mock('../../../src/main/db', () => ({ getDatabase: () => mockDb })) vi.mock('../../../src/main/services/LoggerService', () => ({ getLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }) })) let getHistoryService: () => ReturnType beforeEach(async () => { vi.resetModules() vi.clearAllMocks() const mod = await import('../../../src/main/services/HistoryService') getHistoryService = mod.getHistoryService }) describe('HistoryService', () => { describe('create', () => { it('insert를 호출하고 엔트리를 반환한다', () => { const svc = getHistoryService() const entry = svc.create({ originalText: '안녕하세요', duration: 3.5, wordCount: 3, mode: 'dictation', status: 'completed', appVersion: '1.0.0' }) expect(mockDb.insert).toHaveBeenCalled() expect(entry.id).toBeDefined() expect(entry.originalText).toBe('안녕하세요') expect(entry.duration).toBe(3.5) expect(entry.createdAt).toBeGreaterThan(0) }) it('stats 업데이트를 호출한다', () => { const svc = getHistoryService() svc.create({ originalText: 'test', duration: 5.0, wordCount: 10, mode: 'dictation', status: 'completed', appVersion: '1.0.0' }) // insert 2번: history + stats update expect(mockDb.insert).toHaveBeenCalledTimes(1) expect(mockDb.update).toHaveBeenCalledTimes(1) // stats update }) }) describe('getById', () => { it('DB에서 조회한 결과를 반환한다', () => { // select().from().where().get()이 null 반환하면 null const svc = getHistoryService() const result = svc.getById('nonexistent') expect(result).toBeNull() }) }) describe('delete', () => { it('delete 쿼리를 실행하고 changes > 0이면 true를 반환한다', () => { const svc = getHistoryService() const result = svc.delete('some-id') expect(mockDb.delete).toHaveBeenCalled() expect(result).toBe(true) }) }) describe('deleteAll', () => { it('delete 쿼리를 실행한다', () => { const svc = getHistoryService() svc.deleteAll() expect(mockDb.delete).toHaveBeenCalled() }) }) describe('getStats', () => { it('stats + today 쿼리를 실행한다', () => { const svc = getHistoryService() const st = svc.getStats() expect(mockDb.select).toHaveBeenCalled() expect(st).toHaveProperty('totalRecordingTimeMs') expect(st).toHaveProperty('todaySessionCount') expect(st).toHaveProperty('streakDays') }) }) describe('dispose', () => { it('에러 없이 호출된다', () => { const svc = getHistoryService() expect(() => svc.dispose()).not.toThrow() }) }) })