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
380 lines
12 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
});
|