Files
inkling/tests/unit/IntentService.test.ts
altair823 dc88da0bee feat(intent): IntentService for set/dismiss with validation
Task 20 of the slice plan. Thin wrapper over the repo's intent
methods that adds two preconditions: setIntent rejects empty
trimmed text, both methods throw "note not found" when the
note id doesn't exist. Repo-level COALESCE on
intent_prompted_at preserves the first-prompt invariant
(spec §3.3); IntentService's job is just input guarding so
the IPC handler stays a one-liner.

Verification: `npx vitest run tests/unit/IntentService.test.ts`
4 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:14:31 +09:00

43 lines
1.4 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest';
import Database from 'better-sqlite3';
import { runMigrations } from '@main/db/migrations/index.js';
import { NoteRepository } from '@main/repository/NoteRepository.js';
import { IntentService } from '@main/services/IntentService.js';
describe('IntentService', () => {
let db: Database.Database;
let repo: NoteRepository;
let svc: IntentService;
beforeEach(() => {
db = new Database(':memory:');
runMigrations(db);
repo = new NoteRepository(db);
svc = new IntentService(repo);
});
it('setIntent stores text on note', () => {
const { id } = repo.create({ rawText: 'x' });
svc.setIntent(id, '내일의 나에게');
expect(repo.findById(id)?.userIntent).toBe('내일의 나에게');
});
it('dismissIntent stamps prompted_at without setting intent', () => {
const { id } = repo.create({ rawText: 'x' });
svc.dismissIntent(id);
const note = repo.findById(id)!;
expect(note.userIntent).toBeNull();
expect(note.intentPromptedAt).not.toBeNull();
});
it('rejects empty intent text', () => {
const { id } = repo.create({ rawText: 'x' });
expect(() => svc.setIntent(id, ' ')).toThrow(/empty/i);
});
it('throws if note does not exist', () => {
expect(() => svc.setIntent('nonexistent', 'x')).toThrow(/not found/i);
expect(() => svc.dismissIntent('nonexistent')).toThrow(/not found/i);
});
});