fix(v026): PR #24 round 1 Critical — B1 production path activation
Round 1 reviewer 발견: B1 (#10) fix 가 dead code. NoteRepository.restoreNote 새 메서드는 unit test 만 호출, production path (CaptureService.restoreNote) 는 옛 repo.restore() 호출 → ai_status reset + pending_jobs INSERT 우회. Fix: - CaptureService.restoreNote 가 repo.restoreNote 호출 - before 의 ai_status 가 'failed' or 'pending' 이면 worker.enqueue(id) 도 호출 (in-memory queue 갱신 — restoreNote 가 DB 만 갱신하면 다음 app start 까지 처리 안 됨) Round 1 Important 도 함께 처리. 단위 +2 cases (failed → enqueue, done → skip enqueue). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -88,9 +88,14 @@ export class CaptureService {
|
||||
|
||||
async restoreNote(noteId: string): Promise<void> {
|
||||
// 이미 active 인 노트는 telemetry emit skip — restore/trash ratio 오염 방지.
|
||||
const note = this.repo.findById(noteId);
|
||||
if (!note || note.deletedAt === null) return;
|
||||
this.repo.restore(noteId);
|
||||
const before = this.repo.findById(noteId);
|
||||
if (!before || before.deletedAt === null) return;
|
||||
// v0.2.6 #10 — production path: repo.restoreNote (ai_status reset + pending_jobs 재생성)
|
||||
this.repo.restoreNote(noteId);
|
||||
// v0.2.6 #10 — in-memory AiWorker queue 갱신: DB 갱신만으로는 다음 앱 실행 시까지 처리 X
|
||||
if (before.aiStatus === 'failed' || before.aiStatus === 'pending') {
|
||||
await this.deps.enqueue(noteId);
|
||||
}
|
||||
if (this.deps.telemetry) {
|
||||
await this.deps.telemetry.emit({ kind: 'restore', payload: { noteId } }).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -324,6 +324,52 @@ describe('CaptureService.trashExpiredBatch', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('CaptureService.restoreNote — enqueue on failed/pending (#10 production path)', () => {
|
||||
let db: Database.Database;
|
||||
let repo: NoteRepository;
|
||||
let store: MediaStore;
|
||||
let tmp: string;
|
||||
let enqueued: string[];
|
||||
let svc: CaptureService;
|
||||
|
||||
beforeEach(() => {
|
||||
db = new Database(':memory:');
|
||||
runMigrations(db);
|
||||
repo = new NoteRepository(db);
|
||||
tmp = mkdtempSync(join(tmpdir(), 'inkling-restore-'));
|
||||
store = new MediaStore(tmp);
|
||||
enqueued = [];
|
||||
svc = new CaptureService(repo, store, {
|
||||
enqueue: async (id) => { enqueued.push(id); },
|
||||
celebrate: () => {}
|
||||
});
|
||||
});
|
||||
|
||||
it('restoreNote calls worker.enqueue when restoring failed note', async () => {
|
||||
const { id } = repo.create({ rawText: 'x' });
|
||||
repo.markAiFailed(id, 'unreachable');
|
||||
repo.trash(id, new Date().toISOString());
|
||||
enqueued.length = 0; // reset
|
||||
|
||||
await svc.restoreNote(id);
|
||||
|
||||
expect(repo.findById(id)!.aiStatus).toBe('pending');
|
||||
expect(enqueued).toContain(id);
|
||||
});
|
||||
|
||||
it('restoreNote does not enqueue done note', async () => {
|
||||
const { id } = repo.create({ rawText: 'x' });
|
||||
repo.updateAiResult(id, { title: 't', summary: 'a\nb\nc', tags: ['x'], provider: 'p' });
|
||||
repo.trash(id, new Date().toISOString());
|
||||
enqueued.length = 0; // reset
|
||||
|
||||
await svc.restoreNote(id);
|
||||
|
||||
expect(repo.findById(id)!.aiStatus).toBe('done');
|
||||
expect(enqueued).not.toContain(id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CaptureService.retryAllFailed', () => {
|
||||
let db: Database.Database;
|
||||
let repo: NoteRepository;
|
||||
|
||||
Reference in New Issue
Block a user