import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import Database from 'better-sqlite3'; import { runMigrations } from '../../src/main/db/migrations/index.js'; import { NoteRepository } from '../../src/main/repository/NoteRepository.js'; describe('NoteRepository.search — FTS5', () => { let db: Database.Database; let repo: NoteRepository; beforeEach(() => { db = new Database(':memory:'); db.pragma('foreign_keys = ON'); runMigrations(db); repo = new NoteRepository(db); const a = repo.create({ rawText: '오늘 월요일 회의 정리' }); repo.updateAiResult(a.id, { title: '회의록', summary: '월요일', tags: ['기획', '회의'], provider: 'p' }); const b = repo.create({ rawText: '결재 요청 본문' }); repo.updateAiResult(b.id, { title: '결재', summary: '요청서', tags: ['결재'], provider: 'p' }); const c = repo.create({ rawText: '버려진 메모' }); repo.setStatus(c.id, 'trashed', null); }); afterEach(() => { db.close(); }); it('빈 query → 빈 배열', () => { expect(repo.search('')).toEqual([]); expect(repo.search(' ')).toEqual([]); }); it('keyword 매칭 → hydrated Note', () => { const r = repo.search('월요일'); expect(r.length).toBeGreaterThan(0); const titles = r.map((n) => n.aiTitle); expect(titles).toContain('회의록'); }); it('multi-token implicit AND', () => { const r1 = repo.search('회의 월요일'); expect(r1.length).toBeGreaterThan(0); const r2 = repo.search('회의 결재'); // 동시 매칭 노트 없음 expect(r2).toEqual([]); }); it('default 는 trashed 제외', () => { const r = repo.search('버려진'); expect(r).toEqual([]); }); it('status filter 명시 시 해당 status 만', () => { const r = repo.search('버려진', { status: 'trashed' }); expect(r.length).toBe(1); }); it('FTS5 special char 안전 처리', () => { expect(() => repo.search('"회의*" (월요일):')).not.toThrow(); }); });