import test from 'ember-sinon-qunit/test-support/test';
import { moduleFor } from 'ember-qunit';
import Service from 'ember-service';
import { A as emberA } from 'ember-array/utils';
import RSVP from 'rsvp';
import EmberObject from 'ember-object';
import nonce from 'web-client/utilities/nonce';
import {
  PUBSUB_CREATE_NOTIFICATION,
  PUBSUB_UPDATE_NOTIFICATION,
  PUBSUB_READ_NOTIFICATIONS,
  PUBSUB_DELETE_NOTIFICATION,
  PUBSUB_CONNECT_TIMEOUT
} from 'web-client/services/onsite-notifications';

moduleFor('service:onsite-notifications', {
  beforeEach() {
    this.userId = nonce();

    let mockApi = Service.extend({});
    let mockSession = Service.extend({
      userData: EmberObject.create({
        id: this.userId
      })
    });
    let mockStore = Service.extend({});

    this.register('service:api', mockApi);
    this.register('service:session', mockSession);
    this.register('service:store', mockStore);

    this.service = this.subject();

    this.service._unbindPubsub = function() { }; // unit tests will tear down a service
  }
});

function newMockNotification(props = {}) {
  let mockNotification = EmberObject.create({
    id: nonce()
  });

  mockNotification.setProperties(props);

  return mockNotification;
}

test('default state', function(assert) {
  assert.expect(6);

  // then
  assert.notOk(this.service.get('hasViewedCenter'));
  assert.ok(this.service.get('hasMorePages'));
  assert.notOk(this.service.get('loadingMore'));
  assert.equal(this.service.get('notifications').length, 0);
  assert.equal(this.service.get('summary'), null);
  assert.equal(this.service.get('connectTimeout'), PUBSUB_CONNECT_TIMEOUT);
});

test('isEmpty is true by default', function(assert) {
  assert.expect(1);

  // then
  assert.notOk(this.service.get('isEmpty'));
});

test('isEmpty is true when there are no more pages and many notifications', function(assert) {
  assert.expect(1);

  let mockNotifications = emberA();
  mockNotifications.push(newMockNotification());

  // when
  this.service.set('hasMorePages', false);
  this.service.set('notifications', mockNotifications);

  // then
  assert.notOk(this.service.get('isEmpty'));
});

test('isEmpty is false when there are no more pages and 0 notifications', function(assert) {
  assert.expect(1);

  this.service.set('hasMorePages', false);
  this.service.set('notifications', emberA());
  assert.ok(this.service.get('isEmpty'));
});

test('setupService sets hasViewedCenter and calls bindPubsub', function(assert) {
  assert.expect(2);

  // given
  let bindPubsubStub = this.stub(this.service, '_bindPubsub');

  // when
  this.service.setupService();

  // then
  assert.ok(bindPubsubStub.calledOnce);
  assert.notOk(this.service.get('hasViewedCenter'));
});

test('getSummary calls the data store', function(assert) {
  assert.expect(4);

  let findRecordReturn = {};
  let findRecordStub = this.stub().returns(findRecordReturn);
  this.service.get('store').findRecord = findRecordStub;

  let summaryResult = this.service.getSummary();

  assert.equal(summaryResult, findRecordReturn);
  assert.ok(findRecordStub.calledOnce);
  assert.equal(findRecordStub.firstCall.args[0], 'onsite-notification-summary');
  assert.equal(findRecordStub.firstCall.args[1], 'user');
});

test('queryNotifications with no cursor calls the data store', function(assert) {
  assert.expect(4);

  // given
  let queryReturn = {};
  let queryStub = this.stub().returns(queryReturn);
  this.service.get('store').query = queryStub;

  // when
  let queryResult = this.service._queryNotifications();

  // then
  assert.equal(queryResult, queryReturn);
  assert.ok(queryStub.calledOnce);
  assert.equal(queryStub.firstCall.args[0], 'onsite-notification');
  assert.deepEqual(queryStub.firstCall.args[1], {});
});

test('queryNotifications with a cursor calls the data store', function(assert) {
  assert.expect(4);

  // given
  let queryReturn = {};
  let queryStub = this.stub().returns(queryReturn);
  this.service.get('store').query = queryStub;
  let testCursor = nonce();

  // when
  let queryResult = this.service._queryNotifications(testCursor);

  // then
  assert.equal(queryResult, queryReturn);
  assert.ok(queryStub.calledOnce);
  assert.equal(queryStub.firstCall.args[0], 'onsite-notification');
  assert.deepEqual(queryStub.firstCall.args[1], {cursor: testCursor});
});

test('loadMoreNotifications returns a resolved promise if there are no more pages', function(assert) {
  assert.expect(1);

  // given
  this.service.set('hasMorePages', false);

  // when
  let loadMoreResult = this.service.loadMoreNotifications();

  // then
  loadMoreResult.then(() => {
    assert.ok(true);
  });
});

test('loadMoreNotifications returns a promise if already loading', function(assert) {
  assert.expect(1);

  // given
  this.service.set('_loadingMorePromise', RSVP.resolve());

  // when
  let loadMoreResult = this.service.loadMoreNotifications();

  // then
  loadMoreResult.then(() => {
    assert.ok(true);
  });
});

test('loadMoreNotifications queries for notifications with the last cursor and updates state', function(assert) {
  assert.expect(12);

  // given
  // mock a promise to allow stepping through different states
  let mockPromise = {
    then(thenCallback) {
      this.thenCallback = thenCallback;
      return this;
    },

    finally(finallyCallback) {
      this.finallyCallback = finallyCallback;
      return this;
    }
  };

  let testLastCursor = nonce();
  this.service.set('_lastCursor', testLastCursor);

  let currentNotifications = [{a: true}, {b: true}, {c: true}];
  this.service.set('notifications', [].concat(currentNotifications));

  let queryNotificationsStub = this.stub(this.service, '_queryNotifications').returns(mockPromise);

  // when
  let loadMoreResult = this.service.loadMoreNotifications();

  // then
  assert.ok(this.service.get('hasMorePages'));
  assert.equal(loadMoreResult, mockPromise);
  assert.ok(queryNotificationsStub.calledOnce);
  assert.equal(queryNotificationsStub.firstCall.args[0], testLastCursor);

  // check state before the `then` callback fires
  assert.equal(this.service.get('_loadingMorePromise'), mockPromise);

  // check the state after the `then` callback fires
  let queriedNotifications = [{d: true},{e: true},{f: true}];
  let mockMeta = { cursor: nonce() };
  queriedNotifications.get = (prop) => {
    assert.equal(prop, 'meta');
    return mockMeta;
  };

  mockPromise.thenCallback(queriedNotifications);

  assert.equal(this.service.get('_lastCursor'), mockMeta.cursor);
  assert.ok(this.service.get('hasMorePages'));
  assert.deepEqual(this.service.get('notifications'), [].concat(currentNotifications).concat(queriedNotifications));
  assert.ok(this.service.get('loadingMore'));

  // check the state after the `finally` callback fires
  mockPromise.finallyCallback();

  assert.equal(this.service.get('_loadingMorePromise'), null);
  assert.notOk(this.service.get('loadingMore'));
});

test('loadMoreNotifications sets hasMorePages to false if there is no cursor', function(assert) {
  assert.expect(5);

  // given
  // mock a promise to allow stepping through different states
  let mockPromise = {
    then(thenCallback) {
      this.thenCallback = thenCallback;
      return this;
    },

    finally() {
      return this;
    }
  };

  let queryNotificationsStub = this.stub(this.service, '_queryNotifications').returns(mockPromise);

  // when
  let loadMoreResult = this.service.loadMoreNotifications();

  // then
  assert.ok(this.service.get('hasMorePages'));

  assert.equal(loadMoreResult, mockPromise);
  assert.ok(queryNotificationsStub.calledOnce);

  // check the state after the `then` callback fires
  let queriedNotifications = [];
  let mockMeta = { cursor: null };
  queriedNotifications.get = (prop) => {
    assert.equal(prop, 'meta');
    return mockMeta;
  };

  mockPromise.thenCallback(queriedNotifications);

  assert.notOk(this.service.get('hasMorePages'));
});

test('dismissNotification calls the API with an authenticated request', function(assert) {
  assert.expect(8);

  // given
  let removeNotificationStub = this.stub(this.service, '_removeNotification');

  let mockNotification = newMockNotification();

  let authRequestRet = {};
  let authRequestStub = this.stub().returns(authRequestRet);

  this.service.get('api').authRequest = authRequestStub;

  // when
  let dismissResult = this.service.dismissNotification(mockNotification);

  // then
  assert.equal(authRequestRet, dismissResult);

  assert.ok(removeNotificationStub.calledOnce);
  assert.equal(removeNotificationStub.firstCall.args[0], mockNotification);

  assert.ok(authRequestStub.calledOnce);
  assert.equal(authRequestStub.firstCall.args[0], 'delete');
  assert.equal(authRequestStub.firstCall.args[1], `users/${this.userId}/notifications/onsite/${mockNotification.get('id')}`);
  assert.equal(authRequestStub.firstCall.args[2], null);
  assert.deepEqual(authRequestStub.firstCall.args[3], { version: 5 });
});

test('removeNotification removes from the collection', function(assert) {
  assert.expect(1);

  // given
  let mockNotification1 = newMockNotification();
  let mockNotification2 = newMockNotification();
  let mockNotification3 = newMockNotification();

  this.service.set('notifications', [mockNotification1, mockNotification2, mockNotification3]);

  // when
  this.service._removeNotification(mockNotification2);

  // then
  assert.deepEqual(this.service.get('notifications'), [mockNotification1, mockNotification3]);
});

test('markAllRead does nothing if there are zero notifications', function(assert) {
  assert.expect(1);

  // given
  let setNotificationsReadStub = this.stub(this.service, 'setNotificationsRead');

  // when
  this.service.markAllRead();

  // then
  assert.notOk(setNotificationsReadStub.calledOnce);
});

test('markAllRead does nothing if there are zero unread notifications', function(assert) {
  assert.expect(1);

  // given
  let setNotificationsReadStub = this.stub(this.service, 'setNotificationsRead');

  let mockNotifications = [
    newMockNotification({read: true}),
    newMockNotification({read: true})
  ];

  this.service.set('notifications', mockNotifications);

  // when
  this.service.markAllRead();

  // then
  assert.notOk(setNotificationsReadStub.calledOnce);
});

test('markAllRead calls setNotificationsRead in chunks with unread unread notifications', function(assert) {
  assert.expect(3);

  // given
  let setNotificationsReadStub = this.stub(this.service, 'setNotificationsRead');

  let mockNotifications = [];
  for (let i = 0; i < 150; ++i) {
    mockNotifications.push(newMockNotification({read: false}));
  }

  this.service.set('notifications', mockNotifications);

  // when
  this.service.markAllRead();

  // then
  assert.equal(setNotificationsReadStub.callCount, 2);
  // check chunking
  assert.equal(setNotificationsReadStub.firstCall.args[0].length, 100);
  assert.equal(setNotificationsReadStub.secondCall.args[0].length, 50);
});

test('setNotificationsRead errors when notifications is falsy', function(assert) {
  assert.expect(1);

  // when
  assert.throws(() => {
    this.service.setNotificationsRead(null);
  }, /Missing notifications/);
});

test('setNotificationsRead errors when notifications is empty', function(assert) {
  assert.expect(1);

  // when
  assert.throws(() => {
    this.service.setNotificationsRead([]);
  }, /Must have more than 0 notifications/);
});

test('setNotificationsRead errors when notifications length is greater than 100', function(assert) {
  assert.expect(1);

  // when
  assert.throws(() => {
    this.service.setNotificationsRead(new Array(101));
  }, /Must have less than 100 notifications/);
});

test('setNotificationsRead calls the API with an authenticated request', function(assert) {
  assert.expect(13);

  // given
  let mockNotifications = [
    newMockNotification(),
    newMockNotification(),
    newMockNotification()
  ];

  let pushPayloadStub = this.stub();
  this.service.get('store').pushPayload = pushPayloadStub;

  let authRequestStub = this.stub();
  this.service.get('api').authRequest = authRequestStub;

  // when
  this.service.setNotificationsRead(mockNotifications);

  // then
  assert.ok(pushPayloadStub.called);
  assert.equal(pushPayloadStub.callCount, mockNotifications.length);
  for (let i = 0; i < mockNotifications.length; ++i) {
    let call = pushPayloadStub.getCall(i);
    assert.equal(call.args[0], 'onsite-notification');
    assert.deepEqual(call.args[1], {
      id: mockNotifications[i].get('id'),
      read: true
    });
  }

  assert.ok(authRequestStub.calledOnce);

  assert.equal(authRequestStub.firstCall.args[0], 'put');
  assert.equal(authRequestStub.firstCall.args[1], `users/${this.userId}/notifications/onsite/read`);
  assert.deepEqual(authRequestStub.firstCall.args[2], {notification_ids: mockNotifications.map(notif => notif.get('id'))});
  assert.deepEqual(authRequestStub.firstCall.args[3], { version: 5 });
});

test('bindPubsub does nothing if a messageHandler exists', function(assert) {
  assert.expect(1);

  // given
  let testUserData = { chat_oauth_token: nonce() };
  let mockPubsub = {};

  this.service.get('session').getCurrentUser = this.stub().returns(RSVP.resolve(testUserData));
  let pubsubStub = this.stub(this.service, '_pubsub').returns(mockPubsub);

  this.service.set('_messageHandler', function() { });

  // when
  this.service._bindPubsub();

  // then
  assert.notOk(pubsubStub.calledOnce);
});

test('bindPubsub gets the summary after pubsub successfully listens', function(assert) {
  assert.expect(11);

  // given
  let testUserData = { chat_oauth_token: nonce() };
  let mockSummary = { unseen_view_count: 10 };
  let mockPubsub = { Listen: this.stub() };

  let getSummaryPromise = RSVP.resolve(mockSummary);
  let getSummaryStub = this.stub(this.service, 'getSummary').returns(getSummaryPromise);

  let getCurrentUserPromise = RSVP.resolve(testUserData);
  this.service.get('session').getCurrentUser = this.stub().returns(getCurrentUserPromise);
  let pubsubStub = this.stub(this.service, '_pubsub').returns(mockPubsub);

  let runTaskStub = this.stub(this.service, 'runTask');

  // when
  this.service._bindPubsub();

  // then
  getCurrentUserPromise.then(() => {
    // asserts must be after `getCurrentUser`
    assert.ok(runTaskStub.calledOnce);
    assert.equal(runTaskStub.firstCall.args[1], this.service.get('connectTimeout'));

    assert.ok(pubsubStub.calledOnce);
    assert.ok(mockPubsub.Listen.calledOnce);
    let listenOpts = mockPubsub.Listen.firstCall.args[0];
    assert.equal(listenOpts.topic, this.service._pubsubTopic());
    assert.equal(listenOpts.auth, testUserData.chat_oauth_token);
    assert.equal(listenOpts.message, this.service.get('_messageHandler'));
    assert.ok(listenOpts.success);
    assert.ok(listenOpts.failure);

    // trigger pubsub promise
    listenOpts.success();

    // need to wait for the next tick
    setTimeout(() => {
      getSummaryPromise.then(() => {
        assert.ok(getSummaryStub.calledOnce);
        assert.deepEqual(this.service.get('summary'), mockSummary);
      });
    }, 0);
  });
});

test('bindPubsub gets the summary after pubsub fails to listen', function(assert) {
  assert.expect(1);

  // given
  let testUserData = { chat_oauth_token: nonce() };
  let mockSummary = { unseen_view_count: 10 };
  let mockPubsub = { Listen: this.stub() };

  let getSummaryPromise = RSVP.resolve(mockSummary);
  let getSummaryStub = this.stub(this.service, 'getSummary').returns(getSummaryPromise);

  let getCurrentUserPromise = RSVP.resolve(testUserData);
  this.service.get('session').getCurrentUser = this.stub().returns(getCurrentUserPromise);
  this.stub(this.service, '_pubsub').returns(mockPubsub);

  this.stub(this.service, 'runTask');

  // when
  this.service._bindPubsub();

  // then
  getCurrentUserPromise.then(() => {
    let listenOpts = mockPubsub.Listen.firstCall.args[0];

    // trigger pubsub promise
    listenOpts.failure();

    // need to wait for the next tick
    setTimeout(() => {
      getSummaryPromise.then(() => {
        assert.ok(getSummaryStub.calledOnce);
      });
    }, 0);
  });
});

test('bindPubsub gets the summary if pubsub times out', function(assert) {
  assert.expect(4);

  // given
  let testUserData = { chat_oauth_token: nonce() };
  let mockSummary = { unseen_view_count: 10 };
  let mockPubsub = { Listen: this.stub() };

  let getSummaryPromise = RSVP.resolve(mockSummary);
  let getSummaryStub = this.stub(this.service, 'getSummary').returns(getSummaryPromise);

  let getCurrentUserPromise = RSVP.resolve(testUserData);
  this.service.get('session').getCurrentUser = this.stub().returns(getCurrentUserPromise);
  let pubsubStub = this.stub(this.service, '_pubsub').returns(mockPubsub);

  this.stub(this.service, 'runTask', (callback, delay) => {
    assert.equal(delay, this.service.get('connectTimeout'));
    callback();
  });

  // when
  this.service._bindPubsub();

  // then
  getCurrentUserPromise.then(() => {
    // asserts must be after `getCurrentUser`
    assert.ok(pubsubStub.calledOnce);
    assert.ok(mockPubsub.Listen.calledOnce);

    // need to wait for the next tick
    setTimeout(() => {
      getSummaryPromise.then(() => {
        assert.ok(getSummaryStub.calledOnce);
      });
    }, 0);
  });
});

test('bindPubsub gets the summary and sets the summary if not already set', function(assert) {
  assert.expect(5);

  // given
  let mockSummary1 = { unseen_view_count: 5};
  this.service.set('summary', mockSummary1);

  let testUserData = { chat_oauth_token: nonce() };
  let mockSummary2 = { unseen_view_count: 10 };
  let mockPubsub = { Listen: this.stub() };

  let getSummaryPromise = RSVP.resolve(mockSummary2);
  let getSummaryStub = this.stub(this.service, 'getSummary').returns(getSummaryPromise);

  let getCurrentUserPromise = RSVP.resolve(testUserData);
  this.service.get('session').getCurrentUser = this.stub().returns(getCurrentUserPromise);
  let pubsubStub = this.stub(this.service, '_pubsub').returns(mockPubsub);

  this.stub(this.service, 'runTask', (callback, delay) => {
    assert.equal(delay, this.service.get('connectTimeout'));
    callback();
  });

  // when
  this.service._bindPubsub();

  // then
  getCurrentUserPromise.then(() => {
    // asserts must be after `getCurrentUser`
    assert.ok(pubsubStub.calledOnce);
    assert.ok(mockPubsub.Listen.calledOnce);

    // need to wait for the next tick
    setTimeout(() => {
      getSummaryPromise.then(() => {
        assert.ok(getSummaryStub.calledOnce);
        assert.equal(this.service.get('summary'), mockSummary1);
      });
    }, 0);
  });
});

test('handleCreatedOrUpdatedNotification does not insert a notification when the center is unviewed', function(assert) {
  assert.expect(4);

  // given
  let mockNotificationPayload = { id: nonce() };
  let mockNotification = newMockNotification({id: mockNotificationPayload.id});

  let pushPayloadStub = this.stub();
  this.service.get('store').pushPayload = pushPayloadStub;

  let peekRecordStub = this.stub().returns(mockNotification);
  this.service.get('store').peekRecord = peekRecordStub;

  this.service.bindNewNotification(null, (notification) => {
    assert.equal(notification, mockNotification);
  });

  // when
  this.service._handleCreatedOrUpdatedNotification(mockNotificationPayload, true);

  // then
  assert.ok(pushPayloadStub.calledOnce);
  assert.ok(peekRecordStub.calledOnce);
  assert.equal(this.service.get('notifications').length, 0);
});


test('handleCreatedOrUpdatedNotification prepends and deduplicates notifications when the center is viewed', function(assert) {
  assert.expect(5);

  // given
  let mockNotificationPayload1 = { id: nonce() };
  let mockNotification1 = newMockNotification({id: mockNotificationPayload1.id});

  let mockNotificationPayload2 = { id: nonce() };
  let mockNotification2 = newMockNotification({id: mockNotificationPayload2.id});

  let mockNotificationPayload3 = { id: nonce() };
  let mockNotification3 = newMockNotification({id: mockNotificationPayload3.id});

  let pushPayloadStub = this.stub();
  this.service.get('store').pushPayload = pushPayloadStub;

  let peekRecordStub = this.stub();
  peekRecordStub.withArgs('onsite-notification', mockNotificationPayload1.id).returns(mockNotification1);
  peekRecordStub.withArgs('onsite-notification', mockNotificationPayload2.id).returns(mockNotification2);
  peekRecordStub.withArgs('onsite-notification', mockNotificationPayload3.id).returns(mockNotification3);
  this.service.get('store').peekRecord = peekRecordStub;

  this.service.set('hasViewedCenter', true);

  // when
  this.service._handleCreatedOrUpdatedNotification(mockNotificationPayload1, true);
  // then
  assert.deepEqual(this.service.get('notifications'), [mockNotification1]);

  // when
  this.service._handleCreatedOrUpdatedNotification(mockNotificationPayload1, true);
  // then
  assert.deepEqual(this.service.get('notifications'), [mockNotification1]);

  // when
  this.service._handleCreatedOrUpdatedNotification(mockNotificationPayload2, true);
  // then
  assert.deepEqual(this.service.get('notifications'), [mockNotification2, mockNotification1]);

  // when
  this.service._handleCreatedOrUpdatedNotification(mockNotificationPayload3, true);
  // then
  assert.deepEqual(this.service.get('notifications'), [mockNotification3, mockNotification2, mockNotification1]);

  // when
  this.service._handleCreatedOrUpdatedNotification(mockNotificationPayload1, true);
  // then
  assert.deepEqual(this.service.get('notifications'), [mockNotification1, mockNotification3, mockNotification2]);
});

test('handleReadNotifications sets each notification to read', function(assert) {
  assert.expect(6);

  // given
  let ids = [nonce(), nonce(), nonce()];
  let mockNotifications = [];
  for (let i = 0; i < ids.length; ++i) {
    let mockNotification = newMockNotification({id: ids[i]});
    mockNotifications.push(mockNotification);
  }

  let peekRecordStub = this.stub();
  this.service.get('store').peekRecord = peekRecordStub;

  peekRecordStub.withArgs('onsite-notification', ids[0]).returns(mockNotifications[0]);
  peekRecordStub.withArgs('onsite-notification', ids[1]).returns(mockNotifications[1]);

  let pushPayloadStub = this.stub();
  this.service.get('store').pushPayload = pushPayloadStub;

  // when
  this.service._handleReadNotifications(ids);

  // then
  assert.equal(peekRecordStub.callCount, ids.length);
  assert.equal(pushPayloadStub.callCount, 2);

  for (let i = 0; i < 2; ++i) {
    assert.equal(pushPayloadStub.getCall(i).args[0], 'onsite-notification');
    assert.deepEqual(pushPayloadStub.getCall(i).args[1], {
      id: ids[i],
      read: true
    });
  }
});

test('handleDeleteNotifcation removes the notification', function(assert) {
  assert.expect(2);

  // given
  let mockNotification = newMockNotification();

  let peekRecordStub = this.stub().returns(mockNotification);
  this.service.get('store').peekRecord = peekRecordStub;

  let removeNotificationStub = this.stub(this.service, '_removeNotification');

  // when
  this.service._handleDeleteNotification(mockNotification.get('id'));

  // then
  assert.ok(removeNotificationStub.calledOnce);
  assert.equal(removeNotificationStub.firstCall.args[0], mockNotification);
});

test('onPubsubMessage updates the summary', function(assert) {
  assert.expect(7);

  // given
  let payload = {
    type: "dummy_type",
    data: {
      summary: {
        unseen_view_count: 10
      }
    }
  };

  let mockSummary = EmberObject.create({
    unseenViewCount: payload.data.summary.unseen_view_count
  });

  let pushPayloadStub = this.stub();
  this.service.get('store').pushPayload = pushPayloadStub;

  let peekRecordStub = this.stub().returns(mockSummary);
  this.service.get('store').peekRecord = peekRecordStub;

  // when
  this.service._onPubsubMessage(JSON.stringify(payload));

  // then
  assert.ok(pushPayloadStub.calledOnce);
  assert.equal(pushPayloadStub.firstCall.args[0], 'onsite-notification-summary');
  assert.deepEqual(pushPayloadStub.firstCall.args[1], payload.data.summary);

  assert.ok(peekRecordStub.calledOnce);
  assert.equal(peekRecordStub.firstCall.args[0], 'onsite-notification-summary');
  assert.equal(peekRecordStub.firstCall.args[1], 'user');

  assert.equal(this.service.get('summary'), mockSummary);
});

test('onPubsubMessage calls a handler based on response type', function(assert) {
  assert.expect(9);

  // given
  let handleCreatedOrUpdatedNotificationStub = this.stub(this.service, '_handleCreatedOrUpdatedNotification');
  let handleReadNotificationsStub = this.stub(this.service, '_handleReadNotifications');
  let handleDeleteNotificationStub = this.stub(this.service, '_handleDeleteNotification');

  let notificationId1 = nonce();
  let notificationId2 = nonce();

  // when
  this.service._onPubsubMessage(JSON.stringify({type: PUBSUB_CREATE_NOTIFICATION, data: {
    persistent: true,
    notification: {
      id: notificationId1
    }
  }}));
  this.service._onPubsubMessage(JSON.stringify({type: PUBSUB_UPDATE_NOTIFICATION, data: {
    persistent: true,
    notification: {
      id: notificationId1
    }
  }}));

  // then
  assert.equal(handleCreatedOrUpdatedNotificationStub.callCount, 2);
  assert.equal(handleCreatedOrUpdatedNotificationStub.firstCall.args[0].id, notificationId1);
  assert.equal(handleCreatedOrUpdatedNotificationStub.secondCall.args[0].id, notificationId1);
  assert.ok(handleCreatedOrUpdatedNotificationStub.firstCall.args[1]);
  assert.ok(handleCreatedOrUpdatedNotificationStub.secondCall.args[1]);

  // when
  this.service._onPubsubMessage(JSON.stringify({type: PUBSUB_READ_NOTIFICATIONS, data: {
    notification_ids: [notificationId1, notificationId2]
  }}));
  // then
  assert.ok(handleReadNotificationsStub.calledOnce);
  assert.deepEqual(handleReadNotificationsStub.firstCall.args[0], [notificationId1, notificationId2]);

  // when
  this.service._onPubsubMessage(JSON.stringify({type: PUBSUB_DELETE_NOTIFICATION, data: {
    notification_id: notificationId1
  }}));
  // then
  assert.ok(handleDeleteNotificationStub.calledOnce);
  assert.equal(handleDeleteNotificationStub.firstCall.args[0], notificationId1);
});
