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
185 lines
4.9 KiB
TypeScript
185 lines
4.9 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { TicketModel } from '../ticketModel';
|
|
|
|
function createTrxHarness(options?: {
|
|
ticketExists?: boolean;
|
|
contactExists?: boolean;
|
|
}) {
|
|
const ticketExists = options?.ticketExists ?? true;
|
|
const contactExists = options?.contactExists ?? true;
|
|
|
|
const insertedComments: any[] = [];
|
|
const ticketUpdates: any[] = [];
|
|
|
|
const commentsInsert = vi.fn(async (data: any) => {
|
|
insertedComments.push(data);
|
|
return [data];
|
|
});
|
|
|
|
const ticketsWhere = vi.fn(() => ({
|
|
first: vi.fn().mockResolvedValue(ticketExists ? { ticket_id: '11111111-1111-1111-1111-111111111111' } : null),
|
|
update: vi.fn(async (updateData: any) => {
|
|
ticketUpdates.push(updateData);
|
|
return 1;
|
|
}),
|
|
}));
|
|
|
|
const contactsWhere = vi.fn(() => ({
|
|
first: vi.fn().mockResolvedValue(
|
|
contactExists ? { contact_name_id: '22222222-2222-2222-2222-222222222222' } : null
|
|
),
|
|
}));
|
|
|
|
const trx: any = vi.fn((table: string) => {
|
|
if (table === 'tickets') {
|
|
return { where: ticketsWhere };
|
|
}
|
|
if (table === 'contacts') {
|
|
return { where: contactsWhere };
|
|
}
|
|
if (table === 'comments') {
|
|
return { insert: commentsInsert };
|
|
}
|
|
throw new Error(`Unexpected table in TicketModel.createComment unit test: ${table}`);
|
|
});
|
|
|
|
return {
|
|
trx,
|
|
insertedComments,
|
|
ticketUpdates,
|
|
};
|
|
}
|
|
|
|
describe('TicketModel.createComment contact authorship', () => {
|
|
const tenant = '33333333-3333-3333-3333-333333333333';
|
|
const ticketId = '11111111-1111-1111-1111-111111111111';
|
|
const contactId = '22222222-2222-2222-2222-222222222222';
|
|
const userId = '44444444-4444-4444-4444-444444444444';
|
|
|
|
it('T004: accepts author_type=contact with contact_id and no author_id', async () => {
|
|
const { trx } = createTrxHarness();
|
|
|
|
const result = await TicketModel.createComment(
|
|
{
|
|
ticket_id: ticketId,
|
|
content: 'Contact-authored comment',
|
|
author_type: 'contact',
|
|
contact_id: contactId,
|
|
},
|
|
tenant,
|
|
trx
|
|
);
|
|
|
|
expect(result.comment_id).toBeDefined();
|
|
});
|
|
|
|
it('T005: rejects invalid contact_id format', async () => {
|
|
const { trx } = createTrxHarness();
|
|
|
|
await expect(
|
|
TicketModel.createComment(
|
|
{
|
|
ticket_id: ticketId,
|
|
content: 'Invalid contact id',
|
|
author_type: 'contact',
|
|
contact_id: 'not-a-uuid',
|
|
},
|
|
tenant,
|
|
trx
|
|
)
|
|
).rejects.toThrow('Contact ID must be a valid UUID');
|
|
});
|
|
|
|
it('T006: rejects contact_id that does not belong to tenant', async () => {
|
|
const { trx } = createTrxHarness({ contactExists: false });
|
|
|
|
await expect(
|
|
TicketModel.createComment(
|
|
{
|
|
ticket_id: ticketId,
|
|
content: 'Missing contact',
|
|
author_type: 'contact',
|
|
contact_id: contactId,
|
|
},
|
|
tenant,
|
|
trx
|
|
)
|
|
).rejects.toThrow('Contact not found or does not belong to tenant');
|
|
});
|
|
|
|
it('T007: persists contact_id on inserted comment row', async () => {
|
|
const { trx, insertedComments } = createTrxHarness();
|
|
|
|
await TicketModel.createComment(
|
|
{
|
|
ticket_id: ticketId,
|
|
content: 'Persist contact id',
|
|
author_type: 'contact',
|
|
contact_id: contactId,
|
|
},
|
|
tenant,
|
|
trx
|
|
);
|
|
|
|
expect(insertedComments).toHaveLength(1);
|
|
expect(insertedComments[0].contact_id).toBe(contactId);
|
|
expect(insertedComments[0].user_id).toBeNull();
|
|
});
|
|
|
|
it('T008: persists both user_id and contact_id when both are provided', async () => {
|
|
const { trx, insertedComments } = createTrxHarness();
|
|
|
|
await TicketModel.createComment(
|
|
{
|
|
ticket_id: ticketId,
|
|
content: 'Persist both author ids',
|
|
author_type: 'contact',
|
|
author_id: userId,
|
|
contact_id: contactId,
|
|
},
|
|
tenant,
|
|
trx
|
|
);
|
|
|
|
expect(insertedComments).toHaveLength(1);
|
|
expect(insertedComments[0].user_id).toBe(userId);
|
|
expect(insertedComments[0].contact_id).toBe(contactId);
|
|
});
|
|
|
|
it('T009: contact-authored public comment sets response_state=awaiting_internal', async () => {
|
|
const { trx, ticketUpdates } = createTrxHarness();
|
|
|
|
await TicketModel.createComment(
|
|
{
|
|
ticket_id: ticketId,
|
|
content: 'Public contact reply',
|
|
author_type: 'contact',
|
|
contact_id: contactId,
|
|
is_internal: false,
|
|
},
|
|
tenant,
|
|
trx
|
|
);
|
|
|
|
expect(ticketUpdates).toContainEqual({ response_state: 'awaiting_internal' });
|
|
});
|
|
|
|
it('T010: contact-authored internal comment does not mutate response_state', async () => {
|
|
const { trx, ticketUpdates } = createTrxHarness();
|
|
|
|
await TicketModel.createComment(
|
|
{
|
|
ticket_id: ticketId,
|
|
content: 'Internal contact note',
|
|
author_type: 'contact',
|
|
contact_id: contactId,
|
|
is_internal: true,
|
|
},
|
|
tenant,
|
|
trx
|
|
);
|
|
|
|
expect(ticketUpdates).toHaveLength(0);
|
|
});
|
|
});
|