import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import Database from 'better-sqlite3'; import { runMigrations } from '../../src/main/db/migrations/index.js'; import { NotebookRepository } from '../../src/main/repository/NotebookRepository.js'; describe('NotebookRepository', () => { let db: Database.Database; let repo: NotebookRepository; beforeEach(() => { db = new Database(':memory:'); db.pragma('foreign_keys = ON'); runMigrations(db); repo = new NotebookRepository(db); }); afterEach(() => { db.close(); }); it('list: 기본 notebook 1개 + noteCount 0', () => { const all = repo.list(); expect(all).toHaveLength(1); expect(all[0]!.name).toBe('기본'); expect(all[0]!.noteCount).toBe(0); }); it('create: 새 notebook 추가', () => { const nb = repo.create({ name: '회사', color: '#0a4b80' }); expect(nb.name).toBe('회사'); expect(nb.color).toBe('#0a4b80'); expect(repo.list()).toHaveLength(2); }); it('create: 같은 이름 두 번이면 throw', () => { repo.create({ name: '회사' }); expect(() => repo.create({ name: '회사' })).toThrow(); }); it('rename: 이름 변경', () => { const nb = repo.create({ name: '회사' }); repo.rename(nb.id, '워크'); const after = repo.findById(nb.id); expect(after?.name).toBe('워크'); }); it('delete: 메모 없으면 OK', () => { const nb = repo.create({ name: '회사' }); const r = repo.delete(nb.id); expect(r.ok).toBe(true); expect(repo.findById(nb.id)).toBeNull(); }); it('delete: 메모 있으면 RESTRICT — ok:false', () => { const nb = repo.create({ name: '회사' }); const ts = '2026-05-14T00:00:00Z'; db.prepare( `INSERT INTO notes(id, raw_text, ai_status, created_at, updated_at, status, notebook_id) VALUES('n1','t','pending',?,?,'active',?)` ).run(ts, ts, nb.id); const r = repo.delete(nb.id); expect(r.ok).toBe(false); if (!r.ok) expect(r.reason).toBe('has_notes'); }); it('noteCount: status="active" 만 카운트 (completed/trashed 제외)', () => { const nb = repo.create({ name: '회사' }); const ts = '2026-05-14T00:00:00Z'; const insert = db.prepare( `INSERT INTO notes(id, raw_text, ai_status, created_at, updated_at, status, notebook_id) VALUES(?,?,?,?,?,?,?)` ); insert.run('n1','t','done',ts,ts,'active',nb.id); insert.run('n2','t','done',ts,ts,'completed',nb.id); insert.run('n3','t','done',ts,ts,'trashed',nb.id); const found = repo.findById(nb.id); expect(found?.noteCount).toBe(1); }); it('moveNote: notebook_id 갱신', () => { const nb = repo.create({ name: '회사' }); const ts = '2026-05-14T00:00:00Z'; const defaultId = repo.list().find((n) => n.name === '기본')!.id; db.prepare( `INSERT INTO notes(id, raw_text, ai_status, created_at, updated_at, status, notebook_id) VALUES('n1','t','pending',?,?,'active',?)` ).run(ts, ts, defaultId); repo.moveNote('n1', nb.id); const r = db.prepare(`SELECT notebook_id FROM notes WHERE id='n1'`).get() as { notebook_id: string }; expect(r.notebook_id).toBe(nb.id); }); it('setColor: 색 변경', () => { const nb = repo.create({ name: '회사', color: '#000' }); repo.setColor(nb.id, '#fff'); expect(repo.findById(nb.id)?.color).toBe('#fff'); }); it('delete: 존재하지 않는 id → ok:false reason="not_found"', () => { const r = repo.delete('does-not-exist'); expect(r.ok).toBe(false); if (!r.ok) expect(r.reason).toBe('not_found'); }); it('findByName: 이름으로 조회', () => { const nb = repo.create({ name: '회사' }); const found = repo.findByName('회사'); expect(found?.id).toBe(nb.id); }); it('findByName: case-insensitive', () => { repo.create({ name: 'Work' }); expect(repo.findByName('work')?.name).toBe('Work'); }); it('findByName: 없으면 null', () => { expect(repo.findByName('없음')).toBeNull(); }); });