UNPKG

pubnub

Version:

Publish & Subscribe Real-time Messaging with PubNub

566 lines (510 loc) 19.2 kB
/* global describe, beforeEach, afterEach, it, after */ /* eslint no-console: 0 */ import assert from 'assert'; import nock from 'nock'; import utils from '../../utils'; import PubNub from '../../../src/node/index'; function publishMessagesToChannel(client: PubNub, count: Number, channel: String, completion: Function) { let publishCompleted = 0; let messages = []; const publish = (messageIdx) => { let payload = { message: { messageIdx: [channel, messageIdx].join(': '), time: Date.now() }, channel }; if (messageIdx % 2 === 0) { payload.meta = { time: payload.message.time }; } client.publish(payload, (status, response) => { publishCompleted += 1; if (!status.error) { messages.push({ message: payload.message, timetoken: response.timetoken }); messages = messages.sort((left, right) => left.timetoken - right.timetoken); } else { console.error('Publish did fail:', status); } if (publishCompleted < count) { publish(publishCompleted); } else if (publishCompleted === count) { completion(messages); } }); }; publish(publishCompleted); } function addActionsInChannel( client: PubNub, count: Number, messageTimetokens: Array<Number>, channel: String, completion: Function ) { const types = ['reaction', 'receipt', 'custom']; const values = [ PubNub.generateUUID(), PubNub.generateUUID(), PubNub.generateUUID(), PubNub.generateUUID(), PubNub.generateUUID(), PubNub.generateUUID(), PubNub.generateUUID(), PubNub.generateUUID(), PubNub.generateUUID(), PubNub.generateUUID(), ]; let actionsToAdd = []; let actionsAdded = 0; let actions = []; for (let messageIdx = 0; messageIdx < messageTimetokens.length; messageIdx += 1) { const messageTimetoken = messageTimetokens[messageIdx]; for (let messageActionIdx = 0; messageActionIdx < count; messageActionIdx += 1) { /** @type MessageAction */ const action = { type: types[(messageActionIdx + 1) % 3], value: values[(messageActionIdx + 1) % 10] }; actionsToAdd.push({ messageTimetoken, action }); } } const addAction = (actionIdx) => { const { messageTimetoken, action } = actionsToAdd[actionIdx]; client.addMessageAction({ channel, messageTimetoken, action }, (status, response) => { actionsAdded += 1; if (!status.error) { actions.push(response.data); actions = actions.sort((left, right) => left.actionTimetoken - right.actionTimetoken); } else { console.error('Action add did fail:', status); } if (actionsAdded < actionsToAdd.length) { addAction(actionsAdded); } else if (actionsAdded === actionsToAdd.length) { completion(actions); } }); }; addAction(actionsAdded); } describe('fetch messages endpoints', () => { const subscribeKey = process.env.SUBSCRIBE_KEY || 'demo'; const publishKey = process.env.PUBLISH_KEY || 'demo'; let pubnub; after(() => { nock.enableNetConnect(); }); afterEach(() => { nock.enableNetConnect(); pubnub.removeAllListeners(); pubnub.unsubscribeAll(); pubnub.stop(); }); beforeEach(() => { nock.cleanAll(); pubnub = new PubNub({ subscribeKey, publishKey, uuid: 'myUUID', useRandomIVs: false }); }); it('supports payload', (done) => { nock.disableNetConnect(); const scope = utils .createNock() .get(`/v3/history/sub-key/${subscribeKey}/channel/ch1%2Cch2`) .query({ max: '10', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, uuid: 'myUUID', include_uuid: 'true', include_message_type: 'true', }) .reply( 200, '{ "channels": { "ch1": [{"message":{"text":"hey1"},"timetoken":"11"}, {"message":{"text":"hey2"},"timetoken":"12"}], "ch2": [{"message":{"text":"hey3"},"timetoken":"21"}, {"message":{"text":"hey2"},"timetoken":"22"}] } }' ); pubnub.fetchMessages({ channels: ['ch1', 'ch2'], count: 10 }, (status, response) => { assert.equal(status.error, false); assert.deepEqual(response, { channels: { ch1: [ { channel: 'ch1', message: { text: 'hey1', }, timetoken: '11', messageType: undefined, uuid: undefined, }, { channel: 'ch1', message: { text: 'hey2', }, timetoken: '12', messageType: undefined, uuid: undefined, }, ], ch2: [ { channel: 'ch2', message: { text: 'hey3', }, timetoken: '21', messageType: undefined, uuid: undefined, }, { channel: 'ch2', message: { text: 'hey2', }, timetoken: '22', messageType: undefined, uuid: undefined, }, ], }, }); assert.equal(scope.isDone(), true); done(); }); }); it('supports encrypted payload', (done) => { nock.disableNetConnect(); const scope = utils .createNock() .get(`/v3/history/sub-key/${subscribeKey}/channel/ch1%2Cch2`) .query({ max: '10', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, uuid: 'myUUID', include_uuid: 'true', include_message_type: 'true', }) .reply( 200, '{ "channels": { "ch1": [{"message":"zFJeF9BVABL80GUiQEBjLg==","timetoken":"11"}, {"message":"zFJeF9BVABL80GUiQEBjLg==","timetoken":"12"}], "ch2": [{"message":"HIq4MTi9nk/KEYlHOKpMCaH78ZXppGynDHrgY9nAd3s=","timetoken":"21"}, {"message":"HIq4MTi9nk/KEYlHOKpMCaH78ZXppGynDHrgY9nAd3s=","timetoken":"22"}] } }' ); pubnub.setCipherKey('cipherKey'); pubnub.fetchMessages({ channels: ['ch1', 'ch2'], count: 10 }, (status, response) => { assert.equal(status.error, false); assert.deepEqual(response, { channels: { ch1: [ { channel: 'ch1', message: { text: 'hey', }, timetoken: '11', messageType: undefined, uuid: undefined, }, { channel: 'ch1', message: { text: 'hey', }, timetoken: '12', messageType: undefined, uuid: undefined, }, ], ch2: [ { channel: 'ch2', message: { text2: 'hey2', }, timetoken: '21', messageType: undefined, uuid: undefined, }, { channel: 'ch2', message: { text2: 'hey2', }, timetoken: '22', messageType: undefined, uuid: undefined, }, ], }, }); assert.equal(scope.isDone(), true); done(); }); }); it('supports metadata', (done) => { const channel = PubNub.generateUUID(); const expectedMessagesCount = 10; publishMessagesToChannel(pubnub, expectedMessagesCount, channel, (messages) => { pubnub.fetchMessages({ channels: [channel], count: 25, includeMeta: true }, (status, response) => { const channelMessages = response.channels[channel]; assert.deepEqual(channelMessages[0].meta, { time: messages[0].message.time }); assert(!channelMessages[1].meta); done(); }); }); }).timeout(60000); it('throws when requested actions for multiple channels', () => { let errorCatched = false; try { pubnub.fetchMessages({ channels: ['channelA', 'channelB'], includeMessageActions: true }, () => {}); } catch (error) { assert.equal( error.message, 'History can return actions data for a single channel only. Either pass a single channel or disable the includeMessageActions flag.' ); errorCatched = true; } assert(errorCatched); }); it("supports actions (stored as 'data' field)", (done) => { const channel = PubNub.generateUUID(); const expectedMessagesCount = 2; const expectedActionsCount = 4; publishMessagesToChannel(pubnub, expectedMessagesCount, channel, (messages) => { const messageTimetokens = messages.map((message) => message.timetoken); addActionsInChannel(pubnub, expectedActionsCount, messageTimetokens, channel, (actions) => { setTimeout(() => { pubnub.fetchMessages({ channels: [channel], includeMessageActions: true }, (status, response) => { assert.equal(status.error, false); const fetchedMessages = response.channels[channel]; const actionsByType = fetchedMessages[0].data; let historyActionsCount = 0; Object.keys(actionsByType).forEach((actionType) => { Object.keys(actionsByType[actionType]).forEach((actionValue) => { let actionFound = false; historyActionsCount += 1; actions.forEach((action) => { if (action.value === actionValue) { actionFound = true; } }); assert.equal(actionFound, true); }); }); assert.equal(historyActionsCount, expectedActionsCount); assert.equal(fetchedMessages[0].timetoken, messageTimetokens[0]); assert.equal( fetchedMessages[fetchedMessages.length - 1].timetoken, messageTimetokens[messageTimetokens.length - 1] ); done(); }); }, 2000); }); }); }).timeout(60000); it("supports actions (stored as 'actions' field)", (done) => { const channel = PubNub.generateUUID(); const expectedMessagesCount = 2; const expectedActionsCount = 4; publishMessagesToChannel(pubnub, expectedMessagesCount, channel, (messages) => { const messageTimetokens = messages.map((message) => message.timetoken); addActionsInChannel(pubnub, expectedActionsCount, messageTimetokens, channel, (actions) => { setTimeout(() => { pubnub.fetchMessages({ channels: [channel], includeMessageActions: true }, (status, response) => { assert.equal(status.error, false); const fetchedMessages = response.channels[channel]; const actionsByType = fetchedMessages[0].actions; let historyActionsCount = 0; Object.keys(actionsByType).forEach((actionType) => { Object.keys(actionsByType[actionType]).forEach((actionValue) => { let actionFound = false; historyActionsCount += 1; actions.forEach((action) => { if (action.value === actionValue) { actionFound = true; } }); assert.equal(actionFound, true); }); }); assert.equal(historyActionsCount, expectedActionsCount); assert.equal(fetchedMessages[0].timetoken, messageTimetokens[0]); assert.equal( fetchedMessages[fetchedMessages.length - 1].timetoken, messageTimetokens[messageTimetokens.length - 1] ); done(); }); }, 2000); }); }); }).timeout(60000); it('should add fetch messages API telemetry information', (done) => { nock.disableNetConnect(); let scope = utils.createNock().get(`/v3/history/sub-key/${subscribeKey}/channel/ch1%2Cch2`).query(true); const delays = [100, 200, 300, 400]; const countedDelays = delays.slice(0, delays.length - 1); const average = Math.floor(countedDelays.reduce((acc, delay) => acc + delay, 0) / countedDelays.length); const leeway = 50; utils .runAPIWithResponseDelays( scope, 200, '{ "channels": { "ch1": [{"message":{"text":"hey1"},"timetoken":"11"}, {"message":{"text":"hey2"},"timetoken":"12"}], "ch2": [{"message":{"text":"hey3"},"timetoken":"21"}, {"message":{"text":"hey2"},"timetoken":"22"}] } }', delays, (completion) => { pubnub.fetchMessages({ channels: ['ch1', 'ch2'], count: 10 }, () => { completion(); }); } ) .then((lastRequest) => { utils.verifyRequestTelemetry(lastRequest.path, 'l_hist', average, leeway); done(); }); }).timeout(60000); it('should return "more" field when server sends it', (done) => { nock.disableNetConnect(); const scope = utils .createNock() .get(`/v3/history-with-actions/sub-key/${subscribeKey}/channel/ch1`) .query({ pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, uuid: 'myUUID', max: '25', include_uuid: 'true', include_message_type: 'true' }) .reply( 200, '{"status":200,"error":false,"error_message":"","channels":{"demo-channel":[{"message":"Hi","timetoken":15610547826970040,"actions":{"receipt":{"read":[{"uuid":"user-7","actionTimetoken":15610547826970044}]}}},{"message":"Hello","timetoken":15610547826970000,"actions":{"reaction":{"smiley_face":[{"uuid":"user-456","actionTimetoken":15610547826970050}]}}}]},"more":{"url":"/v3/history-with-actions/sub-key/s/channel/c?start=15610547826970000&max=98","start":"15610547826970000","max":98}}' ); pubnub.fetchMessages({ channels: ['ch1'], includeMessageActions: true}, (status, response) => { assert.equal(status.error, false); assert.equal(response.more.url, '/v3/history-with-actions/sub-key/s/channel/c?start=15610547826970000&max=98'); assert.equal(response.more.start, '15610547826970000'); assert.equal(response.more.max, 98); done(); }); }); it('should request 100 messages when count not provided with single channel', (done) => { nock.disableNetConnect(); const scope = utils .createNock() .get(`/v3/history/sub-key/${subscribeKey}/channel/ch1`) .query({ max: '100', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, uuid: 'myUUID', include_uuid: 'true', include_message_type: 'true', }) .reply( 200, '{ "error": false, "error_message": "", "channels": { "ch1": [ { "message_type": null, "message": "hello world", "timetoken": "16048329933709932", "uuid": "test-uuid"} ] } }' ); pubnub.fetchMessages({ channels: ['ch1'] }, (status, response) => { assert.equal(status.error, false); done(); }); }); it('should request 25 messages when count not provided with multiple channels', (done) => { nock.disableNetConnect(); const scope = utils .createNock() .get(`/v3/history/sub-key/${subscribeKey}/channel/ch1%2Cch2`) .query({ max: '25', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, uuid: 'myUUID', include_uuid: 'true', include_message_type: 'true', }) .reply( 200, '{ "error": false, "error_message": "", "channels": { "ch1": [ { "message_type": null, "message": "hello world", "timetoken": "16048329933709932", "uuid": "test-uuid"} ] } }' ); pubnub.fetchMessages({ channels: ['ch1', 'ch2'] }, (status, response) => { assert.equal(status.error, false); done(); }); }); it('should request 25 messages when count not provided for history-with-actions', (done) => { nock.disableNetConnect(); const scope = utils .createNock() .get(`/v3/history-with-actions/sub-key/${subscribeKey}/channel/ch1`) .query({ max: '25', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, uuid: 'myUUID', include_uuid: 'true', include_message_type: 'true' }) .reply( 200, '{ "error": false, "error_message": "", "channels": { "ch1": [ { "message_type": null, "message": "hello world", "timetoken": "16048329933709932", "uuid": "test-uuid"} ] } }' ); pubnub.fetchMessages({ channels: ['ch1'], includeMessageActions: true}, (status, response) => { assert.equal(status.error, false); done(); }); }); it('should request provided number of messages when count is specified for history-with-actions', (done) => { nock.disableNetConnect(); const scope = utils .createNock() .get(`/v3/history-with-actions/sub-key/${subscribeKey}/channel/ch1`) .query({ max: '10', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, uuid: 'myUUID', include_uuid: 'true', include_message_type: 'true' }) .reply( 200, '{ "error": false, "error_message": "", "channels": { "ch1": [ { "message_type": null, "message": "hello world", "timetoken": "16048329933709932", "uuid": "test-uuid"} ] } }' ); pubnub.fetchMessages({ channels: ['ch1'], includeMessageActions: true, count: 10}, (status, response) => { assert.equal(status.error, false); done(); }); }); it('should request provided number of messages when count is specified for batch history with single channel', (done) => { nock.disableNetConnect(); const scope = utils .createNock() .get(`/v3/history/sub-key/${subscribeKey}/channel/ch1`) .query({ max: '10', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, uuid: 'myUUID', include_uuid: 'true', include_message_type: 'true' }) .reply( 200, '{ "error": false, "error_message": "", "channels": { "ch1": [ { "message_type": null, "message": "hello world", "timetoken": "16048329933709932", "uuid": "test-uuid"} ] } }' ); pubnub.fetchMessages({ channels: ['ch1'], count: 10}, (status, response) => { assert.equal(status.error, false); done(); }); }); it('should request provided number of messages when count is specified for batch history with multiple channels', (done) => { nock.disableNetConnect(); const scope = utils .createNock() .get(`/v3/history/sub-key/${subscribeKey}/channel/ch1%2Cch2`) .query({ max: '10', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, uuid: 'myUUID', include_uuid: 'true', include_message_type: 'true' }) .reply( 200, '{ "error": false, "error_message": "", "channels": { "ch1": [ { "message_type": null, "message": "hello world", "timetoken": "16048329933709932", "uuid": "test-uuid"} ] } }' ); pubnub.fetchMessages({ channels: ['ch1', 'ch2'], count: 10}, (status, response) => { assert.equal(status.error, false); done(); }); }); });