fix(v026): #10 restoreNote 가 failed 노트 시 pending_jobs 재생성

restore 가 deleted_at = NULL 만 했음 → ai_status='failed' 인 노트는
영구 fail 상태로 복구. atomic transaction 안에서 ai_status='pending' reset
+ INSERT OR IGNORE INTO pending_jobs.

- failed → pending + pending_jobs 재처리 path 복구
- done 은 영향 X (이미 결과 있음)
- pending 은 pending_jobs 재생성 (defensive — trash 도중 jobs 미정상 상태 가능)
- 단위 +3 cases (failed/done/pending 각 케이스)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
altair823
2026-05-05 01:15:23 +09:00
parent 6fdb72101f
commit df27a9637e
2 changed files with 68 additions and 0 deletions

View File

@@ -267,6 +267,49 @@ describe('NoteRepository', () => {
repo.updateAiResult(d, { title: 't', summary: 'a\nb\nc', tags: ['x'], dueDate: todayKst, provider: 'p' });
expect(repo.findRecallCandidate()?.id).toBe(d);
});
it('restoreNote re-enqueues failed note (ai_status reset to pending + pending_jobs INSERT)', () => {
const id = repo.create({ rawText: 'x' }).id;
repo.markAiFailed(id, 'unreachable');
repo.trash(id, new Date().toISOString());
expect(repo.findById(id)!.aiStatus).toBe('failed');
repo.restoreNote(id);
const after = repo.findById(id)!;
expect(after.deletedAt).toBeNull();
expect(after.aiStatus).toBe('pending');
expect(after.aiError).toBeNull();
const job = db.prepare('SELECT * FROM pending_jobs WHERE note_id=?').get(id);
expect(job).toBeDefined();
});
it('restoreNote does not re-enqueue done note', () => {
const id = repo.create({ rawText: 'x' }).id;
repo.updateAiResult(id, { title: 't', summary: 'a\nb\nc', tags: ['x'], provider: 'p' });
repo.trash(id, new Date().toISOString());
expect(repo.findById(id)!.aiStatus).toBe('done');
repo.restoreNote(id);
expect(repo.findById(id)!.aiStatus).toBe('done');
const job = db.prepare('SELECT * FROM pending_jobs WHERE note_id=?').get(id);
expect(job).toBeUndefined();
});
it('restoreNote re-enqueues pending note (defensive)', () => {
const id = repo.create({ rawText: 'x' }).id;
// 인공적으로 pending_jobs 비운 후 trash
db.prepare('DELETE FROM pending_jobs WHERE note_id=?').run(id);
repo.trash(id, new Date().toISOString());
expect(repo.findById(id)!.aiStatus).toBe('pending');
repo.restoreNote(id);
expect(repo.findById(id)!.aiStatus).toBe('pending');
const job = db.prepare('SELECT * FROM pending_jobs WHERE note_id=?').get(id);
expect(job).toBeDefined();
});
});
describe('NoteRepository.trash', () => {