feat(retry): NoteRepository — findFailedIds/countFailed/retryAllFailed/setNextRunAt (#2 v0.2.3)

This commit is contained in:
altair823
2026-05-02 03:15:05 +09:00
parent 821db4001d
commit 2e3f0edffd
2 changed files with 123 additions and 0 deletions

View File

@@ -574,3 +574,67 @@ describe('NoteRepository.trashBatch', () => {
expect(repo.findById(a)!.deletedAt).toBe('2026-05-01T12:00:00.000Z');
});
});
describe('NoteRepository — failed retry helpers', () => {
let db: Database.Database;
let repo: NoteRepository;
beforeEach(() => {
db = new Database(':memory:');
runMigrations(db);
repo = new NoteRepository(db);
});
function makeFailed(rawText: string, deletedAt: string | null = null): string {
const { id } = repo.create({ rawText });
db.prepare(
`UPDATE notes SET ai_status='failed', ai_error='boom', deleted_at=? WHERE id=?`
).run(deletedAt, id);
db.prepare(`DELETE FROM pending_jobs WHERE note_id=?`).run(id);
return id;
}
it('findFailedIds returns ai_status=failed AND deleted_at IS NULL only', () => {
const a = makeFailed('a');
makeFailed('b', '2026-04-30T00:00:00Z'); // trashed
repo.create({ rawText: 'pending' }); // pending status
expect(repo.findFailedIds().sort()).toEqual([a].sort());
});
it('countFailed counts active failed notes only', () => {
makeFailed('a');
makeFailed('b');
makeFailed('c', '2026-04-30T00:00:00Z');
expect(repo.countFailed()).toBe(2);
});
it('retryAllFailed atomic — ai_status reset + pending_jobs 재투입', () => {
const a = makeFailed('a');
const b = makeFailed('b');
const r = repo.retryAllFailed('2026-05-01T12:00:00.000Z');
expect(r.ids.sort()).toEqual([a, b].sort());
expect(repo.findById(a)!.aiStatus).toBe('pending');
expect(repo.findById(b)!.aiStatus).toBe('pending');
expect(repo.findById(a)!.aiError).toBeNull();
const jobs = repo.getAllPendingJobs();
expect(jobs.map((j) => j.noteId).sort()).toEqual([a, b].sort());
for (const j of jobs) {
expect(j.attempts).toBe(0);
expect(j.nextRunAt).toBe('2026-05-01T12:00:00.000Z');
}
});
it('retryAllFailed empty — { ids: [] }', () => {
expect(repo.retryAllFailed('2026-05-01T12:00:00.000Z')).toEqual({ ids: [] });
});
it('setNextRunAt — attempts 변경 없이 next_run_at + last_error 갱신', () => {
const { id } = repo.create({ rawText: 'x' });
repo.incrementJobAttempt(id, '2026-05-01T11:00:00.000Z', 'first error');
// attempts 가 1 이 됨
repo.setNextRunAt(id, '2026-05-01T12:00:00.000Z', 'unreachable');
const job = repo.getAllPendingJobs().find((j) => j.noteId === id)!;
expect(job.attempts).toBe(1); // 변화 없음
expect(job.nextRunAt).toBe('2026-05-01T12:00:00.000Z');
});
});