wulf-pulse/lib/services/pax8-company-match-resolver.test.ts
lorentz afdcf1412d test(14-02): add failing test for resolvePax8CompanyMatch
- Five behavior cases: success (both writes), not_found, already_resolved,
  company_not_found, and non-candidate companyId still resolves (D-05/D-09)
- Hand-rolled mock tx asserts SQL + bound params per query call
2026-07-11 14:28:44 -04:00

162 lines
5.3 KiB
TypeScript

/**
* pax8-company-match-resolver.ts unit tests.
*
* The resolver takes `tx` as a parameter (it runs inside
* postgresClient.transaction() at the call site), so no module mock is
* needed here — a hand-rolled mock tx `{ query: vi.fn() }` is scripted per
* test to return the sequenced results, and assertions check the SQL
* strings + bound params of each `query` call plus the returned
* ResolveResult, matching the mocking discipline of
* pax8-company-matcher.test.ts.
*/
import { describe, it, expect, vi } from 'vitest';
import { resolvePax8CompanyMatch } from './pax8-company-match-resolver';
interface MockCall {
sql: string;
params: unknown[];
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type QueryMock = ReturnType<typeof vi.fn<(...args: any[]) => Promise<{ rows: unknown[]; rowCount: number }>>>;
function makeTx(queryMock: QueryMock) {
return {
query: <T = unknown>(sql: string, params?: unknown[]) =>
queryMock(sql, params) as Promise<{ rows: T[]; rowCount: number }>,
};
}
function calls(queryMock: QueryMock): MockCall[] {
return queryMock.mock.calls.map(([sql, params]) => ({
sql: String(sql),
params: (params as unknown[]) ?? [],
}));
}
describe('resolvePax8CompanyMatch', () => {
it('writes both pax8_companies and pax8_company_match_review on success', async () => {
const queryMock = vi.fn();
// 1. review SELECT ... FOR UPDATE
queryMock.mockResolvedValueOnce({
rows: [{ pax8_company_id: 'pax8-uuid-1', resolved_at: null }],
rowCount: 1,
});
// 2. company existence/active check
queryMock.mockResolvedValueOnce({ rows: [{ '?column?': 1 }], rowCount: 1 });
// 3. UPDATE pax8_companies
queryMock.mockResolvedValueOnce({ rows: [], rowCount: 1 });
// 4. UPDATE pax8_company_match_review
queryMock.mockResolvedValueOnce({ rows: [], rowCount: 1 });
const tx = makeTx(queryMock);
const result = await resolvePax8CompanyMatch(tx, {
reviewId: 'review-uuid-1',
companyId: 42,
note: 'looks right',
userId: 'user-1',
});
expect(result).toEqual({ ok: true, resolvedToCompanyId: 42 });
const c = calls(queryMock);
expect(c).toHaveLength(4);
expect(c[2].sql).toMatch(/UPDATE\s+pax8_companies/i);
expect(c[2].sql).toMatch(/match_method\s*=\s*'manual'/i);
expect(c[2].params).toEqual([ 'pax8-uuid-1', 42 ]);
expect(c[3].sql).toMatch(/UPDATE\s+pax8_company_match_review/i);
expect(c[3].sql).toMatch(/resolved_at\s*=\s*NOW\(\)/i);
expect(c[3].params).toEqual(['review-uuid-1', 'user-1', 42, 'looks right']);
});
it('returns not_found and issues no updates when the review row is missing', async () => {
const queryMock = vi.fn();
queryMock.mockResolvedValueOnce({ rows: [], rowCount: 0 });
const tx = makeTx(queryMock);
const result = await resolvePax8CompanyMatch(tx, {
reviewId: 'missing-review',
companyId: 42,
note: null,
userId: 'user-1',
});
expect(result).toEqual({
ok: false,
code: 'not_found',
message: expect.any(String),
});
expect(queryMock).toHaveBeenCalledTimes(1);
});
it('returns already_resolved and issues no updates when resolved_at is set', async () => {
const queryMock = vi.fn();
queryMock.mockResolvedValueOnce({
rows: [{ pax8_company_id: 'pax8-uuid-1', resolved_at: '2026-01-01T00:00:00.000Z' }],
rowCount: 1,
});
const tx = makeTx(queryMock);
const result = await resolvePax8CompanyMatch(tx, {
reviewId: 'review-uuid-1',
companyId: 42,
note: null,
userId: 'user-1',
});
expect(result).toEqual({
ok: false,
code: 'already_resolved',
message: expect.any(String),
});
expect(queryMock).toHaveBeenCalledTimes(1);
});
it('returns company_not_found and issues no pax8_companies write when target does not exist/is inactive', async () => {
const queryMock = vi.fn();
queryMock.mockResolvedValueOnce({
rows: [{ pax8_company_id: 'pax8-uuid-1', resolved_at: null }],
rowCount: 1,
});
queryMock.mockResolvedValueOnce({ rows: [], rowCount: 0 });
const tx = makeTx(queryMock);
const result = await resolvePax8CompanyMatch(tx, {
reviewId: 'review-uuid-1',
companyId: 999,
note: null,
userId: 'user-1',
});
expect(result).toEqual({
ok: false,
code: 'company_not_found',
message: expect.any(String),
});
expect(queryMock).toHaveBeenCalledTimes(2);
});
it('resolves successfully with a companyId NOT in candidate_company_ids (manual-search / zero-candidate case)', async () => {
const queryMock = vi.fn();
queryMock.mockResolvedValueOnce({
rows: [{ pax8_company_id: 'pax8-uuid-1', resolved_at: null }],
rowCount: 1,
});
queryMock.mockResolvedValueOnce({ rows: [{ '?column?': 1 }], rowCount: 1 });
queryMock.mockResolvedValueOnce({ rows: [], rowCount: 1 });
queryMock.mockResolvedValueOnce({ rows: [], rowCount: 1 });
const tx = makeTx(queryMock);
// companyId 777 was never a member of candidate_company_ids for this
// review — resolver must not check membership at all.
const result = await resolvePax8CompanyMatch(tx, {
reviewId: 'review-uuid-1',
companyId: 777,
note: null,
userId: 'user-1',
});
expect(result).toEqual({ ok: true, resolvedToCompanyId: 777 });
});
});