feat(retry): NoteRepository — findFailedIds/countFailed/retryAllFailed/setNextRunAt (#2 v0.2.3)
This commit is contained in:
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user