From 36eafa1ce9747e819918a56ebebe28b2634bb948 Mon Sep 17 00:00:00 2001 From: altair823 Date: Sun, 10 May 2026 13:45:37 +0900 Subject: [PATCH] fix(v032): NoteRepository.create now param + time-dep test flake fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/main/repository/NoteRepository.ts | 10 +++++----- tests/unit/NoteRepository.test.ts | 18 ++++++++++++++++++ .../unit/NoteRepository.upsertFromSync.test.ts | 4 ++-- tests/unit/NoteRevisions.test.ts | 15 ++++++++++----- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/main/repository/NoteRepository.ts b/src/main/repository/NoteRepository.ts index f6959bd..e74cbd1 100644 --- a/src/main/repository/NoteRepository.ts +++ b/src/main/repository/NoteRepository.ts @@ -79,25 +79,25 @@ const KEBAB_CASE_RE = /^[a-z0-9-]+$/; export class NoteRepository { constructor(private db: Database.Database) {} - create(input: CreateNoteInput): { id: string } { + create(input: CreateNoteInput, now: Date = new Date()): { id: string } { const id = uuidv7(); - const now = new Date().toISOString(); + const ts = now.toISOString(); const aiStatus: AiStatus = input.aiStatus ?? 'pending'; const tx = this.db.transaction(() => { this.db .prepare(`INSERT INTO notes (id, raw_text, ai_status, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`) - .run(id, input.rawText, aiStatus, now, now); + .run(id, input.rawText, aiStatus, ts, ts); this.db .prepare(`INSERT INTO note_revisions (note_id, raw_text, edited_at, edited_by) VALUES (?, ?, ?, 'capture')`) - .run(id, input.rawText, now); + .run(id, input.rawText, ts); // pending_jobs 는 'pending' 일 때만 생성 — 'disabled' 노트는 worker 가 처리 안 함. if (aiStatus === 'pending') { this.db .prepare(`INSERT INTO pending_jobs (note_id, attempts, next_run_at) VALUES (?, 0, ?)`) - .run(id, now); + .run(id, ts); } }); tx(); diff --git a/tests/unit/NoteRepository.test.ts b/tests/unit/NoteRepository.test.ts index 82002ff..2542e68 100644 --- a/tests/unit/NoteRepository.test.ts +++ b/tests/unit/NoteRepository.test.ts @@ -310,6 +310,24 @@ describe('NoteRepository', () => { const job = db.prepare('SELECT * FROM pending_jobs WHERE note_id=?').get(id); expect(job).toBeDefined(); }); + + it('create accepts explicit now param', () => { + const fixed = new Date('2026-05-09T10:00:00.000Z'); + const { id } = repo.create({ rawText: 'hello' }, fixed); + const note = repo.findById(id)!; + expect(note.createdAt).toBe('2026-05-09T10:00:00.000Z'); + expect(note.updatedAt).toBe('2026-05-09T10:00:00.000Z'); + }); + + it('create defaults now to new Date when omitted', () => { + const before = Date.now(); + const { id } = repo.create({ rawText: 'hello' }); + const after = Date.now(); + const note = repo.findById(id)!; + const ts = new Date(note.createdAt).getTime(); + expect(ts).toBeGreaterThanOrEqual(before); + expect(ts).toBeLessThanOrEqual(after); + }); }); describe('NoteRepository.trash', () => { diff --git a/tests/unit/NoteRepository.upsertFromSync.test.ts b/tests/unit/NoteRepository.upsertFromSync.test.ts index 4500dad..8a7e8ae 100644 --- a/tests/unit/NoteRepository.upsertFromSync.test.ts +++ b/tests/unit/NoteRepository.upsertFromSync.test.ts @@ -52,7 +52,7 @@ describe('NoteRepository.upsertFromSync', () => { }); it('id 있음 + raw_text 동일 + source 더 최신 → metadata 갱신 (status=updated)', () => { - const created = repo.create({ rawText: 'sync 본문' }); + 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 }); @@ -75,7 +75,7 @@ describe('NoteRepository.upsertFromSync', () => { }); it('id 있음 + raw_text 다름 + source 더 최신 → updateRawText (status=updated) + new user revision', () => { - const created = repo.create({ rawText: 'old text' }); + 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'); diff --git a/tests/unit/NoteRevisions.test.ts b/tests/unit/NoteRevisions.test.ts index 44c3bae..d47273f 100644 --- a/tests/unit/NoteRevisions.test.ts +++ b/tests/unit/NoteRevisions.test.ts @@ -18,7 +18,8 @@ describe('NoteRepository — note_revisions', () => { describe('updateRawText', () => { it('notes.raw_text 갱신 + 새 user revision INSERT (single transaction)', () => { - const { id } = repo.create({ rawText: 'v1' }); + const v1At = new Date('2026-05-09T00:00:00Z'); + const { id } = repo.create({ rawText: 'v1' }, v1At); const t = new Date('2026-05-10T00:00:00Z'); repo.updateRawText(id, 'v2', t); @@ -41,7 +42,8 @@ describe('NoteRepository — note_revisions', () => { }); it('atomic: 두 번 호출 시 두 revision 모두 누적 (chain history)', () => { - const { id } = repo.create({ rawText: 'v1' }); + const v1At = new Date('2026-05-09T00:00:00Z'); + const { id } = repo.create({ rawText: 'v1' }, v1At); repo.updateRawText(id, 'v2', new Date('2026-05-10T00:00:00Z')); repo.updateRawText(id, 'v3', new Date('2026-05-11T00:00:00Z')); const revs = db @@ -53,7 +55,8 @@ describe('NoteRepository — note_revisions', () => { describe('listRevisions', () => { it('DESC 순서 + edited_by + camelCase hydrate', () => { - const { id } = repo.create({ rawText: 'v1' }); + const v1At = new Date('2026-05-09T00:00:00Z'); + const { id } = repo.create({ rawText: 'v1' }, v1At); repo.updateRawText(id, 'v2', new Date('2026-05-10T00:00:00Z')); repo.updateRawText(id, 'v3', new Date('2026-05-11T00:00:00Z')); @@ -73,7 +76,8 @@ describe('NoteRepository — note_revisions', () => { describe('restoreRevision', () => { it('옛 raw_text 를 새 user revision 으로 INSERT + notes.raw_text 갱신', () => { - const { id } = repo.create({ rawText: 'v1' }); + const v1At = new Date('2026-05-09T00:00:00Z'); + const { id } = repo.create({ rawText: 'v1' }, v1At); repo.updateRawText(id, 'v2', new Date('2026-05-10T00:00:00Z')); repo.updateRawText(id, 'v3', new Date('2026-05-11T00:00:00Z')); @@ -101,7 +105,8 @@ describe('NoteRepository — note_revisions', () => { describe('AiWorker source 회귀', () => { it('updateRawText 후 findById 가 latest raw_text 반환 (옛 revision 미노출)', () => { - const { id } = repo.create({ rawText: 'v1' }); + const v1At = new Date('2026-05-09T00:00:00Z'); + const { id } = repo.create({ rawText: 'v1' }, v1At); repo.updateRawText(id, 'v2 corrected', new Date('2026-05-10T00:00:00Z')); const note = repo.findById(id); expect(note?.rawText).toBe('v2 corrected');