apps/web/src/lib/salesConfirmation.test.ts
1import { expect, test } from 'bun:test'
2import { isHandoffCancellation } from '@local/ai/components/workflows'
3import { HANDOFF_CONSENT } from '@local/config/ai-consent'
4import { acceptedSalesOffer } from './aiFlows'
5import { verifiedHandoffReply } from './aiHandoff'
6import {
7 commercialRequestDetails,
8 confirmedSalesRequest,
9 salesAcceptanceReply,
10} from './salesConfirmation'
11
12test('the confirmation retains the stated exhibition objective and includes the current recipient', () => {
13 const details = commercialRequestDetails(
14 [{ role: 'user', text: 'We are looking to exhibit to meet HR buyers.' }],
15 'Yes, please send it.',
16 )
17 expect(details).toBe('We are looking to exhibit to meet HR buyers.')
18 expect(commercialRequestDetails([], 'yes please send it')).toBeUndefined()
19 const summary = salesAcceptanceReply(
20 {
21 name: 'Jordan',
22 email: 'jordan@example.org',
23 role: 'Director',
24 company: 'Example',
25 },
26 'paris',
27 false,
28 undefined,
29 details,
30 )
31 expect(summary).toContain('meet HR buyers')
32 expect(summary).toContain('jordan@example.org')
33})
34
35const offer = [
36 {
37 role: 'assistant' as const,
38 text:
39 'Yes, you can sponsor Paris. Would you like our sales team to get in touch?',
40 },
41]
42const profile = {
43 name: 'Jordan UAT',
44 email: 'jordan.uat@example.com',
45 company: 'Example',
46 role: 'Director',
47}
48test('typo acceptance progresses the offer but does not authorize dispatch', () => {
49 for (const text of [
50 'yes pleas1',
51 'yes pleas',
52 'yes please',
53 'yes',
54 'yes pls',
55 'yes get in touch',
56 ]) {
57 expect(acceptedSalesOffer(offer, text)).toBe(true)
58 expect(confirmedSalesRequest(offer, text)).toBe(false)
59 }
60 for (const text of ['yes please but not now', 'no', 'yes if it is free'])
61 expect(acceptedSalesOffer(offer, text)).toBe(false)
62})
63test('saved details and consent produce a request summary and separate confirmation', () => {
64 const summary = salesAcceptanceReply(profile, 'paris', true, undefined)
65 for (const detail of [
66 'Jordan UAT',
67 'Example',
68 'jordan.uat@example.com',
69 'Paris',
70 ])
71 expect(summary).toContain(detail)
72 expect(summary.endsWith(HANDOFF_CONSENT.sponsorship)).toBe(true)
73 const history = [{ role: 'assistant' as const, text: summary }]
74 expect(confirmedSalesRequest(history, 'yes please')).toBe(true)
75 expect(isHandoffCancellation(history, 'Cancel that.')).toBe(true)
76 for (const text of ['cancel', 'yes but tomorrow', 'no', 'change my email'])
77 expect(confirmedSalesRequest(history, text)).toBe(false)
78})
79test('missing details or consent are requested without claiming success', () => {
80 expect(salesAcceptanceReply(undefined, 'paris', false, undefined)).toContain(
81 'What company',
82 )
83 expect(
84 salesAcceptanceReply(
85 { company: 'Example', role: 'Director' },
86 'paris',
87 true,
88 undefined,
89 ),
90 ).toContain('What name')
91 expect(
92 salesAcceptanceReply(
93 { ...profile, email: undefined },
94 'paris',
95 true,
96 undefined,
97 ),
98 ).toContain('What work email')
99 expect(salesAcceptanceReply(profile, 'miami', false, undefined)).toContain(
100 HANDOFF_CONSENT.sponsorship,
101 )
102})
103test('booking caveat belongs only to delegate confirmations', () => {
104 expect(verifiedHandoffReply('', { kind: 'lead', ok: true })).not.toContain(
105 'booking',
106 )
107 expect(
108 verifiedHandoffReply('', { kind: 'lead', ok: true, ticketEnquiry: true }),
109 ).toContain('not a confirmed booking')
110})
111
112test('contextual sales offer remains actionable when the model calls the team them', () => {
113 expect(
114 acceptedSalesOffer(
115 [
116 {
117 role: 'assistant',
118 text:
119 'Our sales team can discuss the options. Would you like me to put you in touch with them directly?',
120 },
121 ],
122 'yes get in touch',
123 ),
124 ).toBe(true)
125 expect(
126 acceptedSalesOffer(
127 [
128 {
129 role: 'assistant',
130 text:
131 'Our hotel provider can help. Would you like me to put you in touch with them directly?',
132 },
133 ],
134 'yes get in touch',
135 ),
136 ).toBe(false)
137})
138