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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
175 lines
5.4 KiB
TypeScript
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',
|
|
});
|
|
});
|
|
});
|