Skip to content

Commit 9a2f7a0

Browse files
committed
test: move test files into lib
1 parent 8aa1c30 commit 9a2f7a0

File tree

7 files changed

+266
-17
lines changed

7 files changed

+266
-17
lines changed

karma.conf.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ if (fs.existsSync(__dirname + '/config.yaml')) {
1414
process.env.AUTH_TOKEN = process.env.AUTH_TOKEN || creds.auth_token;
1515
}
1616

17-
const testFiles = process.env.INTEGRATION_TEST_FILES ?
18-
process.env.INTEGRATION_TEST_FILES.split(',') : ['tests/integration/**/*.ts'];
17+
const testFiles = ["lib/tests/**/*.ts"];
1918

2019
console.log('Test Files:', testFiles);
2120

@@ -102,13 +101,12 @@ module.exports = function(config: any) {
102101
'lib/**/*',
103102
...testFiles,
104103
],
105-
tsconfig: './tsconfig-karma.json',
104+
tsconfig: './tsconfig.json',
106105
},
107106
logLevel: process.env.E2E_LOGLEVEL || config.LOG_DEBUG,
108107
port: 9876,
109108
preprocessors: {
110109
'lib/**/*.ts': 'karma-typescript',
111-
'tests/integration/**/*.ts': 'karma-typescript',
112110
},
113111
reporters: ['spec', 'karma-typescript', 'junit'],
114112
singleRun: true,

lib/tests/lib/token.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const Twilio = require('twilio');
2+
const env = require('../env.js');
3+
4+
function generateAccessToken(identity, ttl, appSid) {
5+
const accessToken = new Twilio.jwt.AccessToken(env.accountSid,
6+
env.apiKeySid,
7+
env.apiKeySecret,
8+
{ ttl: ttl || 300, identity });
9+
10+
accessToken.addGrant(new Twilio.jwt.AccessToken.VoiceGrant({
11+
incomingAllow: true,
12+
outgoingApplicationSid: appSid || env.appSid,
13+
}));
14+
15+
return accessToken.toJwt();
16+
}
17+
18+
function generateCapabilityToken() {
19+
const outgoingScope = new Twilio.jwt.ClientCapability.OutgoingClientScope({
20+
applicationSid: env.appSid
21+
});
22+
23+
const token = new Twilio.jwt.ClientCapability({
24+
accountSid: env.accountSid,
25+
authToken: env.authToken,
26+
});
27+
28+
token.addScope(outgoingScope);
29+
return token.toJwt();
30+
}
31+
32+
module.exports = { generateAccessToken, generateCapabilityToken };

lib/tests/unit/device.ts

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import * as assert from 'assert';
2+
import Call from '../../twilio/call';
3+
import Device from '../../twilio/device';
4+
import { generateAccessToken } from '../lib/token';
5+
6+
// (rrowland) The TwiML expected by these tests can be found in the README.md
7+
8+
describe('Device', function() {
9+
this.timeout(10000);
10+
11+
let device1: Device;
12+
let device2: Device;
13+
let identity1: string;
14+
let identity2: string;
15+
let token1: string;
16+
let token2: string;
17+
18+
before(() => {
19+
identity1 = 'id1-' + Date.now();
20+
identity2 = 'id2-' + Date.now();
21+
token1 = generateAccessToken(identity1);
22+
token2 = generateAccessToken(identity2);
23+
device1 = new Device(token1);
24+
device2 = new Device(token2);
25+
26+
return Promise.all([
27+
device1.register(),
28+
device2.register(),
29+
]);
30+
});
31+
32+
after(() => {
33+
if (device1) {
34+
device1.disconnectAll();
35+
device1.destroy();
36+
}
37+
38+
if (device2) {
39+
device2.disconnectAll();
40+
device2.destroy();
41+
}
42+
});
43+
44+
describe('tokenWillExpire event', () => {
45+
const setupDevice = (tokenTtl: number, tokenRefreshMs: number) => {
46+
const accessToken = generateAccessToken(`device-tokenWillExpire-${Date.now()}`, tokenTtl);
47+
const device = new Device(accessToken, { tokenRefreshMs });
48+
device.on(Device.EventName.Error, () => { /* no-op */ });
49+
return device;
50+
};
51+
52+
it('should emit a "tokenWillExpire" event', async () => {
53+
const device = setupDevice(10, 1000);
54+
await new Promise<void>(async (resolve, reject) => {
55+
let failureTimeout: any = null;
56+
device.on(Device.EventName.TokenWillExpire, () => {
57+
if (failureTimeout) {
58+
clearTimeout(failureTimeout);
59+
}
60+
resolve();
61+
});
62+
await device.register();
63+
failureTimeout = setTimeout(reject, 9500);
64+
});
65+
});
66+
67+
it('should not emit a "tokenWillExpire" event early', async () => {
68+
const device = setupDevice(10, 1000);
69+
await new Promise<void>(async (resolve, reject) => {
70+
let successTimeout: any = null;
71+
device.on(Device.EventName.TokenWillExpire, () => {
72+
if (successTimeout) {
73+
clearTimeout(successTimeout);
74+
}
75+
reject();
76+
});
77+
await device.register();
78+
// NOTE(csantos): Expected time before tokenWillExpire event is raised is about 9s
79+
// for a ttl: 10s and tokenRefreshMS: 1000, meaning we should resolve the test at less than 9s.
80+
// However, the ttl returned from signaling, which we use to calculate the timeout,
81+
// sometimes returns 1s less from the original ttl we provided. So let's account for that.
82+
// Instead of resolving the test before 9s, we should do it before 8s instead.
83+
successTimeout = setTimeout(resolve, 7500);
84+
});
85+
});
86+
87+
it('should emit a "tokenWillExpire" event as soon as possible if the option is smaller than the ttl', async () => {
88+
const device = setupDevice(5, 10000);
89+
90+
const eventPromises = Promise.all([
91+
Device.EventName.TokenWillExpire,
92+
Device.EventName.Registering,
93+
].map((eventName: string) => new Promise<number>(res => {
94+
device.on(eventName, () => res(Date.now()));
95+
})));
96+
97+
device.register();
98+
99+
const [expireTime, registeringTime] = await eventPromises;
100+
const diff = Math.abs(expireTime - registeringTime);
101+
// ttl returned from signaling, which we use to calculate the timeout,
102+
// sometimes returns 1s less from the original ttl we provided. So let's account for that.
103+
assert(diff < 2000, `event time occurred too late; diff: ${diff}`);
104+
});
105+
});
106+
107+
describe('device 1 calls device 2', () => {
108+
let call1: Call;
109+
let call2: Call;
110+
111+
before(() => new Promise<void>(async resolve => {
112+
device2.once(Device.EventName.Incoming, (call: Call) => {
113+
call2 = call;
114+
resolve();
115+
});
116+
call1 = await (device1['connect'] as any)({
117+
params: { To: identity2, Custom1: 'foo + bar', Custom2: undefined, Custom3: '我不吃蛋' }
118+
});
119+
}));
120+
121+
describe('and device 2 accepts', () => {
122+
beforeEach(() => {
123+
if (!call1 || !call2) {
124+
throw new Error(`Calls weren't both open at beforeEach`);
125+
}
126+
});
127+
128+
it('should connect the call', (done) => {
129+
call2.once('accept', () => done());
130+
call2.accept();
131+
});
132+
133+
it('should stay open 3 seconds', (done) => {
134+
function fail() {
135+
call1.removeListener('disconnect', fail);
136+
call2.removeListener('disconnect', fail);
137+
done(new Error('Expected the call to stay open for 3 seconds'));
138+
}
139+
140+
call1.once('disconnect', fail);
141+
call2.once('disconnect', fail);
142+
143+
setTimeout(() => {
144+
call1.removeListener('disconnect', fail);
145+
call2.removeListener('disconnect', fail);
146+
done();
147+
}, 3000);
148+
});
149+
150+
it('should set callerInfo to null on both calls', () => {
151+
assert.equal(call1!.callerInfo, null);
152+
assert.equal(call2!.callerInfo, null);
153+
});
154+
155+
it('should be using the PCMU codec for both calls', (done) => {
156+
let codec1: string | null | undefined = null;
157+
158+
function setCodec(sample: any) {
159+
if (codec1 === null) {
160+
codec1 = sample.codecName;
161+
} else {
162+
if (codec1 === 'opus' || sample.codecName === 'opus') {
163+
done(new Error('Codec is opus'));
164+
} else {
165+
done();
166+
}
167+
}
168+
}
169+
170+
const monitor1: any = call1['_monitor'];
171+
const monitor2: any = call2['_monitor'];
172+
if (!monitor1 || !monitor2) {
173+
done(new Error('Missing monitor'));
174+
}
175+
monitor1.once('sample', setCodec);
176+
monitor2.once('sample', setCodec);
177+
});
178+
179+
it('should update network priority to high if supported', () => {
180+
if (!call2 || !call2['_mediaHandler'] || !call2['_mediaHandler']._sender) {
181+
throw new Error('Expected sender to be present');
182+
}
183+
const sender = call2['_mediaHandler']._sender;
184+
const params = sender.getParameters();
185+
const encoding = params.encodings && params.encodings[0];
186+
187+
if (!params.priority && !encoding) {
188+
// Not supported by browser.
189+
return;
190+
}
191+
192+
assert(params.priority === 'high'
193+
|| (encoding && encoding.priority === 'high')
194+
|| (encoding && encoding.networkPriority === 'high'));
195+
});
196+
197+
it('should receive the correct custom parameters from the TwiML app', () => {
198+
const comparator =
199+
([customParamKeyA]: string[], [customParamKeyB]: string[]) => {
200+
return customParamKeyA.localeCompare(customParamKeyB);
201+
};
202+
assert.deepEqual(
203+
Array.from(call2.customParameters.entries()).sort(comparator),
204+
[
205+
['custom + param', '我不吃蛋'],
206+
['custom1', 'foo + bar'],
207+
['custom2', 'undefined'],
208+
['custom3', '我不吃蛋'],
209+
['duplicate', '123456'],
210+
['foobar', 'some + value'],
211+
],
212+
);
213+
});
214+
215+
it('should post metrics', (done) => {
216+
const publisher = call1['_publisher'];
217+
(publisher as any)['_request'] = { post: (params: any, err: Function) => {
218+
if (/EndpointMetrics$/.test(params.url)) {
219+
done();
220+
}
221+
}};
222+
});
223+
224+
it('should hang up', (done) => {
225+
call1.once('disconnect', () => done());
226+
call2.disconnect();
227+
});
228+
});
229+
});
230+
});

tsconfig-base.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"moduleResolution": "node",
88
"noImplicitAny": true,
99
"noImplicitThis": true,
10+
"rootDir": "lib",
1011
"inlineSourceMap": true,
1112
"strictNullChecks": true
1213
},

tsconfig-esm.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"compilerOptions": {
44
"target": "es6",
55
"module": "ESNext",
6-
"outDir": "esm",
7-
"rootDir": "lib",
6+
"outDir": "esm"
87
}
98
}

tsconfig-karma.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

tsconfig.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@
44
"target": "es5",
55
"module": "commonjs",
66
"outDir": "es5",
7-
"rootDir": "lib",
87
}
98
}

0 commit comments

Comments
 (0)