feat(expiry): NoteRepository.trashBatch atomic (#5 v0.2.3)

This commit is contained in:
altair823
2026-05-02 00:01:03 +09:00
parent 00423fb235
commit fec80361dd
2 changed files with 71 additions and 0 deletions

View File

@@ -529,3 +529,48 @@ describe('NoteRepository.findExpiredCandidates', () => {
expect(r.map((n) => n.id)).toEqual([past]);
});
});
describe('NoteRepository.trashBatch', () => {
let db: Database.Database;
let repo: NoteRepository;
beforeEach(() => {
db = new Database(':memory:');
runMigrations(db);
repo = new NoteRepository(db);
});
it('atomically trashes all valid ids and returns trashedCount', () => {
const a = repo.create({ rawText: 'a' }).id;
const b = repo.create({ rawText: 'b' }).id;
const c = repo.create({ rawText: 'c' }).id;
const r = repo.trashBatch([a, b, c], '2026-05-01T12:00:00.000Z');
expect(r.trashedCount).toBe(3);
expect(repo.findById(a)!.deletedAt).toBe('2026-05-01T12:00:00.000Z');
expect(repo.findById(b)!.deletedAt).toBe('2026-05-01T12:00:00.000Z');
expect(repo.findById(c)!.deletedAt).toBe('2026-05-01T12:00:00.000Z');
expect(db.prepare('SELECT COUNT(*) AS c FROM pending_jobs WHERE note_id IN (?,?,?)').get(a, b, c))
.toMatchObject({ c: 0 });
});
it('returns trashedCount=0 for empty array (no-op)', () => {
const r = repo.trashBatch([], '2026-05-01T12:00:00.000Z');
expect(r.trashedCount).toBe(0);
});
it('skips ids that are already trashed (idempotent — count = 0 transitions)', () => {
const a = repo.create({ rawText: 'a' }).id;
repo.trash(a, '2026-04-30T00:00:00.000Z');
const r = repo.trashBatch([a], '2026-05-01T12:00:00.000Z');
expect(r.trashedCount).toBe(0);
expect(repo.findById(a)!.deletedAt).toBe('2026-04-30T00:00:00.000Z');
});
it('counts only the valid active ids (mix of valid + invalid + already-trashed)', () => {
const a = repo.create({ rawText: 'a' }).id;
const b = repo.create({ rawText: 'b' }).id;
repo.trash(b, '2026-04-30T00:00:00.000Z');
const r = repo.trashBatch([a, b, 'nonexistent-id'], '2026-05-01T12:00:00.000Z');
expect(r.trashedCount).toBe(1);
expect(repo.findById(a)!.deletedAt).toBe('2026-05-01T12:00:00.000Z');
});
});