pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
272 lines (235 loc) • 9.69 kB
text/typescript
import { after, binding, given, then, when } from 'cucumber-tsflow';
import { DemoKeyset } from '../shared/keysets';
import { PubNub, PubNubManager } from '../shared/pubnub';
// import type { MessageEvent, PresenceEvent, StatusEvent } from 'pubnub';
import type { Change } from '../../../src/event-engine/core/change';
import { DataTable } from '@cucumber/cucumber';
import { expect } from 'chai';
import PubNubClass from '../../../lib/node/index.js';
import { Subscription, StatusEvent } from '../../../lib/types';
function logChangelog(changelog: Change<any, any>) {
switch (changelog.type) {
case 'engineStarted':
console.log(`START ${changelog.state.label}`);
return;
case 'transitionDone':
console.log(`${changelog.fromState.label} ===> ${changelog.toState.label}`);
return;
case 'invocationDispatched':
console.log(
`◊ ${changelog.invocation.type} ${changelog.invocation.type === 'CANCEL' ? changelog.invocation.payload : ''}`,
);
return;
case 'eventReceived':
console.log(`! ${changelog.event.type}`);
return;
}
}
([PubNubManager, DemoKeyset])
class EventEngineSteps {
private pubnub?: PubNub;
private messagePromise?: Promise<Subscription.MessageEvent>;
private statusPromise?: Promise<StatusEvent>;
private presencePromise?: Promise<Subscription.PresenceEvent>;
private changelog: Change<any, any>[] = [];
private configuration: any = {};
constructor(
private manager: PubNubManager,
private keyset: DemoKeyset,
) {}
private async testDelay(time: number) {
return new Promise((resolve) => setTimeout(resolve, time * 1000));
}
('the demo keyset with event engine enabled')
givenDemoKeyset() {
this.pubnub = this.manager.getInstance({ ...this.keyset, enableEventEngine: true });
(this.pubnub as any).eventEngine._engine.subscribe((changelog: Change<any, any>) => {
if (changelog.type === 'eventReceived' || changelog.type === 'invocationDispatched') {
this.changelog.push(changelog);
}
});
}
('the demo keyset with Presence EE enabled')
givenPresenceEEDemoKeyset() {
this.configuration.enableEventEngine = true;
}
('heartbeatInterval set to {string}, timeout set to {string} and suppressLeaveEvents set to {string}')
whenPresenceConfiguration(heartbeatInterval: string, presenceTimeout: string, suppressLeaveEvents: string) {
this.configuration.heartbeatInterval = +heartbeatInterval;
this.configuration.presenceTimeout = +presenceTimeout;
this.configuration.suppressLeaveEvents = suppressLeaveEvents === 'true';
}
('I join {string}, {string}, {string} channels')
whenJoinChannels(channelOne: string, channelTwo: string, channelThree: string) {
this.pubnub = this.manager.getInstance({ ...this.keyset, ...this.configuration });
(this.pubnub as any).presenceEventEngine?._engine.subscribe((changelog: Change<any, any>) => {
if (changelog.type === 'eventReceived' || changelog.type === 'invocationDispatched') {
this.changelog.push(changelog);
}
});
this.pubnub?.subscribe({ channels: [channelOne, channelTwo, channelThree] });
}
('I join {string}, {string}, {string} channels with presence')
whenJoinChannelsWithPresence(channelOne: string, channelTwo: string, channelThree: string) {
this.pubnub = this.manager.getInstance({ ...this.keyset, ...this.configuration });
(this.pubnub as any)?.presenceEventEngine?._engine.subscribe((changelog: Change<any, any>) => {
if (changelog.type === 'eventReceived' || changelog.type === 'invocationDispatched') {
this.changelog.push(changelog);
}
});
this.statusPromise = new Promise<StatusEvent>((resolveStatus) => {
this.presencePromise = new Promise<Subscription.PresenceEvent>((resolvePresence) => {
this.pubnub?.addListener({
presence(presenceEvent) {
resolvePresence(presenceEvent);
},
status(statusEvent) {
resolveStatus(statusEvent);
},
});
this.pubnub?.subscribe({ channels: [channelOne, channelTwo, channelThree], withPresence: true });
});
});
}
('I wait for getting Presence joined events', undefined, 10000)
async thenPresenceJoinEvent() {
const status = await this.presencePromise;
}
('I wait {string} seconds')
async thenWait(seconds: string) {
await this.testDelay(+seconds);
}
('I observe the following Events and Invocations of the Presence EE:')
async thenIObservePresenceEE(dataTable: DataTable) {
const expectedChangelog = dataTable.hashes();
const actualChangelog: { type: string; name: string }[] = [];
for (const entry of this.changelog) {
if (entry.type === 'eventReceived') {
actualChangelog.push({ type: 'event', name: entry.event.type });
} else if (entry.type === 'invocationDispatched') {
actualChangelog.push({
type: 'invocation',
name: `${entry.invocation.type}${entry.invocation.type === 'CANCEL' ? `_${entry.invocation.payload}` : ''}`,
});
}
}
expect(actualChangelog).to.deep.equal(expectedChangelog);
}
('I leave {string} and {string} channels with presence')
async theILeave(channelOne: string, channelTwo: string) {
await this.testDelay(0.02);
this.pubnub?.unsubscribe({ channels: [channelOne, channelTwo] });
}
('a linear reconnection policy with {int} retries')
givenLinearReconnectionPolicy(retries: number) {
// @ts-ignore
this.configuration.retryConfiguration = PubNubClass.LinearRetryPolicy({
delay: 2,
maximumRetry: retries,
});
// @ts-ignore
this.pubnub = this.manager.getInstance({
...this.keyset,
enableEventEngine: true,
// @ts-ignore
retryConfiguration: PubNubClass.LinearRetryPolicy({
delay: 2,
maximumRetry: retries,
}),
});
(this.pubnub as any).eventEngine._engine.subscribe((changelog: Change<any, any>) => {
if (changelog.type === 'eventReceived' || changelog.type === 'invocationDispatched') {
this.changelog.push(changelog);
}
});
}
('I receive an error in my heartbeat response', undefined, 10000)
async thenHeartbeatError() {
await this.testDelay(9);
}
('I subscribe')
async whenISubscribe() {
this.statusPromise = new Promise<StatusEvent>((resolveStatus) => {
this.messagePromise = new Promise<Subscription.MessageEvent>((resolveMessage) => {
this.pubnub?.addListener({
message(messageEvent) {
setTimeout(() => resolveMessage(messageEvent), 100);
},
status(statusEvent) {
setTimeout(() => resolveStatus(statusEvent), 100);
},
});
this.pubnub?.subscribe({ channels: ['test'] });
});
});
}
(/I subscribe with timetoken (\d*)/)
async whenISubscribeWithTimetoken(timetoken: number) {
this.statusPromise = new Promise<StatusEvent>((resolveStatus) => {
this.messagePromise = new Promise<Subscription.MessageEvent>((resolveMessage) => {
this.pubnub?.addListener({
message(messageEvent) {
setTimeout(() => resolveMessage(messageEvent), 100);
},
status(statusEvent) {
setTimeout(() => resolveStatus(statusEvent), 100);
},
});
this.pubnub?.subscribe({ channels: ['test'], timetoken: timetoken });
});
});
}
('I publish a message')
async whenIPublishAMessage() {
const status = await this.statusPromise;
expect(status?.category).to.equal('PNConnectedCategory');
const timetoken = await this.pubnub?.publish({ channel: 'test', message: { content: 'Hello world!' } });
}
('I receive an error in my subscribe response', undefined, 10000)
async thenIReceiveError() {
const status = await this.statusPromise;
expect(status?.category).to.equal('PNConnectionErrorCategory');
}
('I receive the message in my subscribe response', undefined, 10000)
async receiveMessage() {
const message = await this.messagePromise;
}
('I observe the following:')
thenIObserve(dataTable: DataTable) {
const expectedChangelog = dataTable.hashes();
const actualChangelog: { type: string; name: string }[] = [];
for (const entry of this.changelog) {
if (entry.type === 'eventReceived') {
actualChangelog.push({ type: 'event', name: entry.event.type });
} else if (entry.type === 'invocationDispatched') {
actualChangelog.push({
type: 'invocation',
name: `${entry.invocation.type}${entry.invocation.type === 'CANCEL' ? `_${entry.invocation.payload}` : ''}`,
});
}
}
expect(actualChangelog).to.deep.equal(expectedChangelog);
}
("I don't observe any Events and Invocations of the Presence EE")
noeventInvocations() {
const actualChangelog: { type: string; name: string }[] = [];
for (const entry of this.changelog) {
if (entry.type === 'eventReceived') {
actualChangelog.push({ type: 'event', name: entry.event.type });
} else if (entry.type === 'invocationDispatched') {
actualChangelog.push({
type: 'invocation',
name: `${entry.invocation.type}${entry.invocation.type === 'CANCEL' ? `_${entry.invocation.payload}` : ''}`,
});
}
}
expect(actualChangelog).to.deep.equal([]);
}
()
dispose() {
if (this.pubnub) {
(this.pubnub as any).removeAllListeners();
(this.pubnub as any).eventEngine.dispose();
}
}
}