Skip to content

Commit 03ab10c

Browse files
committed
Working inboundCall tests with 100% coverage
1 parent 75d9fee commit 03ab10c

File tree

5 files changed

+117
-26
lines changed

5 files changed

+117
-26
lines changed

src/@types/types.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ export interface JoinConferenceParams extends Request {
3838
}
3939
}
4040

41-
export interface ParticipantsToDial[] {
42-
messagingBinding: {
43-
address: string
44-
proxyAddress: string
45-
}
41+
export interface ParticipantToDial {
42+
address: string,
43+
proxyAddress: string
4644
}

src/services/inboundCall.service.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getConversationByAddressPair } from "../utils/getConversationByAddressP
55
import client from '../twilioClient'
66
import { participantsToDial } from "../utils/participantsToDial.util";
77
import { listConversationParticipants } from "../utils";
8+
import { generateConferenceName } from "../utils/generateConferenceName.util";
89

910
export const generateTwiml = async (from: string, to: string) => {
1011
let response = new VoiceResponse();
@@ -27,7 +28,7 @@ export const generateTwiml = async (from: string, to: string) => {
2728
}, process.env.CONNECTING_CALL_ANNOUCEMENT);
2829

2930
if (dialList.length > 1) {
30-
const conferenceName = `${from}_at_${Date.now()}`
31+
const conferenceName = generateConferenceName(from)
3132

3233
const callPromises = dialList.map(pa => {
3334
console.log(`Dialing ${pa.address} from ${pa.proxyAddress}...`);
@@ -39,12 +40,7 @@ export const generateTwiml = async (from: string, to: string) => {
3940
});
4041
});
4142

42-
try {
43-
await Promise.all(callPromises)
44-
} catch(err) {
45-
console.log(err)
46-
throw new Error(err)
47-
}
43+
await Promise.all(callPromises)
4844

4945
const dial = response.dial();
5046
dial.conference({
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const generateConferenceName = (phoneNumber: string) : string => {
2+
return encodeURIComponent(`${phoneNumber}_at_${Date.now()}`)
3+
}
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { ParticipantInstance } from "twilio/lib/rest/conversations/v1/conversation/participant";
2+
import { ParticipantToDial } from "../@types/types";
23

3-
export const participantsToDial = (participants: Array<ParticipantInstance>, from: string) : ParticipantsToDial => {
4+
export const participantsToDial = (participants: Array<ParticipantInstance>, from: string) : ParticipantToDial[] => {
45

5-
let accumulator: {address: string, proxyAddress: string }[]
6-
return participants.reduce((result, p) => {
6+
const output = participants.reduce((result, p) => {
77
if (p.messagingBinding.type === "sms" && p.messagingBinding.address != from) {
88
console.log(`Adding ${p.messagingBinding.address} to list of numbers to dial.\n`)
99

@@ -14,5 +14,7 @@ export const participantsToDial = (participants: Array<ParticipantInstance>, fro
1414
}
1515

1616
return result;
17-
}, accumulator)
17+
}, [])
18+
19+
return output;
1820
}

tests/services/inboundCall.test.ts

Lines changed: 102 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11

2+
import client from '../../src/twilioClient'
23
import { generateTwiml } from '../../src/services/inboundCall.service'
34
import { getConversationByAddressPair } from "../../src/utils/getConversationByAddressPair.util";
45
import { listConversationParticipants } from '../../src/utils/listConversationParticipants.util';
56
import { participantsToDial } from '../../src/utils/participantsToDial.util'
7+
import { generateConferenceName } from '../../src/utils/generateConferenceName.util'
68

7-
9+
jest.mock('../../src/twilioClient')
810
jest.mock("../../src/utils/getConversationByAddressPair.util")
9-
1011
jest.mock("../../src/utils/listConversationParticipants.util")
11-
1212
jest.mock('../../src/utils/participantsToDial.util')
13+
jest.mock('../../src/utils/generateConferenceName.util')
1314

1415
describe('inbound call service', () => {
1516
const env = process.env
1617
const mockGetConversationAddressPair = jest.mocked(getConversationByAddressPair, true)
1718
const mockListConversationParticipants = jest.mocked(listConversationParticipants, true)
1819
const mockParticipantsToDial = jest.mocked(participantsToDial, true)
1920

20-
2121
beforeEach(() => {
2222
jest.resetModules()
2323
process.env = { ...env }
24-
2524
})
2625

2726
afterEach(() => {
@@ -39,8 +38,20 @@ describe('inbound call service', () => {
3938

4039
expect(result.toString()).toBe("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response><Say voice=\"alice\" language=\"en\">You session has ended, please call our main line.</Say></Response>")
4140
})
41+
42+
it('creates a conference and dials participants if dialList is longer than 1', async () => {
43+
process.env.DOMAIN = 'testdomain.com'
44+
process.env.CALL_ANNOUNCMENT_VOICE = 'alice'
45+
process.env.CALL_ANNOUCEMENT_LANGUAGE = 'en'
46+
process.env.CONNECTING_CALL_ANNOUCEMENT = 'Connecting you to your agent now.'
47+
48+
let mockedClient = jest.mocked(client, true)
49+
const createSpy = jest.fn((callObject) => { return })
50+
const mockedConferenceName = jest.mocked(generateConferenceName, true)
4251

43-
it('creates a conference if dialList is longer than 1', async () => {
52+
mockedClient['calls'] = {
53+
create: (options) => createSpy(options)
54+
} as any
4455

4556
mockGetConversationAddressPair.mockResolvedValue({ conversationSid: 'CH123'} as any)
4657
mockListConversationParticipants.mockResolvedValue([{
@@ -50,16 +61,97 @@ describe('inbound call service', () => {
5061
}
5162
}] as any)
5263

53-
mockParticipantsToDial.mockResolvedValue([{
64+
const mockParticipantToDial = [{
65+
address: '+1112223333',
66+
proxyAddress: '+2223334444'
67+
}, {
68+
address: '+3334445555',
69+
proxyAddress: '+4445556666'
70+
}]
71+
72+
mockParticipantsToDial.mockReturnValueOnce(mockParticipantToDial)
73+
mockedConferenceName.mockReturnValue('test_conference')
74+
75+
const result = await generateTwiml('+1112223333', '+2223334444')
76+
77+
expect(result.toString()).toBe("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response><Say language=\"en\">Connecting you to your agent now.</Say><Dial><Conference endConferenceOnExit=\"true\">test_conference</Conference></Dial></Response>")
78+
79+
expect(createSpy).toHaveBeenNthCalledWith(1,
80+
expect.objectContaining({
81+
to: '+1112223333',
82+
from: '+2223334444',
83+
url: 'https://testdomain.com/join-conference?conferenceName=test_conference'
84+
})
85+
)
86+
87+
expect(createSpy).toHaveBeenNthCalledWith(2,
88+
expect.objectContaining({
89+
to: '+3334445555',
90+
from: '+4445556666',
91+
url: 'https://testdomain.com/join-conference?conferenceName=test_conference'
92+
})
93+
)
94+
})
95+
96+
it('throws an error if an outbound call fails', async () => {
97+
process.env.DOMAIN = 'testdomain.com'
98+
process.env.CALL_ANNOUNCMENT_VOICE = 'alice'
99+
process.env.CALL_ANNOUCEMENT_LANGUAGE = 'en'
100+
process.env.CONNECTING_CALL_ANNOUCEMENT = 'Connecting you to your agent now.'
101+
102+
let mockedClient = jest.mocked(client, true)
103+
const createSpy = jest.fn((callObject) => { return })
104+
const mockedConferenceName = jest.mocked(generateConferenceName, true)
105+
106+
mockedClient['calls'] = {
107+
create: jest.fn(() => { throw new Error('Call fail') })
108+
} as any
109+
110+
mockGetConversationAddressPair.mockResolvedValue({ conversationSid: 'CH123'} as any)
111+
mockListConversationParticipants.mockResolvedValue([{
54112
messagingBinding: {
55113
address: '+1112223333',
56114
proxyAddress: '+2223334444'
57115
}
58-
}])
116+
}] as any)
59117

60-
const result = await generateTwiml('+1112223333', '+2223334444')
118+
const mockParticipantToDial = [{
119+
address: '+1112223333',
120+
proxyAddress: '+2223334444'
121+
}, {
122+
address: '+3334445555',
123+
proxyAddress: '+4445556666'
124+
}]
125+
126+
mockParticipantsToDial.mockReturnValueOnce(mockParticipantToDial)
127+
128+
await expect(generateTwiml('+1112223333', '+2223334444'))
129+
.rejects
130+
.toThrowError('Call fail')
131+
})
132+
133+
it('connects caller with dial if there is only one other participant', async () => {
134+
process.env.CALL_ANNOUNCMENT_VOICE = 'alice'
135+
process.env.CALL_ANNOUCEMENT_LANGUAGE = 'en'
136+
process.env.CONNECTING_CALL_ANNOUCEMENT = 'Connecting you to your agent now.'
137+
138+
mockGetConversationAddressPair.mockResolvedValue({ conversationSid: 'CH123'} as any)
139+
mockListConversationParticipants.mockResolvedValue([{
140+
messagingBinding: {
141+
address: '+1112223333',
142+
proxyAddress: '+2223334444'
143+
}
144+
}] as any)
145+
146+
const mockParticipantToDial = [{
147+
address: '+1112223333',
148+
proxyAddress: '+2223334444'
149+
}]
61150

62-
})
151+
mockParticipantsToDial.mockReturnValueOnce(mockParticipantToDial)
63152

153+
const result = await generateTwiml('+1112223333', '+2223334444')
64154

155+
expect(result.toString()).toBe("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response><Say language=\"en\">Connecting you to your agent now.</Say><Dial callerId=\"+2223334444\"><Number>+1112223333</Number></Dial></Response>")
156+
})
65157
})

0 commit comments

Comments
 (0)