client.js 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161
  1. const VError = require('verror');
  2. const net = require('net');
  3. const http2 = require('http2');
  4. const debug = require('debug')('apn');
  5. const credentials = require('../lib/credentials')({
  6. logger: debug,
  7. });
  8. const TEST_PORT = 30939;
  9. const LOAD_TEST_BATCH_SIZE = 2000;
  10. const config = require('../lib/config')({
  11. logger: debug,
  12. prepareCertificate: () => ({}), // credentials.certificate,
  13. prepareToken: credentials.token,
  14. prepareCA: credentials.ca,
  15. });
  16. const Client = require('../lib/client')({
  17. logger: debug,
  18. config,
  19. http2,
  20. });
  21. debug.log = console.log.bind(console);
  22. // function builtNotification() {
  23. // return {
  24. // headers: {},
  25. // body: JSON.stringify({ aps: { badge: 1 } }),
  26. // };
  27. // }
  28. // function FakeStream(deviceId, statusCode, response) {
  29. // const fakeStream = new stream.Transform({
  30. // transform: sinon.spy(function(chunk, encoding, callback) {
  31. // expect(this.headers).to.be.calledOnce;
  32. //
  33. // const headers = this.headers.firstCall.args[0];
  34. // expect(headers[":path"].substring(10)).to.equal(deviceId);
  35. //
  36. // this.emit("headers", {
  37. // ":status": statusCode
  38. // });
  39. // callback(null, Buffer.from(JSON.stringify(response) || ""));
  40. // })
  41. // });
  42. // fakeStream.headers = sinon.stub();
  43. //
  44. // return fakeStream;
  45. // }
  46. // XXX these may be flaky in CI due to being sensitive to timing,
  47. // and if a test case crashes, then others may get stuck.
  48. //
  49. // Try to fix this if any issues come up.
  50. describe('Client', () => {
  51. let server;
  52. let client;
  53. const MOCK_BODY = '{"mock-key":"mock-value"}';
  54. const MOCK_DEVICE_TOKEN = 'abcf0123abcf0123abcf0123abcf0123abcf0123abcf0123abcf0123abcf0123';
  55. // Create an insecure http2 client for unit testing.
  56. // (APNS would use https://, not http://)
  57. // (It's probably possible to allow accepting invalid certificates instead,
  58. // but that's not the most important point of these tests)
  59. const createClient = (port, timeout = 500) => {
  60. const c = new Client({
  61. port: TEST_PORT,
  62. address: '127.0.0.1',
  63. });
  64. c._mockOverrideUrl = `http://127.0.0.1:${port}`;
  65. c.config.port = port;
  66. c.config.address = '127.0.0.1';
  67. c.config.requestTimeout = timeout;
  68. return c;
  69. };
  70. // Create an insecure server for unit testing.
  71. const createAndStartMockServer = (port, cb) => {
  72. server = http2.createServer((req, res) => {
  73. const buffers = [];
  74. req.on('data', data => buffers.push(data));
  75. req.on('end', () => {
  76. const requestBody = Buffer.concat(buffers).toString('utf-8');
  77. cb(req, res, requestBody);
  78. });
  79. });
  80. server.listen(port);
  81. server.on('error', err => {
  82. expect.fail(`unexpected error ${err}`);
  83. });
  84. // Don't block the tests if this server doesn't shut down properly
  85. server.unref();
  86. return server;
  87. };
  88. const createAndStartMockLowLevelServer = (port, cb) => {
  89. server = http2.createServer();
  90. server.on('stream', cb);
  91. server.listen(port);
  92. server.on('error', err => {
  93. expect.fail(`unexpected error ${err}`);
  94. });
  95. // Don't block the tests if this server doesn't shut down properly
  96. server.unref();
  97. return server;
  98. };
  99. afterEach(done => {
  100. const closeServer = () => {
  101. if (server) {
  102. server.close();
  103. server = null;
  104. }
  105. done();
  106. };
  107. if (client) {
  108. client.shutdown(closeServer);
  109. client = null;
  110. } else {
  111. closeServer();
  112. }
  113. });
  114. it('Treats HTTP 200 responses as successful', async () => {
  115. let didRequest = false;
  116. let establishedConnections = 0;
  117. let requestsServed = 0;
  118. server = createAndStartMockServer(TEST_PORT, (req, res, requestBody) => {
  119. expect(req.headers).to.deep.equal({
  120. ':authority': '127.0.0.1',
  121. ':method': 'POST',
  122. ':path': `/3/device/${MOCK_DEVICE_TOKEN}`,
  123. ':scheme': 'https',
  124. 'apns-someheader': 'somevalue',
  125. });
  126. expect(requestBody).to.equal(MOCK_BODY);
  127. // res.setHeader('X-Foo', 'bar');
  128. // res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  129. res.writeHead(200);
  130. res.end('');
  131. requestsServed += 1;
  132. didRequest = true;
  133. });
  134. server.on('connection', () => (establishedConnections += 1));
  135. await new Promise(resolve => server.on('listening', resolve));
  136. client = createClient(TEST_PORT);
  137. const runSuccessfulRequest = async () => {
  138. const mockHeaders = { 'apns-someheader': 'somevalue' };
  139. const mockNotification = {
  140. headers: mockHeaders,
  141. body: MOCK_BODY,
  142. };
  143. const mockDevice = MOCK_DEVICE_TOKEN;
  144. const result = await client.write(mockNotification, mockDevice);
  145. expect(result).to.deep.equal({ device: MOCK_DEVICE_TOKEN });
  146. expect(didRequest).to.be.true;
  147. };
  148. expect(establishedConnections).to.equal(0); // should not establish a connection until it's needed
  149. // Validate that when multiple valid requests arrive concurrently,
  150. // only one HTTP/2 connection gets established
  151. await Promise.all([
  152. runSuccessfulRequest(),
  153. runSuccessfulRequest(),
  154. runSuccessfulRequest(),
  155. runSuccessfulRequest(),
  156. runSuccessfulRequest(),
  157. ]);
  158. didRequest = false;
  159. await runSuccessfulRequest();
  160. expect(establishedConnections).to.equal(1); // should establish a connection to the server and reuse it
  161. expect(requestsServed).to.equal(6);
  162. });
  163. // Assert that this doesn't crash when a large batch of requests are requested simultaneously
  164. it('Treats HTTP 200 responses as successful (load test for a batch of requests)', async function () {
  165. this.timeout(10000);
  166. let establishedConnections = 0;
  167. let requestsServed = 0;
  168. server = createAndStartMockServer(TEST_PORT, (req, res, requestBody) => {
  169. expect(req.headers).to.deep.equal({
  170. ':authority': '127.0.0.1',
  171. ':method': 'POST',
  172. ':path': `/3/device/${MOCK_DEVICE_TOKEN}`,
  173. ':scheme': 'https',
  174. 'apns-someheader': 'somevalue',
  175. });
  176. expect(requestBody).to.equal(MOCK_BODY);
  177. // Set a timeout of 100 to simulate latency to a remote server.
  178. setTimeout(() => {
  179. res.writeHead(200);
  180. res.end('');
  181. requestsServed += 1;
  182. }, 100);
  183. });
  184. server.on('connection', () => (establishedConnections += 1));
  185. await new Promise(resolve => server.on('listening', resolve));
  186. client = createClient(TEST_PORT, 1500);
  187. const runSuccessfulRequest = async () => {
  188. const mockHeaders = { 'apns-someheader': 'somevalue' };
  189. const mockNotification = {
  190. headers: mockHeaders,
  191. body: MOCK_BODY,
  192. };
  193. const mockDevice = MOCK_DEVICE_TOKEN;
  194. const result = await client.write(mockNotification, mockDevice);
  195. expect(result).to.deep.equal({ device: MOCK_DEVICE_TOKEN });
  196. };
  197. expect(establishedConnections).to.equal(0); // should not establish a connection until it's needed
  198. // Validate that when multiple valid requests arrive concurrently,
  199. // only one HTTP/2 connection gets established
  200. const promises = [];
  201. for (let i = 0; i < LOAD_TEST_BATCH_SIZE; i++) {
  202. promises.push(runSuccessfulRequest());
  203. }
  204. await Promise.all(promises);
  205. expect(establishedConnections).to.equal(1); // should establish a connection to the server and reuse it
  206. expect(requestsServed).to.equal(LOAD_TEST_BATCH_SIZE);
  207. });
  208. // https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/handling_notification_responses_from_apns
  209. it('JSON decodes HTTP 400 responses', async () => {
  210. let didRequest = false;
  211. let establishedConnections = 0;
  212. server = createAndStartMockServer(TEST_PORT, (req, res, requestBody) => {
  213. expect(requestBody).to.equal(MOCK_BODY);
  214. // res.setHeader('X-Foo', 'bar');
  215. // res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  216. res.writeHead(400);
  217. res.end('{"reason": "BadDeviceToken"}');
  218. didRequest = true;
  219. });
  220. server.on('connection', () => (establishedConnections += 1));
  221. await new Promise(resolve => server.on('listening', resolve));
  222. client = createClient(TEST_PORT);
  223. const infoMessages = [];
  224. const errorMessages = [];
  225. const mockInfoLogger = message => {
  226. infoMessages.push(message);
  227. };
  228. const mockErrorLogger = message => {
  229. errorMessages.push(message);
  230. };
  231. mockInfoLogger.enabled = true;
  232. mockErrorLogger.enabled = true;
  233. client.setLogger(mockInfoLogger, mockErrorLogger);
  234. const runRequestWithBadDeviceToken = async () => {
  235. const mockHeaders = { 'apns-someheader': 'somevalue' };
  236. const mockNotification = {
  237. headers: mockHeaders,
  238. body: MOCK_BODY,
  239. };
  240. const mockDevice = MOCK_DEVICE_TOKEN;
  241. const result = await client.write(mockNotification, mockDevice);
  242. expect(result).to.deep.equal({
  243. device: MOCK_DEVICE_TOKEN,
  244. response: {
  245. reason: 'BadDeviceToken',
  246. },
  247. status: 400,
  248. });
  249. expect(didRequest).to.be.true;
  250. didRequest = false;
  251. };
  252. await runRequestWithBadDeviceToken();
  253. await runRequestWithBadDeviceToken();
  254. expect(establishedConnections).to.equal(1); // should establish a connection to the server and reuse it
  255. expect(infoMessages).to.deep.equal([
  256. 'Session connected',
  257. 'Request ended with status 400 and responseData: {"reason": "BadDeviceToken"}',
  258. 'Request ended with status 400 and responseData: {"reason": "BadDeviceToken"}',
  259. ]);
  260. expect(errorMessages).to.deep.equal([]);
  261. });
  262. // node-apn started closing connections in response to a bug report where HTTP 500 responses
  263. // persisted until a new connection was reopened
  264. it('Closes connections when HTTP 500 responses are received', async () => {
  265. let establishedConnections = 0;
  266. let responseDelay = 50;
  267. server = createAndStartMockServer(TEST_PORT, (req, res, requestBody) => {
  268. // Wait 50ms before sending the responses in parallel
  269. setTimeout(() => {
  270. expect(requestBody).to.equal(MOCK_BODY);
  271. res.writeHead(500);
  272. res.end('{"reason": "InternalServerError"}');
  273. }, responseDelay);
  274. });
  275. server.on('connection', () => (establishedConnections += 1));
  276. await new Promise(resolve => server.on('listening', resolve));
  277. client = createClient(TEST_PORT);
  278. const runRequestWithInternalServerError = async () => {
  279. const mockHeaders = { 'apns-someheader': 'somevalue' };
  280. const mockNotification = {
  281. headers: mockHeaders,
  282. body: MOCK_BODY,
  283. };
  284. const mockDevice = MOCK_DEVICE_TOKEN;
  285. const result = await client.write(mockNotification, mockDevice);
  286. expect(result).to.exist;
  287. expect(result.device).to.equal(MOCK_DEVICE_TOKEN);
  288. expect(result.error).to.be.an.instanceof(VError);
  289. expect(result.error.message).to.have.string('stream ended unexpectedly');
  290. };
  291. await runRequestWithInternalServerError();
  292. await runRequestWithInternalServerError();
  293. await runRequestWithInternalServerError();
  294. expect(establishedConnections).to.equal(3); // should close and establish new connections on http 500
  295. // Validate that nothing wrong happens when multiple HTTP 500s are received simultaneously.
  296. // (no segfaults, all promises get resolved, etc.)
  297. responseDelay = 50;
  298. await Promise.all([
  299. runRequestWithInternalServerError(),
  300. runRequestWithInternalServerError(),
  301. runRequestWithInternalServerError(),
  302. runRequestWithInternalServerError(),
  303. ]);
  304. expect(establishedConnections).to.equal(4); // should close and establish new connections on http 500
  305. });
  306. it('Handles unexpected invalid JSON responses', async () => {
  307. let establishedConnections = 0;
  308. const responseDelay = 0;
  309. server = createAndStartMockServer(TEST_PORT, (req, res, requestBody) => {
  310. // Wait 50ms before sending the responses in parallel
  311. setTimeout(() => {
  312. expect(requestBody).to.equal(MOCK_BODY);
  313. res.writeHead(500);
  314. res.end('PC LOAD LETTER');
  315. }, responseDelay);
  316. });
  317. server.on('connection', () => (establishedConnections += 1));
  318. await new Promise(resolve => server.on('listening', resolve));
  319. client = createClient(TEST_PORT);
  320. const runRequestWithInternalServerError = async () => {
  321. const mockHeaders = { 'apns-someheader': 'somevalue' };
  322. const mockNotification = {
  323. headers: mockHeaders,
  324. body: MOCK_BODY,
  325. };
  326. const mockDevice = MOCK_DEVICE_TOKEN;
  327. const result = await client.write(mockNotification, mockDevice);
  328. // Should not happen, but if it does, the promise should resolve with an error
  329. expect(result.device).to.equal(MOCK_DEVICE_TOKEN);
  330. expect(result.error.message.startsWith('Unexpected error processing APNs response: Unexpected token')).to.equal(true);
  331. };
  332. await runRequestWithInternalServerError();
  333. await runRequestWithInternalServerError();
  334. expect(establishedConnections).to.equal(1); // Currently reuses the connection.
  335. });
  336. it('Handles APNs timeouts', async () => {
  337. let didGetRequest = false;
  338. let didGetResponse = false;
  339. server = createAndStartMockServer(TEST_PORT, (req, res, requestBody) => {
  340. didGetRequest = true;
  341. setTimeout(() => {
  342. res.writeHead(200);
  343. res.end('');
  344. didGetResponse = true;
  345. }, 1900);
  346. });
  347. client = createClient(TEST_PORT);
  348. const onListeningPromise = new Promise(resolve => server.on('listening', resolve));
  349. await onListeningPromise;
  350. const mockHeaders = { 'apns-someheader': 'somevalue' };
  351. const mockNotification = {
  352. headers: mockHeaders,
  353. body: MOCK_BODY,
  354. };
  355. const mockDevice = MOCK_DEVICE_TOKEN;
  356. const performRequestExpectingTimeout = async () => {
  357. const result = await client.write(mockNotification, mockDevice);
  358. expect(result).to.deep.equal({
  359. device: MOCK_DEVICE_TOKEN,
  360. error: new VError('apn write timeout'),
  361. });
  362. expect(didGetRequest).to.be.true;
  363. expect(didGetResponse).to.be.false;
  364. };
  365. await performRequestExpectingTimeout();
  366. didGetResponse = false;
  367. didGetRequest = false;
  368. // Should be able to have multiple in flight requests all get notified that the server is shutting down
  369. await Promise.all([
  370. performRequestExpectingTimeout(),
  371. performRequestExpectingTimeout(),
  372. performRequestExpectingTimeout(),
  373. performRequestExpectingTimeout(),
  374. ]);
  375. });
  376. it('Handles goaway frames', async () => {
  377. let didGetRequest = false;
  378. let establishedConnections = 0;
  379. server = createAndStartMockLowLevelServer(TEST_PORT, stream => {
  380. const { session } = stream;
  381. const errorCode = 1;
  382. didGetRequest = true;
  383. session.goaway(errorCode);
  384. });
  385. server.on('connection', () => (establishedConnections += 1));
  386. client = createClient(TEST_PORT);
  387. const onListeningPromise = new Promise(resolve => server.on('listening', resolve));
  388. await onListeningPromise;
  389. const mockHeaders = { 'apns-someheader': 'somevalue' };
  390. const mockNotification = {
  391. headers: mockHeaders,
  392. body: MOCK_BODY,
  393. };
  394. const mockDevice = MOCK_DEVICE_TOKEN;
  395. const performRequestExpectingGoAway = async () => {
  396. const result = await client.write(mockNotification, mockDevice);
  397. expect(result.device).to.equal(MOCK_DEVICE_TOKEN);
  398. expect(result.error).to.be.an.instanceof(VError);
  399. expect(didGetRequest).to.be.true;
  400. didGetRequest = false;
  401. };
  402. await performRequestExpectingGoAway();
  403. await performRequestExpectingGoAway();
  404. expect(establishedConnections).to.equal(2);
  405. });
  406. it('Handles unexpected protocol errors (no response sent)', async () => {
  407. let didGetRequest = false;
  408. let establishedConnections = 0;
  409. let responseTimeout = 0;
  410. server = createAndStartMockLowLevelServer(TEST_PORT, stream => {
  411. setTimeout(() => {
  412. const { session } = stream;
  413. didGetRequest = true;
  414. if (session) {
  415. session.destroy();
  416. }
  417. }, responseTimeout);
  418. });
  419. server.on('connection', () => (establishedConnections += 1));
  420. client = createClient(TEST_PORT);
  421. const onListeningPromise = new Promise(resolve => server.on('listening', resolve));
  422. await onListeningPromise;
  423. const mockHeaders = { 'apns-someheader': 'somevalue' };
  424. const mockNotification = {
  425. headers: mockHeaders,
  426. body: MOCK_BODY,
  427. };
  428. const mockDevice = MOCK_DEVICE_TOKEN;
  429. const performRequestExpectingDisconnect = async () => {
  430. const result = await client.write(mockNotification, mockDevice);
  431. expect(result).to.deep.equal({
  432. device: MOCK_DEVICE_TOKEN,
  433. error: new VError('stream ended unexpectedly with status null and empty body'),
  434. });
  435. expect(didGetRequest).to.be.true;
  436. };
  437. await performRequestExpectingDisconnect();
  438. didGetRequest = false;
  439. await performRequestExpectingDisconnect();
  440. didGetRequest = false;
  441. expect(establishedConnections).to.equal(2);
  442. responseTimeout = 10;
  443. await Promise.all([
  444. performRequestExpectingDisconnect(),
  445. performRequestExpectingDisconnect(),
  446. performRequestExpectingDisconnect(),
  447. performRequestExpectingDisconnect(),
  448. ]);
  449. expect(establishedConnections).to.equal(3);
  450. });
  451. it('Establishes a connection through a proxy server', async () => {
  452. let didRequest = false;
  453. let establishedConnections = 0;
  454. let requestsServed = 0;
  455. server = createAndStartMockServer(TEST_PORT, (req, res, requestBody) => {
  456. expect(req.headers).to.deep.equal({
  457. ':authority': '127.0.0.1',
  458. ':method': 'POST',
  459. ':path': `/3/device/${MOCK_DEVICE_TOKEN}`,
  460. ':scheme': 'https',
  461. 'apns-someheader': 'somevalue',
  462. });
  463. expect(requestBody).to.equal(MOCK_BODY);
  464. // res.setHeader('X-Foo', 'bar');
  465. // res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  466. res.writeHead(200);
  467. res.end('');
  468. requestsServed += 1;
  469. didRequest = true;
  470. });
  471. server.on('connection', () => (establishedConnections += 1));
  472. await new Promise(resolve => server.once('listening', resolve));
  473. // Proxy forwards all connections to TEST_PORT
  474. const proxy = net.createServer(clientSocket => {
  475. clientSocket.once('data', () => {
  476. const serverSocket = net.createConnection(TEST_PORT, () => {
  477. clientSocket.write('HTTP/1.1 200 OK\r\n\r\n');
  478. clientSocket.pipe(serverSocket);
  479. setTimeout(() => {
  480. serverSocket.pipe(clientSocket);
  481. }, 1);
  482. });
  483. });
  484. clientSocket.on('error', () => {});
  485. });
  486. await new Promise(resolve => proxy.listen(3128, resolve));
  487. // Client configured with a port that the server is not listening on
  488. client = createClient(TEST_PORT + 1);
  489. // So without adding a proxy config request will fail with a network error
  490. client.config.proxy = { host: '127.0.0.1', port: 3128 };
  491. const runSuccessfulRequest = async () => {
  492. const mockHeaders = { 'apns-someheader': 'somevalue' };
  493. const mockNotification = {
  494. headers: mockHeaders,
  495. body: MOCK_BODY,
  496. };
  497. const mockDevice = MOCK_DEVICE_TOKEN;
  498. const result = await client.write(mockNotification, mockDevice);
  499. expect(result).to.deep.equal({ device: MOCK_DEVICE_TOKEN });
  500. expect(didRequest).to.be.true;
  501. };
  502. expect(establishedConnections).to.equal(0); // should not establish a connection until it's needed
  503. // Validate that when multiple valid requests arrive concurrently,
  504. // only one HTTP/2 connection gets established
  505. await Promise.all([
  506. runSuccessfulRequest(),
  507. runSuccessfulRequest(),
  508. runSuccessfulRequest(),
  509. runSuccessfulRequest(),
  510. runSuccessfulRequest(),
  511. ]);
  512. didRequest = false;
  513. await runSuccessfulRequest();
  514. expect(establishedConnections).to.equal(1); // should establish a connection to the server and reuse it
  515. expect(requestsServed).to.equal(6);
  516. proxy.close();
  517. });
  518. // let fakes, Client;
  519. // beforeEach(() => {
  520. // fakes = {
  521. // config: sinon.stub(),
  522. // EndpointManager: sinon.stub(),
  523. // endpointManager: new EventEmitter(),
  524. // };
  525. // fakes.EndpointManager.returns(fakes.endpointManager);
  526. // fakes.endpointManager.shutdown = sinon.stub();
  527. // Client = require("../lib/client")(fakes);
  528. // });
  529. // describe("constructor", () => {
  530. // it("prepares the configuration with passed options", () => {
  531. // let options = { production: true };
  532. // let client = new Client(options);
  533. // expect(fakes.config).to.be.calledWith(options);
  534. // });
  535. // describe("EndpointManager instance", function() {
  536. // it("is created", () => {
  537. // let client = new Client();
  538. // expect(fakes.EndpointManager).to.be.calledOnce;
  539. // expect(fakes.EndpointManager).to.be.calledWithNew;
  540. // });
  541. // it("is passed the prepared configuration", () => {
  542. // const returnSentinel = { "configKey": "configValue"};
  543. // fakes.config.returns(returnSentinel);
  544. // let client = new Client({});
  545. // expect(fakes.EndpointManager).to.be.calledWith(returnSentinel);
  546. // });
  547. // });
  548. // });
  549. describe('write', () => {
  550. // beforeEach(() => {
  551. // fakes.config.returnsArg(0);
  552. // fakes.endpointManager.getStream = sinon.stub();
  553. // fakes.EndpointManager.returns(fakes.endpointManager);
  554. // });
  555. // context("a stream is available", () => {
  556. // let client;
  557. // context("transmission succeeds", () => {
  558. // beforeEach( () => {
  559. // client = new Client( { address: "testapi" } );
  560. // fakes.stream = new FakeStream("abcd1234", "200");
  561. // fakes.endpointManager.getStream.onCall(0).returns(fakes.stream);
  562. // });
  563. // it("attempts to acquire one stream", () => {
  564. // return client.write(builtNotification(), "abcd1234")
  565. // .then(() => {
  566. // expect(fakes.endpointManager.getStream).to.be.calledOnce;
  567. // });
  568. // });
  569. // describe("headers", () => {
  570. // it("sends the required HTTP/2 headers", () => {
  571. // return client.write(builtNotification(), "abcd1234")
  572. // .then(() => {
  573. // expect(fakes.stream.headers).to.be.calledWithMatch( {
  574. // ":scheme": "https",
  575. // ":method": "POST",
  576. // ":authority": "testapi",
  577. // ":path": "/3/device/abcd1234",
  578. // });
  579. // });
  580. // });
  581. // it("does not include apns headers when not required", () => {
  582. // return client.write(builtNotification(), "abcd1234")
  583. // .then(() => {
  584. // ["apns-id", "apns-priority", "apns-expiration", "apns-topic"].forEach( header => {
  585. // expect(fakes.stream.headers).to.not.be.calledWithMatch(sinon.match.has(header));
  586. // });
  587. // });
  588. // });
  589. // it("sends the notification-specific apns headers when specified", () => {
  590. // let notification = builtNotification();
  591. // notification.headers = {
  592. // "apns-id": "123e4567-e89b-12d3-a456-42665544000",
  593. // "apns-priority": 5,
  594. // "apns-expiration": 123,
  595. // "apns-topic": "io.apn.node",
  596. // };
  597. // return client.write(notification, "abcd1234")
  598. // .then(() => {
  599. // expect(fakes.stream.headers).to.be.calledWithMatch( {
  600. // "apns-id": "123e4567-e89b-12d3-a456-42665544000",
  601. // "apns-priority": 5,
  602. // "apns-expiration": 123,
  603. // "apns-topic": "io.apn.node",
  604. // });
  605. // });
  606. // });
  607. // context("when token authentication is enabled", () => {
  608. // beforeEach(() => {
  609. // fakes.token = {
  610. // generation: 0,
  611. // current: "fake-token",
  612. // regenerate: sinon.stub(),
  613. // isExpired: sinon.stub()
  614. // };
  615. // client = new Client( { address: "testapi", token: fakes.token } );
  616. // fakes.stream = new FakeStream("abcd1234", "200");
  617. // fakes.endpointManager.getStream.onCall(0).returns(fakes.stream);
  618. // });
  619. // it("sends the bearer token", () => {
  620. // let notification = builtNotification();
  621. // return client.write(notification, "abcd1234").then(() => {
  622. // expect(fakes.stream.headers).to.be.calledWithMatch({
  623. // authorization: "bearer fake-token",
  624. // });
  625. // });
  626. // });
  627. // });
  628. // context("when token authentication is disabled", () => {
  629. // beforeEach(() => {
  630. // client = new Client( { address: "testapi" } );
  631. // fakes.stream = new FakeStream("abcd1234", "200");
  632. // fakes.endpointManager.getStream.onCall(0).returns(fakes.stream);
  633. // });
  634. // it("does not set an authorization header", () => {
  635. // let notification = builtNotification();
  636. // return client.write(notification, "abcd1234").then(() => {
  637. // expect(fakes.stream.headers.firstCall.args[0]).to.not.have.property("authorization");
  638. // })
  639. // });
  640. // })
  641. // });
  642. // it("writes the notification data to the pipe", () => {
  643. // const notification = builtNotification();
  644. // return client.write(notification, "abcd1234")
  645. // .then(() => {
  646. // expect(fakes.stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(notification.body)));
  647. // });
  648. // });
  649. // it("ends the stream", () => {
  650. // sinon.spy(fakes.stream, "end");
  651. // return client.write(builtNotification(), "abcd1234")
  652. // .then(() => {
  653. // expect(fakes.stream.end).to.be.calledOnce;
  654. // });
  655. // });
  656. // it("resolves with the device token", () => {
  657. // return expect(client.write(builtNotification(), "abcd1234"))
  658. // .to.become({ device: "abcd1234" });
  659. // });
  660. // });
  661. // context("error occurs", () => {
  662. // let promise;
  663. // context("general case", () => {
  664. // beforeEach(() => {
  665. // const client = new Client( { address: "testapi" } );
  666. // fakes.stream = new FakeStream("abcd1234", "400", { "reason" : "BadDeviceToken" });
  667. // fakes.endpointManager.getStream.onCall(0).returns(fakes.stream);
  668. // promise = client.write(builtNotification(), "abcd1234");
  669. // });
  670. // it("resolves with the device token, status code and response", () => {
  671. // return expect(promise).to.eventually.deep.equal({ status: "400", device: "abcd1234", response: { reason: "BadDeviceToken" }});
  672. // });
  673. // })
  674. // context("ExpiredProviderToken", () => {
  675. // beforeEach(() => {
  676. // let tokenGenerator = sinon.stub().returns("fake-token");
  677. // const client = new Client( { address: "testapi", token: tokenGenerator });
  678. // })
  679. // });
  680. // });
  681. // context("stream ends without completing request", () => {
  682. // let promise;
  683. // beforeEach(() => {
  684. // const client = new Client( { address: "testapi" } );
  685. // fakes.stream = new stream.Transform({
  686. // transform: function(chunk, encoding, callback) {}
  687. // });
  688. // fakes.stream.headers = sinon.stub();
  689. // fakes.endpointManager.getStream.onCall(0).returns(fakes.stream);
  690. // promise = client.write(builtNotification(), "abcd1234");
  691. // fakes.stream.push(null);
  692. // });
  693. // it("resolves with an object containing the device token", () => {
  694. // return expect(promise).to.eventually.have.property("device", "abcd1234");
  695. // });
  696. // it("resolves with an object containing an error", () => {
  697. // return promise.then( (response) => {
  698. // expect(response).to.have.property("error");
  699. // expect(response.error).to.be.an.instanceOf(Error);
  700. // expect(response.error).to.match(/stream ended unexpectedly/);
  701. // });
  702. // });
  703. // });
  704. // context("stream is unprocessed", () => {
  705. // let promise;
  706. // beforeEach(() => {
  707. // const client = new Client( { address: "testapi" } );
  708. // fakes.stream = new stream.Transform({
  709. // transform: function(chunk, encoding, callback) {}
  710. // });
  711. // fakes.stream.headers = sinon.stub();
  712. // fakes.secondStream = FakeStream("abcd1234", "200");
  713. // fakes.endpointManager.getStream.onCall(0).returns(fakes.stream);
  714. // fakes.endpointManager.getStream.onCall(1).returns(fakes.secondStream);
  715. // promise = client.write(builtNotification(), "abcd1234");
  716. // setImmediate(() => {
  717. // fakes.stream.emit("unprocessed");
  718. // });
  719. // });
  720. // it("attempts to resend on a new stream", function (done) {
  721. // setImmediate(() => {
  722. // expect(fakes.endpointManager.getStream).to.be.calledTwice;
  723. // done();
  724. // });
  725. // });
  726. // it("fulfills the promise", () => {
  727. // return expect(promise).to.eventually.deep.equal({ device: "abcd1234" });
  728. // });
  729. // });
  730. // context("stream error occurs", () => {
  731. // let promise;
  732. // beforeEach(() => {
  733. // const client = new Client( { address: "testapi" } );
  734. // fakes.stream = new stream.Transform({
  735. // transform: function(chunk, encoding, callback) {}
  736. // });
  737. // fakes.stream.headers = sinon.stub();
  738. // fakes.endpointManager.getStream.onCall(0).returns(fakes.stream);
  739. // promise = client.write(builtNotification(), "abcd1234");
  740. // });
  741. // context("passing an Error", () => {
  742. // beforeEach(() => {
  743. // fakes.stream.emit("error", new Error("stream error"));
  744. // });
  745. // it("resolves with an object containing the device token", () => {
  746. // return expect(promise).to.eventually.have.property("device", "abcd1234");
  747. // });
  748. // it("resolves with an object containing a wrapped error", () => {
  749. // return promise.then( (response) => {
  750. // expect(response.error).to.be.an.instanceOf(Error);
  751. // expect(response.error).to.match(/apn write failed/);
  752. // expect(response.error.cause()).to.be.an.instanceOf(Error).and.match(/stream error/);
  753. // });
  754. // });
  755. // });
  756. // context("passing a string", () => {
  757. // it("resolves with the device token and an error", () => {
  758. // fakes.stream.emit("error", "stream error");
  759. // return promise.then( (response) => {
  760. // expect(response).to.have.property("device", "abcd1234");
  761. // expect(response.error).to.to.be.an.instanceOf(Error);
  762. // expect(response.error).to.match(/apn write failed/);
  763. // expect(response.error).to.match(/stream error/);
  764. // });
  765. // });
  766. // });
  767. // });
  768. // });
  769. // context("no new stream is returned but the endpoint later wakes up", () => {
  770. // let notification, promise;
  771. // beforeEach( () => {
  772. // const client = new Client( { address: "testapi" } );
  773. // fakes.stream = new FakeStream("abcd1234", "200");
  774. // fakes.endpointManager.getStream.onCall(0).returns(null);
  775. // fakes.endpointManager.getStream.onCall(1).returns(fakes.stream);
  776. // notification = builtNotification();
  777. // promise = client.write(notification, "abcd1234");
  778. // expect(fakes.stream.headers).to.not.be.called;
  779. // fakes.endpointManager.emit("wakeup");
  780. // return promise;
  781. // });
  782. // it("sends the required headers to the newly available stream", () => {
  783. // expect(fakes.stream.headers).to.be.calledWithMatch( {
  784. // ":scheme": "https",
  785. // ":method": "POST",
  786. // ":authority": "testapi",
  787. // ":path": "/3/device/abcd1234",
  788. // });
  789. // });
  790. // it("writes the notification data to the pipe", () => {
  791. // expect(fakes.stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(notification.body)));
  792. // });
  793. // });
  794. // context("when 5 successive notifications are sent", () => {
  795. // beforeEach(() => {
  796. // fakes.streams = [
  797. // new FakeStream("abcd1234", "200"),
  798. // new FakeStream("adfe5969", "400", { reason: "MissingTopic" }),
  799. // new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }),
  800. // new FakeStream("bcfe4433", "200"),
  801. // new FakeStream("aabbc788", "413", { reason: "PayloadTooLarge" }),
  802. // ];
  803. // });
  804. // context("streams are always returned", () => {
  805. // let promises;
  806. // beforeEach( () => {
  807. // const client = new Client( { address: "testapi" } );
  808. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]);
  809. // fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]);
  810. // fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]);
  811. // fakes.endpointManager.getStream.onCall(3).returns(fakes.streams[3]);
  812. // fakes.endpointManager.getStream.onCall(4).returns(fakes.streams[4]);
  813. // promises = Promise.all([
  814. // client.write(builtNotification(), "abcd1234"),
  815. // client.write(builtNotification(), "adfe5969"),
  816. // client.write(builtNotification(), "abcd1335"),
  817. // client.write(builtNotification(), "bcfe4433"),
  818. // client.write(builtNotification(), "aabbc788"),
  819. // ]);
  820. // return promises;
  821. // });
  822. // it("sends the required headers for each stream", () => {
  823. // expect(fakes.streams[0].headers).to.be.calledWithMatch( { ":path": "/3/device/abcd1234" } );
  824. // expect(fakes.streams[1].headers).to.be.calledWithMatch( { ":path": "/3/device/adfe5969" } );
  825. // expect(fakes.streams[2].headers).to.be.calledWithMatch( { ":path": "/3/device/abcd1335" } );
  826. // expect(fakes.streams[3].headers).to.be.calledWithMatch( { ":path": "/3/device/bcfe4433" } );
  827. // expect(fakes.streams[4].headers).to.be.calledWithMatch( { ":path": "/3/device/aabbc788" } );
  828. // });
  829. // it("writes the notification data for each stream", () => {
  830. // fakes.streams.forEach( stream => {
  831. // expect(stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(builtNotification().body)));
  832. // });
  833. // });
  834. // it("resolves with the notification outcomes", () => {
  835. // return expect(promises).to.eventually.deep.equal([
  836. // { device: "abcd1234"},
  837. // { device: "adfe5969", status: "400", response: { reason: "MissingTopic" } },
  838. // { device: "abcd1335", status: "410", response: { reason: "BadDeviceToken", timestamp: 123456789 } },
  839. // { device: "bcfe4433"},
  840. // { device: "aabbc788", status: "413", response: { reason: "PayloadTooLarge" } },
  841. // ]);
  842. // });
  843. // });
  844. // context("some streams return, others wake up later", () => {
  845. // let promises;
  846. // beforeEach( function() {
  847. // const client = new Client( { address: "testapi" } );
  848. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]);
  849. // fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]);
  850. // promises = Promise.all([
  851. // client.write(builtNotification(), "abcd1234"),
  852. // client.write(builtNotification(), "adfe5969"),
  853. // client.write(builtNotification(), "abcd1335"),
  854. // client.write(builtNotification(), "bcfe4433"),
  855. // client.write(builtNotification(), "aabbc788"),
  856. // ]);
  857. // setTimeout(() => {
  858. // fakes.endpointManager.getStream.reset();
  859. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[2]);
  860. // fakes.endpointManager.getStream.onCall(1).returns(null);
  861. // fakes.endpointManager.emit("wakeup");
  862. // }, 1);
  863. // setTimeout(() => {
  864. // fakes.endpointManager.getStream.reset();
  865. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[3]);
  866. // fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[4]);
  867. // fakes.endpointManager.emit("wakeup");
  868. // }, 2);
  869. // return promises;
  870. // });
  871. // it("sends the correct device ID for each stream", () => {
  872. // expect(fakes.streams[0].headers).to.be.calledWithMatch({":path": "/3/device/abcd1234"});
  873. // expect(fakes.streams[1].headers).to.be.calledWithMatch({":path": "/3/device/adfe5969"});
  874. // expect(fakes.streams[2].headers).to.be.calledWithMatch({":path": "/3/device/abcd1335"});
  875. // expect(fakes.streams[3].headers).to.be.calledWithMatch({":path": "/3/device/bcfe4433"});
  876. // expect(fakes.streams[4].headers).to.be.calledWithMatch({":path": "/3/device/aabbc788"});
  877. // });
  878. // it("writes the notification data for each stream", () => {
  879. // fakes.streams.forEach( stream => {
  880. // expect(stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(builtNotification().body)));
  881. // });
  882. // });
  883. // it("resolves with the notification reponses", () => {
  884. // return expect(promises).to.eventually.deep.equal([
  885. // { device: "abcd1234"},
  886. // { device: "adfe5969", status: "400", response: { reason: "MissingTopic" } },
  887. // { device: "abcd1335", status: "410", response: { reason: "BadDeviceToken", timestamp: 123456789 } },
  888. // { device: "bcfe4433"},
  889. // { device: "aabbc788", status: "413", response: { reason: "PayloadTooLarge" } },
  890. // ]);
  891. // });
  892. // });
  893. // context("connection fails", () => {
  894. // let promises, client;
  895. // beforeEach( function() {
  896. // client = new Client( { address: "testapi" } );
  897. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]);
  898. // promises = Promise.all([
  899. // client.write(builtNotification(), "abcd1234"),
  900. // client.write(builtNotification(), "adfe5969"),
  901. // client.write(builtNotification(), "abcd1335"),
  902. // ]);
  903. // setTimeout(() => {
  904. // fakes.endpointManager.getStream.reset();
  905. // fakes.endpointManager.emit("error", new Error("endpoint failed"));
  906. // }, 1);
  907. // return promises;
  908. // });
  909. // it("resolves with 1 success", () => {
  910. // return promises.then( response => {
  911. // expect(response[0]).to.deep.equal({ device: "abcd1234" });
  912. // });
  913. // });
  914. // it("resolves with 2 errors", () => {
  915. // return promises.then( response => {
  916. // expect(response[1]).to.deep.equal({ device: "adfe5969", error: new Error("endpoint failed") });
  917. // expect(response[2]).to.deep.equal({ device: "abcd1335", error: new Error("endpoint failed") });
  918. // })
  919. // });
  920. // it("clears the queue", () => {
  921. // return promises.then( () => {
  922. // expect(client.queue.length).to.equal(0);
  923. // });
  924. // });
  925. // });
  926. // });
  927. // describe("token generator behaviour", () => {
  928. // beforeEach(() => {
  929. // fakes.token = {
  930. // generation: 0,
  931. // current: "fake-token",
  932. // regenerate: sinon.stub(),
  933. // isExpired: sinon.stub()
  934. // }
  935. // fakes.streams = [
  936. // new FakeStream("abcd1234", "200"),
  937. // new FakeStream("adfe5969", "400", { reason: "MissingTopic" }),
  938. // new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }),
  939. // ];
  940. // });
  941. // it("reuses the token", () => {
  942. // const client = new Client( { address: "testapi", token: fakes.token } );
  943. // fakes.token.regenerate = () => {
  944. // fakes.token.generation = 1;
  945. // fakes.token.current = "second-token";
  946. // }
  947. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]);
  948. // fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]);
  949. // fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]);
  950. // return Promise.all([
  951. // client.write(builtNotification(), "abcd1234"),
  952. // client.write(builtNotification(), "adfe5969"),
  953. // client.write(builtNotification(), "abcd1335"),
  954. // ]).then(() => {
  955. // expect(fakes.streams[0].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" });
  956. // expect(fakes.streams[1].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" });
  957. // expect(fakes.streams[2].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" });
  958. // });
  959. // });
  960. // context("token expires", () => {
  961. // beforeEach(() => {
  962. // fakes.token.regenerate = function (generation) {
  963. // if (generation === fakes.token.generation) {
  964. // fakes.token.generation += 1;
  965. // fakes.token.current = "token-" + fakes.token.generation;
  966. // }
  967. // }
  968. // });
  969. // it("resends the notification with a new token", () => {
  970. // fakes.streams = [
  971. // new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }),
  972. // new FakeStream("adfe5969", "200"),
  973. // ];
  974. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]);
  975. // const client = new Client( { address: "testapi", token: fakes.token } );
  976. // const promise = client.write(builtNotification(), "adfe5969");
  977. // setTimeout(() => {
  978. // fakes.endpointManager.getStream.reset();
  979. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[1]);
  980. // fakes.endpointManager.emit("wakeup");
  981. // }, 1);
  982. // return promise.then(() => {
  983. // expect(fakes.streams[0].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" });
  984. // expect(fakes.streams[1].headers).to.be.calledWithMatch({ authorization: "bearer token-1" });
  985. // });
  986. // });
  987. // it("only regenerates the token once per-expiry", () => {
  988. // fakes.streams = [
  989. // new FakeStream("abcd1234", "200"),
  990. // new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }),
  991. // new FakeStream("abcd1335", "403", { reason: "ExpiredProviderToken" }),
  992. // new FakeStream("adfe5969", "400", { reason: "MissingTopic" }),
  993. // new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }),
  994. // ];
  995. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]);
  996. // fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]);
  997. // fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]);
  998. // const client = new Client( { address: "testapi", token: fakes.token } );
  999. // const promises = Promise.all([
  1000. // client.write(builtNotification(), "abcd1234"),
  1001. // client.write(builtNotification(), "adfe5969"),
  1002. // client.write(builtNotification(), "abcd1335"),
  1003. // ]);
  1004. // setTimeout(() => {
  1005. // fakes.endpointManager.getStream.reset();
  1006. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[3]);
  1007. // fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[4]);
  1008. // fakes.endpointManager.emit("wakeup");
  1009. // }, 1);
  1010. // return promises.then(() => {
  1011. // expect(fakes.streams[0].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" });
  1012. // expect(fakes.streams[1].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" });
  1013. // expect(fakes.streams[2].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" });
  1014. // expect(fakes.streams[3].headers).to.be.calledWithMatch({ authorization: "bearer token-1" });
  1015. // expect(fakes.streams[4].headers).to.be.calledWithMatch({ authorization: "bearer token-1" });
  1016. // });
  1017. // });
  1018. // it("abandons sending after 3 ExpiredProviderToken failures", () => {
  1019. // fakes.streams = [
  1020. // new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }),
  1021. // new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }),
  1022. // new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }),
  1023. // ];
  1024. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]);
  1025. // fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]);
  1026. // fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]);
  1027. // const client = new Client( { address: "testapi", token: fakes.token } );
  1028. // return expect(client.write(builtNotification(), "adfe5969")).to.eventually.have.property("status", "403");
  1029. // });
  1030. // it("regenerate token", () => {
  1031. // fakes.stream = new FakeStream("abcd1234", "200");
  1032. // fakes.endpointManager.getStream.onCall(0).returns(fakes.stream);
  1033. // fakes.token.isExpired = function (current, validSeconds) {
  1034. // return true;
  1035. // }
  1036. // let client = new Client({
  1037. // address: "testapi",
  1038. // token: fakes.token
  1039. // });
  1040. // return client.write(builtNotification(), "abcd1234")
  1041. // .then(() => {
  1042. // expect(fakes.token.generation).to.equal(1);
  1043. // });
  1044. // });
  1045. // it("internal server error", () => {
  1046. // fakes.stream = new FakeStream("abcd1234", "500", { reason: "InternalServerError" });
  1047. // fakes.stream.connection = sinon.stub();
  1048. // fakes.stream.connection.close = sinon.stub();
  1049. // fakes.endpointManager.getStream.onCall(0).returns(fakes.stream);
  1050. // let client = new Client({
  1051. // address: "testapi",
  1052. // token: fakes.token
  1053. // });
  1054. // return expect(client.write(builtNotification(), "abcd1234")).to.eventually.have.deep.property("error.jse_shortmsg","Error 500, stream ended unexpectedly");
  1055. // });
  1056. // });
  1057. // });
  1058. });
  1059. describe('shutdown', () => {
  1060. // beforeEach(() => {
  1061. // fakes.config.returnsArg(0);
  1062. // fakes.endpointManager.getStream = sinon.stub();
  1063. // fakes.EndpointManager.returns(fakes.endpointManager);
  1064. // });
  1065. // context("with no pending notifications", () => {
  1066. // it("invokes shutdown on endpoint manager", () => {
  1067. // let client = new Client();
  1068. // client.shutdown();
  1069. // expect(fakes.endpointManager.shutdown).to.be.calledOnce;
  1070. // });
  1071. // });
  1072. // context("with pending notifications", () => {
  1073. // it("invokes shutdown on endpoint manager after queue drains", () => {
  1074. // let client = new Client({ address: "none" });
  1075. // fakes.streams = [
  1076. // new FakeStream("abcd1234", "200"),
  1077. // new FakeStream("adfe5969", "400", { reason: "MissingTopic" }),
  1078. // new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }),
  1079. // new FakeStream("bcfe4433", "200"),
  1080. // new FakeStream("aabbc788", "413", { reason: "PayloadTooLarge" }),
  1081. // ];
  1082. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]);
  1083. // fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]);
  1084. // let promises = Promise.all([
  1085. // client.write(builtNotification(), "abcd1234"),
  1086. // client.write(builtNotification(), "adfe5969"),
  1087. // client.write(builtNotification(), "abcd1335"),
  1088. // client.write(builtNotification(), "bcfe4433"),
  1089. // client.write(builtNotification(), "aabbc788"),
  1090. // ]);
  1091. // client.shutdown();
  1092. // expect(fakes.endpointManager.shutdown).to.not.be.called;
  1093. // setTimeout(() => {
  1094. // fakes.endpointManager.getStream.reset();
  1095. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[2]);
  1096. // fakes.endpointManager.getStream.onCall(1).returns(null);
  1097. // fakes.endpointManager.emit("wakeup");
  1098. // }, 1);
  1099. // setTimeout(() => {
  1100. // fakes.endpointManager.getStream.reset();
  1101. // fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[3]);
  1102. // fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[4]);
  1103. // fakes.endpointManager.emit("wakeup");
  1104. // }, 2);
  1105. // return promises.then( () => {
  1106. // expect(fakes.endpointManager.shutdown).to.have.been.called;
  1107. // });
  1108. // });
  1109. // });
  1110. });
  1111. });