|
@@ -14,7 +14,9 @@ class FakeChatwoot {
|
|
|
labels: string[] = [];
|
|
labels: string[] = [];
|
|
|
attributeWrites: Record<string, unknown>[] = [];
|
|
attributeWrites: Record<string, unknown>[] = [];
|
|
|
unassignCalls = 0;
|
|
unassignCalls = 0;
|
|
|
|
|
+ statusCalls: string[] = [];
|
|
|
unassignSucceeds = true;
|
|
unassignSucceeds = true;
|
|
|
|
|
+ statusSucceeds = true;
|
|
|
getCalls = 0;
|
|
getCalls = 0;
|
|
|
conversation: Record<string, unknown> = { id: 500, labels: [], custom_attributes: {} };
|
|
conversation: Record<string, unknown> = { id: 500, labels: [], custom_attributes: {} };
|
|
|
|
|
|
|
@@ -34,6 +36,10 @@ class FakeChatwoot {
|
|
|
this.unassignCalls++;
|
|
this.unassignCalls++;
|
|
|
return this.unassignSucceeds;
|
|
return this.unassignSucceeds;
|
|
|
}
|
|
}
|
|
|
|
|
+ async setConversationStatus(_id: number, status: 'open' | 'resolved' | 'pending'): Promise<boolean> {
|
|
|
|
|
+ this.statusCalls.push(status);
|
|
|
|
|
+ return this.statusSucceeds;
|
|
|
|
|
+ }
|
|
|
async sendOutgoingMessage(): Promise<unknown> {
|
|
async sendOutgoingMessage(): Promise<unknown> {
|
|
|
return {};
|
|
return {};
|
|
|
}
|
|
}
|
|
@@ -126,6 +132,43 @@ test('unassign is skipped when the flag is off', async () => {
|
|
|
const meta = JSON.parse(event?.metaJson ?? '{}') as Record<string, unknown>;
|
|
const meta = JSON.parse(event?.metaJson ?? '{}') as Record<string, unknown>;
|
|
|
assert.equal(meta.unassignRequested, false);
|
|
assert.equal(meta.unassignRequested, false);
|
|
|
assert.equal(meta.unassigned, null);
|
|
assert.equal(meta.unassigned, null);
|
|
|
|
|
+ applyTestConfig({ DATABASE_URL: dbUrl });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('ticket creation opens the conversation by default and audits the real outcome', async () => {
|
|
|
|
|
+ applyTestConfig({ DATABASE_URL: dbUrl });
|
|
|
|
|
+ const cw = new FakeChatwoot();
|
|
|
|
|
+ await createTicket(500, 'handoff', as(cw));
|
|
|
|
|
+ assert.deepEqual(cw.statusCalls, ['open']);
|
|
|
|
|
+
|
|
|
|
|
+ const event = await db().auditEvent.findFirst({ where: { eventType: 'ticket_created' } });
|
|
|
|
|
+ const meta = JSON.parse(event?.metaJson ?? '{}') as Record<string, unknown>;
|
|
|
|
|
+ assert.equal(meta.openRequested, true);
|
|
|
|
|
+ assert.equal(meta.opened, true);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('open status is skipped when the flag is off', async () => {
|
|
|
|
|
+ applyTestConfig({ DATABASE_URL: dbUrl, CHATWOOT_OPEN_ON_TICKET: 'false' });
|
|
|
|
|
+ const cw = new FakeChatwoot();
|
|
|
|
|
+ await createTicket(500, 'handoff', as(cw));
|
|
|
|
|
+ assert.deepEqual(cw.statusCalls, []);
|
|
|
|
|
+
|
|
|
|
|
+ const event = await db().auditEvent.findFirst({ where: { eventType: 'ticket_created' } });
|
|
|
|
|
+ const meta = JSON.parse(event?.metaJson ?? '{}') as Record<string, unknown>;
|
|
|
|
|
+ assert.equal(meta.openRequested, false);
|
|
|
|
|
+ assert.equal(meta.opened, null);
|
|
|
|
|
+ applyTestConfig({ DATABASE_URL: dbUrl });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('a failed open status update is audited as failed, not success', async () => {
|
|
|
|
|
+ applyTestConfig({ DATABASE_URL: dbUrl });
|
|
|
|
|
+ const cw = new FakeChatwoot();
|
|
|
|
|
+ cw.statusSucceeds = false;
|
|
|
|
|
+ await createTicket(500, 'handoff', as(cw));
|
|
|
|
|
+
|
|
|
|
|
+ const event = await db().auditEvent.findFirst({ where: { eventType: 'ticket_created' } });
|
|
|
|
|
+ const meta = JSON.parse(event?.metaJson ?? '{}') as Record<string, unknown>;
|
|
|
|
|
+ assert.equal(meta.opened, false, 'the audit must record effect, not intent');
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
test('unassign runs when the flag is on and the real outcome is audited', async () => {
|
|
test('unassign runs when the flag is on and the real outcome is audited', async () => {
|
|
@@ -161,6 +204,7 @@ test('an adopted ticket is also unassigned when the flag is on', async () => {
|
|
|
await createTicket(500, 'handoff', as(cw));
|
|
await createTicket(500, 'handoff', as(cw));
|
|
|
|
|
|
|
|
assert.equal(cw.unassignCalls, 1, 'adoption must reach the same Chatwoot state as creation');
|
|
assert.equal(cw.unassignCalls, 1, 'adoption must reach the same Chatwoot state as creation');
|
|
|
|
|
+ assert.deepEqual(cw.statusCalls, ['open'], 'adoption must leave the conversation open');
|
|
|
assert.ok(cw.labels.includes('ticket'));
|
|
assert.ok(cw.labels.includes('ticket'));
|
|
|
applyTestConfig({ DATABASE_URL: dbUrl });
|
|
applyTestConfig({ DATABASE_URL: dbUrl });
|
|
|
});
|
|
});
|