provider.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. const sinon = require('sinon');
  2. const EventEmitter = require('events');
  3. describe('Provider', function () {
  4. let fakes, Provider;
  5. beforeEach(function () {
  6. fakes = {
  7. Client: sinon.stub(),
  8. client: new EventEmitter(),
  9. };
  10. fakes.Client.returns(fakes.client);
  11. fakes.client.write = sinon.stub();
  12. fakes.client.shutdown = sinon.stub();
  13. Provider = require('../lib/provider')(fakes);
  14. });
  15. describe('constructor', function () {
  16. context('called without `new`', function () {
  17. it('returns a new instance', function () {
  18. expect(Provider()).to.be.an.instanceof(Provider);
  19. });
  20. });
  21. describe('Client instance', function () {
  22. it('is created', function () {
  23. Provider();
  24. expect(fakes.Client).to.be.calledOnce;
  25. expect(fakes.Client).to.be.calledWithNew;
  26. });
  27. it('is passed the options', function () {
  28. const options = { configKey: 'configValue' };
  29. Provider(options);
  30. expect(fakes.Client).to.be.calledWith(options);
  31. });
  32. });
  33. });
  34. describe('send', function () {
  35. describe('single notification behaviour', function () {
  36. let provider;
  37. context('transmission succeeds', function () {
  38. beforeEach(function () {
  39. provider = new Provider({ address: 'testapi' });
  40. fakes.client.write.onCall(0).returns(Promise.resolve({ device: 'abcd1234' }));
  41. });
  42. it('invokes the writer with correct `this`', function () {
  43. return provider.send(notificationDouble(), 'abcd1234').then(function () {
  44. expect(fakes.client.write).to.be.calledOn(fakes.client);
  45. });
  46. });
  47. it('writes the notification to the client once', function () {
  48. return provider.send(notificationDouble(), 'abcd1234').then(function () {
  49. const notification = notificationDouble();
  50. const builtNotification = {
  51. headers: notification.headers(),
  52. body: notification.compile(),
  53. };
  54. expect(fakes.client.write).to.be.calledOnce;
  55. expect(fakes.client.write).to.be.calledWith(builtNotification, 'abcd1234');
  56. });
  57. });
  58. it('does not pass the array index to writer', function () {
  59. return provider.send(notificationDouble(), 'abcd1234').then(function () {
  60. expect(fakes.client.write.firstCall.args[2]).to.be.undefined;
  61. });
  62. });
  63. it('resolves with the device token in the sent array', function () {
  64. return expect(provider.send(notificationDouble(), 'abcd1234')).to.become({
  65. sent: [{ device: 'abcd1234' }],
  66. failed: [],
  67. });
  68. });
  69. });
  70. context('error occurs', function () {
  71. let promise;
  72. beforeEach(function () {
  73. const provider = new Provider({ address: 'testapi' });
  74. fakes.client.write.onCall(0).returns(
  75. Promise.resolve({
  76. device: 'abcd1234',
  77. status: '400',
  78. response: { reason: 'BadDeviceToken' },
  79. })
  80. );
  81. promise = provider.send(notificationDouble(), 'abcd1234');
  82. });
  83. it('resolves with the device token, status code and response in the failed array', function () {
  84. return expect(promise).to.eventually.deep.equal({
  85. sent: [],
  86. failed: [{ device: 'abcd1234', status: '400', response: { reason: 'BadDeviceToken' } }],
  87. });
  88. });
  89. });
  90. });
  91. context('when multiple tokens are passed', function () {
  92. beforeEach(function () {
  93. fakes.resolutions = [
  94. { device: 'abcd1234' },
  95. { device: 'adfe5969', status: '400', response: { reason: 'MissingTopic' } },
  96. {
  97. device: 'abcd1335',
  98. status: '410',
  99. response: { reason: 'BadDeviceToken', timestamp: 123456789 },
  100. },
  101. { device: 'bcfe4433' },
  102. { device: 'aabbc788', status: '413', response: { reason: 'PayloadTooLarge' } },
  103. { device: 'fbcde238', error: new Error('connection failed') },
  104. ];
  105. });
  106. context('streams are always returned', function () {
  107. let promise;
  108. beforeEach(function () {
  109. const provider = new Provider({ address: 'testapi' });
  110. for (let i = 0; i < fakes.resolutions.length; i++) {
  111. fakes.client.write.onCall(i).returns(Promise.resolve(fakes.resolutions[i]));
  112. }
  113. promise = provider.send(
  114. notificationDouble(),
  115. fakes.resolutions.map(res => res.device)
  116. );
  117. return promise;
  118. });
  119. it('resolves with the sent notifications', function () {
  120. return promise.then(response => {
  121. expect(response.sent).to.deep.equal([{ device: 'abcd1234' }, { device: 'bcfe4433' }]);
  122. });
  123. });
  124. it('resolves with the device token, status code and response or error of the unsent notifications', function () {
  125. return promise.then(response => {
  126. expect(response.failed[3].error).to.be.an.instanceof(Error);
  127. response.failed[3].error = { message: response.failed[3].error.message };
  128. expect(response.failed).to.deep.equal(
  129. [
  130. { device: 'adfe5969', status: '400', response: { reason: 'MissingTopic' } },
  131. {
  132. device: 'abcd1335',
  133. status: '410',
  134. response: { reason: 'BadDeviceToken', timestamp: 123456789 },
  135. },
  136. { device: 'aabbc788', status: '413', response: { reason: 'PayloadTooLarge' } },
  137. { device: 'fbcde238', error: { message: 'connection failed' } },
  138. ],
  139. `Unexpected result: ${JSON.stringify(response.failed)}`
  140. );
  141. });
  142. });
  143. });
  144. });
  145. });
  146. describe('shutdown', function () {
  147. it('invokes shutdown on the client', function () {
  148. const callback = sinon.spy();
  149. const provider = new Provider({});
  150. provider.shutdown(callback);
  151. expect(fakes.client.shutdown).to.be.calledOnceWithExactly(callback);
  152. });
  153. });
  154. });
  155. function notificationDouble() {
  156. return {
  157. headers: sinon.stub().returns({}),
  158. payload: { aps: { badge: 1 } },
  159. compile: function () {
  160. return JSON.stringify(this.payload);
  161. },
  162. };
  163. }