143 lines
4.5 KiB
TypeScript
143 lines
4.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
const { mockQuery, mockConnect } = vi.hoisted(() => ({
|
|
mockQuery: vi.fn(),
|
|
mockConnect: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('../db/index.js', () => ({
|
|
query: mockQuery,
|
|
getPool: () => ({ connect: mockConnect }),
|
|
}));
|
|
|
|
import {
|
|
recordDelivery,
|
|
markProcessing,
|
|
markDone,
|
|
markFailed,
|
|
resetStaleProcessing,
|
|
} from '../db/ingest_events.dao.js';
|
|
|
|
describe('ingest_events.dao', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('recordDelivery', () => {
|
|
it('should return the new row id for a delivery that has not been seen', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [{ id: 42 }] });
|
|
|
|
const result = await recordDelivery({ provider: 'slack', externalId: 'evt_1' });
|
|
|
|
expect(result).toBe(42);
|
|
});
|
|
|
|
it('should return null when the delivery conflicts with an existing row', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [] });
|
|
|
|
const result = await recordDelivery({ provider: 'slack', externalId: 'evt_1' });
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should insert with ON CONFLICT DO NOTHING on the provider and external id', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [{ id: 1 }] });
|
|
|
|
await recordDelivery({ provider: 'linear', externalId: 'evt_2' });
|
|
|
|
const [sql] = mockQuery.mock.calls[0] as [string, unknown[]];
|
|
expect(sql).toContain('INSERT INTO ingest_events');
|
|
expect(sql).toContain('ON CONFLICT (provider, external_id) DO NOTHING');
|
|
expect(sql).toContain('RETURNING id');
|
|
});
|
|
|
|
it('should bind the provider, external id and integration id', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [{ id: 7 }] });
|
|
|
|
await recordDelivery({ provider: 'github', externalId: 'evt_3', integrationId: 12 });
|
|
|
|
const [, params] = mockQuery.mock.calls[0] as [string, unknown[]];
|
|
expect(params).toEqual(['github', 'evt_3', 12]);
|
|
});
|
|
|
|
it('should bind null when no integration id is supplied', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [{ id: 8 }] });
|
|
|
|
await recordDelivery({ provider: 'rest', externalId: 'evt_4' });
|
|
|
|
const [, params] = mockQuery.mock.calls[0] as [string, unknown[]];
|
|
expect(params[2]).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('markProcessing', () => {
|
|
it('should set the status to processing and increment attempts', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [] });
|
|
|
|
await markProcessing(5);
|
|
|
|
const [sql, params] = mockQuery.mock.calls[0] as [string, unknown[]];
|
|
expect(sql).toContain("status = 'processing'");
|
|
expect(sql).toContain('attempts = attempts + 1');
|
|
expect(params).toEqual([5]);
|
|
});
|
|
});
|
|
|
|
describe('markDone', () => {
|
|
it('should set the status to done and clear the last error', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [] });
|
|
|
|
await markDone(9);
|
|
|
|
const [sql, params] = mockQuery.mock.calls[0] as [string, unknown[]];
|
|
expect(sql).toContain("status = 'done'");
|
|
expect(sql).toContain('last_error = NULL');
|
|
expect(params).toEqual([9]);
|
|
});
|
|
});
|
|
|
|
describe('markFailed', () => {
|
|
it('should store the failure message against the row', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [] });
|
|
|
|
await markFailed(3, 'normalizer threw');
|
|
|
|
const [sql, params] = mockQuery.mock.calls[0] as [string, unknown[]];
|
|
expect(sql).toContain("status = 'failed'");
|
|
expect(sql).toContain('last_error = $2');
|
|
expect(params).toEqual([3, 'normalizer threw']);
|
|
});
|
|
|
|
it('should truncate an over-long error to 2000 characters', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [] });
|
|
|
|
await markFailed(3, 'x'.repeat(5000));
|
|
|
|
const [, params] = mockQuery.mock.calls[0] as [string, unknown[]];
|
|
expect(params[1]).toBe('x'.repeat(2000));
|
|
});
|
|
});
|
|
|
|
describe('resetStaleProcessing', () => {
|
|
it('should return the number of rows returned to pending', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [], rowCount: 4 });
|
|
|
|
const result = await resetStaleProcessing(30_000);
|
|
|
|
expect(result).toBe(4);
|
|
const [sql, params] = mockQuery.mock.calls[0] as [string, unknown[]];
|
|
expect(sql).toContain("SET status = 'pending'");
|
|
expect(sql).toContain("status = 'processing'");
|
|
expect(params).toEqual(['30000']);
|
|
});
|
|
|
|
it('should return 0 when the driver reports a null row count', async () => {
|
|
mockQuery.mockResolvedValue({ rows: [], rowCount: null });
|
|
|
|
const result = await resetStaleProcessing(30_000);
|
|
|
|
expect(result).toBe(0);
|
|
});
|
|
});
|
|
});
|