@accounter/server
Version:
Accounter GraphQL server
134 lines • 6.85 kB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { EmailIngestionControlProvider } from '../providers/email-ingestion-control.provider.js';
import { IngestReasonCode } from '../contracts.js';
// ---------------------------------------------------------------------------
// Mock helpers
// ---------------------------------------------------------------------------
const NOW = new Date('2026-06-11T12:00:00.000Z');
const FUTURE = new Date(NOW.getTime() + 5 * 60 * 1000); // +5 min
const PAST = new Date(NOW.getTime() - 1); // 1 ms ago — expired
const baseGrant = {
id: 'row-uuid',
jti: 'jti-uuid',
owner_id: 'tenant-uuid',
message_id: 'msg-abc-123',
raw_message_hash: 'sha256-abc',
action: 'ingest',
expires_at: FUTURE,
consumed_at: null,
business_id: null,
};
const baseInput = {
jti: 'jti-uuid',
tenantId: 'tenant-uuid',
messageId: 'msg-abc-123',
rawMessageHash: 'sha256-abc',
};
function isControlStatement(text) {
return (text === 'BEGIN' || text === 'COMMIT' || text === 'ROLLBACK' || text.includes('set_config'));
}
function makeProvider(grantRows = [baseGrant], consumeRows = [{ id: 'row-uuid' }]) {
// validateAndConsumeGrant runs on a pooled client inside a transaction. Serve
// BEGIN / SET LOCAL / COMMIT / ROLLBACK transparently and dispense the lookup
// then the consume response (in order) for the actual data queries.
// `dataQueries` records the data-query SQL so tests can assert how many ran.
const responses = [
{ rows: grantRows, rowCount: grantRows.length },
{ rows: consumeRows, rowCount: consumeRows.length },
];
const dataQueries = [];
const queryFn = vi.fn((text) => {
const sqlText = typeof text === 'string' ? text : '';
if (isControlStatement(sqlText)) {
return Promise.resolve({ rows: [], rowCount: 0 });
}
dataQueries.push(sqlText);
return Promise.resolve(responses.shift() ?? { rows: [], rowCount: 0 });
});
const release = vi.fn();
const connect = vi.fn().mockResolvedValue({ query: queryFn, release });
const pool = { query: queryFn, connect };
return {
provider: new EmailIngestionControlProvider({ pool }),
queryFn,
dataQueries,
};
}
// ---------------------------------------------------------------------------
// validateAndConsumeGrant
// ---------------------------------------------------------------------------
describe('EmailIngestionControlProvider.validateAndConsumeGrant', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(NOW);
});
afterEach(() => {
vi.useRealTimers();
});
it('returns valid=true and grant data on successful consume', async () => {
const { provider } = makeProvider();
const result = await provider.validateAndConsumeGrant(baseInput);
expect(result).toMatchObject({
valid: true,
grant: expect.objectContaining({ jti: 'jti-uuid', tenantId: 'tenant-uuid', action: 'ingest' }),
});
});
it('surfaces the grant business_id onto the validated grant', async () => {
const withBusiness = { ...baseGrant, business_id: 'business-uuid-x' };
const { provider } = makeProvider([withBusiness], [{ id: 'row-uuid' }]);
const result = await provider.validateAndConsumeGrant(baseInput);
expect(result).toMatchObject({ valid: true, grant: { businessId: 'business-uuid-x' } });
});
it('returns GRANT_INVALID when jti is not found', async () => {
const { provider } = makeProvider([], []);
const result = await provider.validateAndConsumeGrant(baseInput);
expect(result).toEqual({ valid: false, reason: IngestReasonCode.GRANT_INVALID });
});
it('returns GRANT_INVALID when grant is already consumed', async () => {
const consumed = { ...baseGrant, consumed_at: new Date(NOW.getTime() - 60_000) };
const { provider } = makeProvider([consumed], []);
const result = await provider.validateAndConsumeGrant(baseInput);
expect(result).toEqual({ valid: false, reason: IngestReasonCode.GRANT_INVALID });
});
it('returns GRANT_INVALID when grant is expired', async () => {
const expired = { ...baseGrant, expires_at: PAST };
const { provider } = makeProvider([expired], []);
const result = await provider.validateAndConsumeGrant(baseInput);
expect(result).toEqual({ valid: false, reason: IngestReasonCode.GRANT_INVALID });
});
it('returns TENANT_MISMATCH when owner_id does not match tenantId', async () => {
const { provider } = makeProvider();
const result = await provider.validateAndConsumeGrant({ ...baseInput, tenantId: 'other-tenant' });
expect(result).toEqual({ valid: false, reason: IngestReasonCode.TENANT_MISMATCH });
});
it('returns GRANT_INVALID when message_id does not match', async () => {
const { provider } = makeProvider();
const result = await provider.validateAndConsumeGrant({ ...baseInput, messageId: 'wrong-msg' });
expect(result).toEqual({ valid: false, reason: IngestReasonCode.GRANT_INVALID });
});
it('returns GRANT_INVALID when raw_message_hash does not match', async () => {
const { provider } = makeProvider();
const result = await provider.validateAndConsumeGrant({ ...baseInput, rawMessageHash: 'wrong-hash' });
expect(result).toEqual({ valid: false, reason: IngestReasonCode.GRANT_INVALID });
});
it('returns GRANT_INVALID when action is not ingest', async () => {
const wrongAction = { ...baseGrant, action: 'other' };
const { provider } = makeProvider([wrongAction], []);
const result = await provider.validateAndConsumeGrant(baseInput);
expect(result).toEqual({ valid: false, reason: IngestReasonCode.GRANT_INVALID });
});
it('returns GRANT_INVALID when consume UPDATE returns no rows (race condition)', async () => {
// grant lookup succeeds but consume returns 0 rows (another request consumed it first)
const { provider } = makeProvider([baseGrant], []);
const result = await provider.validateAndConsumeGrant(baseInput);
expect(result).toEqual({ valid: false, reason: IngestReasonCode.GRANT_INVALID });
});
it('does not call consume when validation fails', async () => {
const { provider, dataQueries } = makeProvider([], []);
await provider.validateAndConsumeGrant(baseInput);
// only one data query (the lookup) — no consume attempt (BEGIN/SET LOCAL/
// COMMIT are transparent transaction control, not data queries)
expect(dataQueries).toHaveLength(1);
});
});
//# sourceMappingURL=email-ingestion-grant-validation.provider.test.js.map