PSA/shared/workflow/runtime/actions/__tests__/businessOperations.contacts.emailSearch.test.ts
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

175 lines
5.4 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
type RegisteredAction = {
id: string;
handler: (input: any, ctx: any) => Promise<any>;
};
const registeredActions: RegisteredAction[] = [];
const registerMock = vi.fn((action: RegisteredAction) => {
registeredActions.push(action);
});
const withTenantTransactionMock = vi.fn();
const requirePermissionMock = vi.fn();
const getContactByEmailMock = vi.fn();
const getContactByIdMock = vi.fn();
const scenario = {
searchRows: [] as any[],
usedAdditionalEmailSearch: false,
};
vi.mock('../../registries/actionRegistry', () => ({
getActionRegistryV2: () => ({ register: registerMock }),
}));
vi.mock('../businessOperations/shared', async () => {
const actual = await vi.importActual<any>('../businessOperations/shared');
return {
...actual,
withTenantTransaction: (ctx: any, callback: (tx: any) => Promise<any>) => withTenantTransactionMock(ctx, callback),
requirePermission: (...args: any[]) => requirePermissionMock(...args),
};
});
vi.mock('../../../../models/contactModel', () => ({
ContactModel: {
getContactByEmail: getContactByEmailMock,
getContactById: getContactByIdMock,
},
}));
function makeSearchQuery(rows: any[]) {
const query: any = {
where: vi.fn((arg: unknown) => {
if (typeof arg === 'function') {
const scoped: any = {
whereRaw: vi.fn().mockReturnThis(),
orWhereRaw: vi.fn().mockReturnThis(),
orWhereExists: vi.fn().mockImplementation((callback: (this: any) => void) => {
scenario.usedAdditionalEmailSearch = true;
const existsQuery: any = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
whereRaw: vi.fn().mockReturnThis(),
andWhere: vi.fn().mockReturnThis(),
andWhereRaw: vi.fn().mockReturnThis(),
};
callback.call(existsQuery);
return scoped;
}),
};
arg.call(scoped);
}
return query;
}),
andWhere: vi.fn().mockReturnThis(),
join: vi.fn().mockReturnThis(),
whereIn: vi.fn().mockReturnThis(),
clone: vi.fn(() => query),
clearSelect: vi.fn().mockReturnThis(),
clearOrder: vi.fn().mockReturnThis(),
countDistinct: vi.fn().mockReturnThis(),
first: vi.fn().mockResolvedValue({ count: rows.length }),
select: vi.fn().mockReturnThis(),
orderBy: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(),
offset: vi.fn().mockResolvedValue(rows),
};
return query;
}
describe('workflow business contact email lookup', () => {
beforeEach(() => {
registeredActions.length = 0;
registerMock.mockClear();
requirePermissionMock.mockResolvedValue(undefined);
getContactByEmailMock.mockReset();
getContactByIdMock.mockReset();
scenario.searchRows = [];
scenario.usedAdditionalEmailSearch = false;
withTenantTransactionMock.mockImplementation(async (_ctx: any, callback: (tx: any) => Promise<any>) => {
const searchQuery = makeSearchQuery(scenario.searchRows);
const trx: any = Object.assign(
vi.fn((table: string) => {
if (table === 'contacts') {
return searchQuery;
}
throw new Error(`Unexpected table ${table}`);
}),
{
raw: vi.fn((value: string) => value),
}
);
return callback({
tenantId: 'tenant-1',
trx,
logger: { info: vi.fn() },
});
});
});
it('T039: contacts.find and contacts.search match additional emails while keeping summary email fields on contact.email', async () => {
const { registerContactActions } = await import('../businessOperations/contacts');
registerContactActions();
getContactByEmailMock.mockResolvedValue({
contact_name_id: '00000000-0000-0000-0000-000000000101',
full_name: 'Workflow Contact',
email: 'primary@example.com',
client_id: '00000000-0000-0000-0000-000000000201',
is_inactive: false,
phone: null,
});
const findAction = registeredActions.find((action) => action.id === 'contacts.find');
expect(findAction).toBeDefined();
const findResult = await findAction!.handler(
{
email: 'billing@example.com',
},
{}
);
expect(getContactByEmailMock).toHaveBeenCalledWith('billing@example.com', 'tenant-1', expect.any(Function));
expect(findResult.contact).toMatchObject({
contact_name_id: '00000000-0000-0000-0000-000000000101',
email: 'primary@example.com',
});
scenario.searchRows = [
{
contact_name_id: '00000000-0000-0000-0000-000000000101',
full_name: 'Workflow Contact',
email: 'primary@example.com',
client_id: '00000000-0000-0000-0000-000000000201',
is_inactive: false,
phone: null,
},
];
const searchAction = registeredActions.find((action) => action.id === 'contacts.search');
expect(searchAction).toBeDefined();
const searchResult = await searchAction!.handler(
{
query: 'billing@example.com',
},
{}
);
expect(scenario.usedAdditionalEmailSearch).toBe(true);
expect(searchResult.contacts[0]).toMatchObject({
contact_name_id: '00000000-0000-0000-0000-000000000101',
email: 'primary@example.com',
});
expect(searchResult.first_contact).toMatchObject({
email: 'primary@example.com',
});
});
});