PSA/shared/workflow/actions/__tests__/emailWorkflowActions.destinationResolver.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

380 lines
12 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
type QueryPlanRow = {
table: string;
where: Record<string, unknown>;
row: any;
};
const withAdminTransactionMock = vi.fn();
let trxImpl: any = null;
vi.mock('@alga-psa/db', () => ({
withAdminTransaction: (callback: (trx: any) => Promise<any>) =>
withAdminTransactionMock(callback),
}));
vi.mock('@alga-psa/event-bus/publishers', () => ({
publishWorkflowEvent: vi.fn(),
}));
function whereMatches(actual: Record<string, unknown>, expected: Record<string, unknown>): boolean {
return Object.entries(expected).every(([key, value]) => actual[key] === value);
}
function createTrxForQueryPlan(plan: QueryPlanRow[]) {
const tablesCalled: string[] = [];
const trx = (table: string) => {
tablesCalled.push(table);
let whereClause: Record<string, unknown> = {};
const builder: any = {
select: vi.fn(() => builder),
where: vi.fn((value: Record<string, unknown>) => {
whereClause = value ?? {};
return builder;
}),
first: vi.fn(async () => {
const match = plan.find((entry) => entry.table === table && whereMatches(whereClause, entry.where));
return match?.row ?? null;
}),
};
return builder;
};
return { trx, tablesCalled };
}
describe('resolveEffectiveInboundTicketDefaults precedence', () => {
beforeEach(() => {
vi.clearAllMocks();
withAdminTransactionMock.mockImplementation(async (callback: (trx: any) => Promise<any>) =>
callback(trxImpl)
);
});
it('T005: exact sender contact with contact override selects contact destination', async () => {
const contactDefaults = {
board_id: 'board-contact-1',
status_id: 'status-1',
priority_id: 'priority-1',
client_id: 'client-contact',
entered_by: 'user-1',
category_id: null,
subcategory_id: null,
location_id: null,
};
const { trx, tablesCalled } = createTrxForQueryPlan([
{
table: 'contacts',
where: { tenant: 'tenant-1', contact_name_id: 'contact-1' },
row: { inbound_ticket_defaults_id: 'defaults-contact-1', client_id: 'client-contact' },
},
{
table: 'inbound_ticket_defaults',
where: { tenant: 'tenant-1', id: 'defaults-contact-1', is_active: true },
row: contactDefaults,
},
]);
trxImpl = trx;
const { resolveEffectiveInboundTicketDefaults } = await import('../emailWorkflowActions');
const result = await resolveEffectiveInboundTicketDefaults({
tenant: 'tenant-1',
providerId: 'provider-1',
providerDefaults: {
board_id: 'board-provider',
status_id: 'status-provider',
priority_id: 'priority-provider',
},
matchedContactId: 'contact-1',
matchedContactClientId: 'client-contact',
domainMatchedClientId: null,
});
expect(result.source).toBe('contact_override');
expect(result.defaults).toEqual(contactDefaults);
expect(result.fallbackReason).toBeUndefined();
expect(tablesCalled).not.toContain('clients');
});
it("T006: exact sender contact without override selects contact's client destination", async () => {
const clientDefaults = {
board_id: 'board-client-1',
status_id: 'status-1',
priority_id: 'priority-1',
client_id: 'client-2',
entered_by: 'user-1',
category_id: null,
subcategory_id: null,
location_id: null,
};
const { trx, tablesCalled } = createTrxForQueryPlan([
{
table: 'contacts',
where: { tenant: 'tenant-1', contact_name_id: 'contact-2' },
row: { inbound_ticket_defaults_id: null, client_id: 'client-2' },
},
{
table: 'clients',
where: { tenant: 'tenant-1', client_id: 'client-2' },
row: { inbound_ticket_defaults_id: 'defaults-client-2' },
},
{
table: 'inbound_ticket_defaults',
where: { tenant: 'tenant-1', id: 'defaults-client-2', is_active: true },
row: clientDefaults,
},
]);
trxImpl = trx;
const { resolveEffectiveInboundTicketDefaults } = await import('../emailWorkflowActions');
const result = await resolveEffectiveInboundTicketDefaults({
tenant: 'tenant-1',
providerId: 'provider-1',
providerDefaults: {
board_id: 'board-provider',
status_id: 'status-provider',
priority_id: 'priority-provider',
},
matchedContactId: 'contact-2',
matchedContactClientId: 'client-2',
domainMatchedClientId: null,
});
expect(result.source).toBe('client_default_from_contact');
expect(result.defaults).toEqual(clientDefaults);
expect(result.fallbackReason).toBeUndefined();
expect(tablesCalled).toContain('clients');
});
it('T007: no exact contact + domain-matched client selects domain client destination', async () => {
const domainClientDefaults = {
board_id: 'board-domain-1',
status_id: 'status-1',
priority_id: 'priority-1',
client_id: 'client-domain-1',
entered_by: 'user-1',
category_id: null,
subcategory_id: null,
location_id: null,
};
const { trx, tablesCalled } = createTrxForQueryPlan([
{
table: 'clients',
where: { tenant: 'tenant-1', client_id: 'client-domain-1' },
row: { inbound_ticket_defaults_id: 'defaults-domain-1' },
},
{
table: 'inbound_ticket_defaults',
where: { tenant: 'tenant-1', id: 'defaults-domain-1', is_active: true },
row: domainClientDefaults,
},
]);
trxImpl = trx;
const { resolveEffectiveInboundTicketDefaults } = await import('../emailWorkflowActions');
const result = await resolveEffectiveInboundTicketDefaults({
tenant: 'tenant-1',
providerId: 'provider-1',
providerDefaults: {
board_id: 'board-provider',
status_id: 'status-provider',
priority_id: 'priority-provider',
},
matchedContactId: null,
matchedContactClientId: null,
domainMatchedClientId: 'client-domain-1',
});
expect(result.source).toBe('client_default_from_domain');
expect(result.defaults).toEqual(domainClientDefaults);
expect(result.fallbackReason).toBeUndefined();
expect(tablesCalled).not.toContain('contacts');
});
it('T008: no contact/domain destination falls back to provider default', async () => {
const providerDefaults = {
board_id: 'board-provider-default',
status_id: 'status-provider-default',
priority_id: 'priority-provider-default',
client_id: null,
entered_by: 'user-1',
category_id: null,
subcategory_id: null,
location_id: null,
};
const { trx, tablesCalled } = createTrxForQueryPlan([]);
trxImpl = trx;
const { resolveEffectiveInboundTicketDefaults } = await import('../emailWorkflowActions');
const result = await resolveEffectiveInboundTicketDefaults({
tenant: 'tenant-1',
providerId: 'provider-1',
providerDefaults,
matchedContactId: null,
matchedContactClientId: null,
domainMatchedClientId: null,
});
expect(result.source).toBe('provider_default');
expect(result.defaults).toEqual(providerDefaults);
expect(result.fallbackReason).toBeUndefined();
expect(tablesCalled).toHaveLength(0);
});
it('T009: falls back to provider default when contact override destination is inactive/invalid', async () => {
const providerDefaults = {
board_id: 'board-provider-default',
status_id: 'status-provider-default',
priority_id: 'priority-provider-default',
client_id: null,
entered_by: 'user-1',
category_id: null,
subcategory_id: null,
location_id: null,
};
const { trx } = createTrxForQueryPlan([
{
table: 'contacts',
where: { tenant: 'tenant-1', contact_name_id: 'contact-9' },
row: { inbound_ticket_defaults_id: 'defaults-contact-invalid', client_id: null },
},
{
table: 'inbound_ticket_defaults',
where: { tenant: 'tenant-1', id: 'defaults-contact-invalid', is_active: true },
row: null,
},
]);
trxImpl = trx;
const { resolveEffectiveInboundTicketDefaults } = await import('../emailWorkflowActions');
const result = await resolveEffectiveInboundTicketDefaults({
tenant: 'tenant-1',
providerId: 'provider-1',
providerDefaults,
matchedContactId: 'contact-9',
matchedContactClientId: null,
domainMatchedClientId: null,
});
expect(result.source).toBe('provider_default');
expect(result.defaults).toEqual(providerDefaults);
expect(result.fallbackReason).toBe('invalid_or_inactive_contact_override');
});
it('T010: falls back to provider default when client destination is inactive/invalid', async () => {
const providerDefaults = {
board_id: 'board-provider-default',
status_id: 'status-provider-default',
priority_id: 'priority-provider-default',
client_id: null,
entered_by: 'user-1',
category_id: null,
subcategory_id: null,
location_id: null,
};
const { trx } = createTrxForQueryPlan([
{
table: 'contacts',
where: { tenant: 'tenant-1', contact_name_id: 'contact-10' },
row: { inbound_ticket_defaults_id: null, client_id: 'client-10' },
},
{
table: 'clients',
where: { tenant: 'tenant-1', client_id: 'client-10' },
row: { inbound_ticket_defaults_id: 'defaults-client-invalid' },
},
{
table: 'inbound_ticket_defaults',
where: { tenant: 'tenant-1', id: 'defaults-client-invalid', is_active: true },
row: null,
},
]);
trxImpl = trx;
const { resolveEffectiveInboundTicketDefaults } = await import('../emailWorkflowActions');
const result = await resolveEffectiveInboundTicketDefaults({
tenant: 'tenant-1',
providerId: 'provider-1',
providerDefaults,
matchedContactId: 'contact-10',
matchedContactClientId: 'client-10',
domainMatchedClientId: null,
});
expect(result.source).toBe('provider_default');
expect(result.defaults).toEqual(providerDefaults);
expect(result.fallbackReason).toBe('invalid_or_inactive_client_default_from_contact');
});
it('T027: structured logs include destination source and fallback reason metadata', async () => {
const providerDefaults = {
board_id: 'board-provider-default',
status_id: 'status-provider-default',
priority_id: 'priority-provider-default',
client_id: null,
entered_by: 'user-1',
category_id: null,
subcategory_id: null,
location_id: null,
};
const { trx } = createTrxForQueryPlan([
{
table: 'contacts',
where: { tenant: 'tenant-1', contact_name_id: 'contact-27' },
row: { inbound_ticket_defaults_id: 'defaults-contact-invalid-27', client_id: null },
},
{
table: 'inbound_ticket_defaults',
where: { tenant: 'tenant-1', id: 'defaults-contact-invalid-27', is_active: true },
row: null,
},
]);
trxImpl = trx;
const debugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
try {
const { resolveEffectiveInboundTicketDefaults } = await import('../emailWorkflowActions');
await resolveEffectiveInboundTicketDefaults({
tenant: 'tenant-1',
providerId: 'provider-1',
providerDefaults,
matchedContactId: 'contact-27',
matchedContactClientId: null,
domainMatchedClientId: null,
});
expect(warnSpy).toHaveBeenCalledWith(
'resolveEffectiveInboundTicketDefaults: invalid contact override destination; using fallback',
expect.objectContaining({
source: 'contact_override',
fallback: 'provider_default',
configuredDefaultsId: 'defaults-contact-invalid-27',
})
);
expect(debugSpy).toHaveBeenCalledWith(
'resolveEffectiveInboundTicketDefaults: resolved destination',
expect.objectContaining({
source: 'provider_default',
fallbackReason: 'invalid_or_inactive_contact_override',
})
);
} finally {
debugSpy.mockRestore();
warnSpy.mockRestore();
}
});
});