Stand up the HTTP surface Prospector calls (this app had no listening port). Bun
service: bearer-auth (MRNUMBER_SERVICE_TOKEN, constant-time) POST /api/screening/requests
{phone, ref} → 202 {accepted, id}, enqueued in a durable SQLite queue; a single serial
worker (one Android box) drains by invoking mr_lookup.py and records the people-service
signal. GET /api/screening/requests/:id returns the row; GET /health is open. Crash-safe
(requeues stale in-flight rows on restart). 7 bun tests + typecheck; smoke-tested end to
end (auth 401, enqueue 202, drain→verdict, invalid-phone 400).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { Database } from 'bun:sqlite';
|
|
|
|
import { ScreeningQueue } from './queue.ts';
|
|
import { drainOnce, type LookupRunner } from './worker.ts';
|
|
|
|
function freshQueue(): ScreeningQueue {
|
|
return new ScreeningQueue(new Database(':memory:'));
|
|
}
|
|
|
|
describe('ScreeningQueue', () => {
|
|
test('enqueue → claimNext is FIFO and flips status to running', () => {
|
|
const q = freshQueue();
|
|
const a = q.enqueue('+15551112222', 'ref-a');
|
|
q.enqueue('+15553334444', null);
|
|
expect(q.pendingCount()).toBe(2);
|
|
|
|
const first = q.claimNext();
|
|
expect(first?.id).toBe(a);
|
|
expect(first?.phone).toBe('+15551112222');
|
|
expect(first?.ref).toBe('ref-a');
|
|
expect(first?.status).toBe('running');
|
|
expect(q.pendingCount()).toBe(1);
|
|
});
|
|
|
|
test('claimNext returns null when nothing pending', () => {
|
|
const q = freshQueue();
|
|
expect(q.claimNext()).toBeNull();
|
|
});
|
|
|
|
test('complete and fail record terminal state', () => {
|
|
const q = freshQueue();
|
|
const id = q.enqueue('+15551112222', null);
|
|
q.claimNext();
|
|
q.complete(id, 'denied');
|
|
const row = q.get(id);
|
|
expect(row?.status).toBe('done');
|
|
expect(row?.verdict).toBe('denied');
|
|
expect(row?.finished_at).not.toBeNull();
|
|
});
|
|
|
|
test('requeueStale recovers a crashed in-flight row', () => {
|
|
const q = freshQueue();
|
|
const id = q.enqueue('+15551112222', null);
|
|
q.claimNext(); // now 'running'
|
|
expect(q.pendingCount()).toBe(0);
|
|
expect(q.requeueStale()).toBe(1);
|
|
expect(q.pendingCount()).toBe(1);
|
|
expect(q.claimNext()?.id).toBe(id);
|
|
});
|
|
});
|
|
|
|
describe('drainOnce', () => {
|
|
test('records the runner verdict on success', async () => {
|
|
const q = freshQueue();
|
|
const id = q.enqueue('+15551112222', 'r');
|
|
const runner: LookupRunner = async (phone) => (phone === '+15551112222' ? 'cop_flag' : null);
|
|
expect(await drainOnce(q, runner)).toBe(true);
|
|
expect(q.get(id)?.status).toBe('done');
|
|
expect(q.get(id)?.verdict).toBe('cop_flag');
|
|
});
|
|
|
|
test('captures the error and marks the row failed', async () => {
|
|
const q = freshQueue();
|
|
const id = q.enqueue('+15551112222', null);
|
|
const runner: LookupRunner = async () => {
|
|
throw new Error('adb offline');
|
|
};
|
|
expect(await drainOnce(q, runner)).toBe(true);
|
|
expect(q.get(id)?.status).toBe('error');
|
|
expect(q.get(id)?.error).toContain('adb offline');
|
|
});
|
|
|
|
test('returns false when the queue is empty', async () => {
|
|
const q = freshQueue();
|
|
expect(await drainOnce(q, async () => 'denied')).toBe(false);
|
|
});
|
|
});
|