- create(input, now?: Date) signature 추가 (기존 setStatus/updateRawText 패턴 정합) - NoteRevisions.test.ts 4 testcase v1 capture 시간 명시 주입 (2026-05-09T00:00:00Z) - upsertFromSync.test.ts 2 testcase v1 capture 시간 명시 주입 - 시스템 시계가 2026-05-10T00:00:00Z 초과 시 DESC ordering 깨지던 회귀 회복 backlog: time-dependent flake (Cut F audit 발견) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
99 lines
4.3 KiB
TypeScript
99 lines
4.3 KiB
TypeScript
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';
|
|
|
|
const baseInput = {
|
|
id: '00000000-0000-0000-0000-000000000001',
|
|
rawText: 'sync 본문',
|
|
createdAt: '2026-05-09T00:00:00Z',
|
|
updatedAt: '2026-05-10T00:00:00Z',
|
|
aiTitle: 'sync 제목',
|
|
aiSummary: 'sync 요약',
|
|
titleEditedByUser: false,
|
|
summaryEditedByUser: false,
|
|
aiProvider: 'p',
|
|
aiGeneratedAt: '2026-05-10T00:00:00Z',
|
|
userIntent: null,
|
|
intentPromptedAt: null,
|
|
tags: [{ name: '동기', source: 'user' as const }],
|
|
status: 'active' as const,
|
|
statusChangedAt: null,
|
|
moveReason: null,
|
|
dueDate: null,
|
|
dueDateEditedByUser: false
|
|
};
|
|
|
|
describe('NoteRepository.upsertFromSync', () => {
|
|
let db: Database.Database;
|
|
let repo: NoteRepository;
|
|
|
|
beforeEach(() => {
|
|
db = new Database(':memory:');
|
|
db.pragma('foreign_keys = ON');
|
|
runMigrations(db);
|
|
repo = new NoteRepository(db);
|
|
});
|
|
|
|
afterEach(() => { db.close(); });
|
|
|
|
it('id 없음 → INSERT (status=inserted) + capture revision + tags FTS sync', () => {
|
|
const r = repo.upsertFromSync(baseInput);
|
|
expect(r.status).toBe('inserted');
|
|
expect(r.id).toBe(baseInput.id);
|
|
const note = repo.findById(baseInput.id);
|
|
expect(note?.rawText).toBe('sync 본문');
|
|
expect(note?.aiTitle).toBe('sync 제목');
|
|
const revs = repo.listRevisions(baseInput.id);
|
|
expect(revs).toHaveLength(1);
|
|
expect(revs[0]!.editedBy).toBe('capture');
|
|
const fts = db.prepare(`SELECT tags FROM notes_fts WHERE note_id=?`).get(baseInput.id) as { tags: string };
|
|
expect(fts.tags).toBe('동기');
|
|
});
|
|
|
|
it('id 있음 + raw_text 동일 + source 더 최신 → metadata 갱신 (status=updated)', () => {
|
|
const created = repo.create({ rawText: 'sync 본문' }, new Date('2026-05-09T00:00:00Z'));
|
|
repo.updateAiResult(created.id, { title: '옛 제목', summary: '옛 요약', tags: ['old'], provider: 'p' });
|
|
db.prepare(`UPDATE notes SET updated_at=? WHERE id=?`).run('2026-05-08T00:00:00Z', created.id);
|
|
const r = repo.upsertFromSync({ ...baseInput, id: created.id });
|
|
expect(r.status).toBe('updated');
|
|
const note = repo.findById(created.id);
|
|
expect(note?.aiTitle).toBe('sync 제목');
|
|
expect(note?.tags.map((t) => t.name)).toEqual(['동기']);
|
|
const fts = db.prepare(`SELECT tags FROM notes_fts WHERE note_id=?`).get(created.id) as { tags: string };
|
|
expect(fts.tags).toBe('동기');
|
|
});
|
|
|
|
it('id 있음 + raw_text 동일 + source 더 옛 → skip (status=skipped)', () => {
|
|
const created = repo.create({ rawText: 'sync 본문' });
|
|
repo.updateAiResult(created.id, { title: '신선한 제목', summary: 'fresh', tags: ['x'], provider: 'p' });
|
|
db.prepare(`UPDATE notes SET updated_at=? WHERE id=?`).run('2026-05-12T00:00:00Z', created.id);
|
|
const r = repo.upsertFromSync({ ...baseInput, id: created.id, updatedAt: '2026-05-10T00:00:00Z' });
|
|
expect(r.status).toBe('skipped');
|
|
const note = repo.findById(created.id);
|
|
expect(note?.aiTitle).toBe('신선한 제목');
|
|
});
|
|
|
|
it('id 있음 + raw_text 다름 + source 더 최신 → updateRawText (status=updated) + new user revision', () => {
|
|
const created = repo.create({ rawText: 'old text' }, new Date('2026-05-09T00:00:00Z'));
|
|
db.prepare(`UPDATE notes SET updated_at=? WHERE id=?`).run('2026-05-08T00:00:00Z', created.id);
|
|
const r = repo.upsertFromSync({ ...baseInput, id: created.id, rawText: 'new sync text' });
|
|
expect(r.status).toBe('updated');
|
|
const note = repo.findById(created.id);
|
|
expect(note?.rawText).toBe('new sync text');
|
|
const revs = repo.listRevisions(created.id);
|
|
expect(revs).toHaveLength(2); // capture (old) + user (new)
|
|
expect(revs[0]!.editedBy).toBe('user');
|
|
expect(revs[0]!.rawText).toBe('new sync text');
|
|
});
|
|
|
|
it('id 있음 + raw_text 다름 + source 더 옛 → skip', () => {
|
|
const created = repo.create({ rawText: 'local fresh' });
|
|
db.prepare(`UPDATE notes SET updated_at=? WHERE id=?`).run('2026-05-15T00:00:00Z', created.id);
|
|
const r = repo.upsertFromSync({ ...baseInput, id: created.id, rawText: 'old sync text', updatedAt: '2026-05-10T00:00:00Z' });
|
|
expect(r.status).toBe('skipped');
|
|
const note = repo.findById(created.id);
|
|
expect(note?.rawText).toBe('local fresh');
|
|
});
|
|
});
|