Skip to content

Commit

Permalink
Fix Chat auto-connect methods and provide full e2e tests (#469)
Browse files Browse the repository at this point in the history
* make all the Chat methods be able to auto-connect the client

* incluses vs in error

* apply same changes for realtime api

* full e2e tests for chat

* export timeoutPromise and make it generic

* dry a bit using timeoutPromise

* cleanup some jest run

* do not intercept/auto-connect chat unsubscribe

* pass API_HOST to drone

* revert smock test

* Update internal/e2e-realtime-api/src/utils.ts

Co-authored-by: Francisco Ramini <framini@gmail.com>

* changesets

Co-authored-by: Francisco Ramini <framini@gmail.com>
  • Loading branch information
edolix and framini authored Mar 22, 2022
1 parent 1944348 commit 4d7bcc3
Show file tree
Hide file tree
Showing 14 changed files with 474 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/plenty-bikes-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sw-internal/e2e-realtime-api': patch
---

Add full e2e tests for Chat
7 changes: 7 additions & 0 deletions .changeset/slimy-turkeys-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@signalwire/core': patch
'@signalwire/js': patch
'@signalwire/realtime-api': patch
---

Fix Chat methods that required the underlay client to be connected.
2 changes: 2 additions & 0 deletions .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ steps:
- npm run build
- npm run -w=@sw-internal/e2e-realtime-api dev
environment:
API_HOST:
from_secret: API_HOST
RELAY_HOST:
from_secret: RELAY_HOST
RELAY_PROJECT:
Expand Down
1 change: 1 addition & 0 deletions internal/e2e-realtime-api/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
API_HOST=api.domain.com
RELAY_HOST=example.domain.com
RELAY_PROJECT=xxx
RELAY_TOKEN=yyy
Expand Down
3 changes: 3 additions & 0 deletions internal/e2e-realtime-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
"devDependencies": {
"dotenv": "^16.0.0",
"esbuild-register": "^3.2.1"
},
"dependencies": {
"ws": "^8.5.0"
}
}
318 changes: 318 additions & 0 deletions internal/e2e-realtime-api/src/chat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
/**
* The goal here is to run Chat from `realtime-api` and `js` SDKs and make sure
* they both receive the proper responses and events.
* The `handler` method grab a CRT and connects a JS ChatClient and a RealtimeAPI ChatClient
* and the consume all the methods asserting both SDKs receive the proper events.
*/
import { timeoutPromise } from '@signalwire/core'
import { Chat as RealtimeAPIChat } from '@signalwire/realtime-api'
import { Chat as JSChat } from '@signalwire/js'
import { WebSocket } from 'ws'
import { createTestRunner, createCRT } from './utils'

// @ts-ignore
global.WebSocket = WebSocket

const promiseTimeout = 4_000
const promiseException = 4 // error code to identify the Promise timeout
// TODO: pass as argument
const channel = 'rw'

const params = {
memberId: 'e2e-uuid-here',
channels: {
rw: {
read: true,
write: true,
},
r: {
read: true,
},
w: {
write: true,
},
},
}

type ChatClient = RealtimeAPIChat.ChatClient | JSChat.Client
const testChatClientSubscribe = (
firstClient: ChatClient,
secondClient: ChatClient
) => {
const promise = new Promise<number>(async (resolve) => {
console.log('Running subscribe..')
let events = 0
const resolveIfDone = () => {
// wait 4 events (rt and js receive their own events + the other member)
if (events === 4) {
firstClient.off('member.joined')
secondClient.off('member.joined')
resolve(0)
}
}

firstClient.on('member.joined', (member) => {
// TODO: Check the member payload
console.log('jsChat member.joined', member)
events += 1
resolveIfDone()
})
secondClient.on('member.joined', (member) => {
// TODO: Check the member payload
console.log('rtChat member.joined', member)
events += 1
resolveIfDone()
})

await Promise.all([
firstClient.subscribe(channel),
secondClient.subscribe(channel),
])
})

return timeoutPromise(promise, promiseTimeout, promiseException)
}

const testChatClientPublish = (
firstClient: ChatClient,
secondClient: ChatClient
) => {
const promise = new Promise<number>(async (resolve) => {
console.log('Running publish..')
let events = 0
const resolveIfDone = () => {
if (events === 2) {
resolve(0)
}
}

const now = Date.now()
firstClient.once('message', (message) => {
console.log('jsChat message', message)
if (message.meta.now === now) {
events += 1
resolveIfDone()
}
})
secondClient.once('message', (message) => {
console.log('rtChat message', message)
if (message.meta.now === now) {
events += 1
resolveIfDone()
}
})

await Promise.all([
firstClient.subscribe(channel),
secondClient.subscribe(channel),
])

await firstClient.publish({
content: 'Hello There',
channel,
meta: {
now,
foo: 'bar',
},
})
})

return timeoutPromise(promise, promiseTimeout, promiseException)
}

const testChatClientUnsubscribe = (
firstClient: ChatClient,
secondClient: ChatClient
) => {
const promise = new Promise<number>(async (resolve) => {
console.log('Running unsubscribe..')
let events = 0
const resolveIfDone = () => {
/**
* waits for 3 events:
* - first one generates 2 events on leave
* - second one generates only 1 event
*/
if (events === 3) {
firstClient.off('member.left')
secondClient.off('member.left')
resolve(0)
}
}

firstClient.on('member.left', (member) => {
// TODO: Check the member payload
console.log('jsChat member.left', member)
events += 1
resolveIfDone()
})
secondClient.on('member.left', (member) => {
// TODO: Check the member payload
console.log('rtChat member.left', member)
events += 1
resolveIfDone()
})

await Promise.all([
firstClient.subscribe(channel),
secondClient.subscribe(channel),
])

await firstClient.unsubscribe(channel)

await secondClient.unsubscribe(channel)
})

return timeoutPromise(promise, promiseTimeout, promiseException)
}

const testChatClientMethods = async (client: ChatClient) => {
console.log('Get Messages..')
const jsMessagesResult = await client.getMessages({
channel,
})
if (!jsMessagesResult.messages) {
console.error('jsChat getMessages error')
return 4
}

return 0
}

const testChatClientSetAndGetMemberState = (
firstClient: ChatClient,
secondClient: ChatClient
) => {
const promise = new Promise<number>(async (resolve, reject) => {
console.log('Set member state..')
let events = 0
const resolveIfDone = () => {
if (events === 2) {
resolve(0)
}
}

firstClient.once('member.updated', (member) => {
// TODO: Check the member payload
console.log('jsChat member.updated', member)
if (member.state.email === 'e2e@example.com') {
events += 1
resolveIfDone()
}
})
secondClient.once('member.updated', (member) => {
console.log('rtChat member.updated', member)
if (member.state.email === 'e2e@example.com') {
events += 1
resolveIfDone()
}
})

console.log('Get Member State..')
const getStateResult = await firstClient.getMemberState({
channels: [channel],
memberId: params.memberId,
})
// TODO: Better compare getStateResult
if (!getStateResult.channels.rw.state) {
console.error('Invalid state', JSON.stringify(getStateResult))
reject(4)
}

await Promise.all([
firstClient.subscribe(channel),
secondClient.subscribe(channel),
])

await firstClient.setMemberState({
channels: [channel],
memberId: params.memberId,
state: {
email: 'e2e@example.com',
},
})
})

return timeoutPromise(promise, promiseTimeout, promiseException)
}

const handler = async () => {
// Create JS Chat Client
const CRT = await createCRT(params)
const jsChat = new JSChat.Client({
host: process.env.RELAY_HOST,
// @ts-expect-error
token: CRT.token,
})

const jsChatResultCode = await testChatClientMethods(jsChat)
if (jsChatResultCode !== 0) {
return jsChatResultCode
}
console.log('Created jsChat')

// Create RT-API Chat Client
const rtChat = new RealtimeAPIChat.Client({
// @ts-expect-error
host: process.env.RELAY_HOST,
project: process.env.RELAY_PROJECT as string,
token: process.env.RELAY_TOKEN as string,
})

const rtChatResultCode = await testChatClientMethods(rtChat)
if (rtChatResultCode !== 0) {
return rtChatResultCode
}
console.log('Created rtChat')

// Test Subscribe
const subscribeResultCode = await testChatClientSubscribe(jsChat, rtChat)
if (subscribeResultCode !== 0) {
return subscribeResultCode
}

// Test Publish
const jsChatPublishCode = await testChatClientPublish(jsChat, rtChat)
if (jsChatPublishCode !== 0) {
return jsChatPublishCode
}
const rtChatPublishCode = await testChatClientPublish(rtChat, jsChat)
if (rtChatPublishCode !== 0) {
return rtChatPublishCode
}

// Test Set/Get Member State
const jsChatGetSetStateCode = await testChatClientSetAndGetMemberState(
jsChat,
rtChat
)
if (jsChatGetSetStateCode !== 0) {
return jsChatGetSetStateCode
}
const rtChatGetSetStateCode = await testChatClientSetAndGetMemberState(
rtChat,
jsChat
)
if (rtChatGetSetStateCode !== 0) {
return rtChatGetSetStateCode
}

// Test Unsubscribe
const unsubscribeResultCode = await testChatClientUnsubscribe(jsChat, rtChat)
if (unsubscribeResultCode !== 0) {
return unsubscribeResultCode
}

return 0
}

async function main() {
const runner = createTestRunner({
name: 'Chat E2E',
testHandler: handler,
})

await runner.run()
}

main()
Loading

0 comments on commit 4d7bcc3

Please sign in to comment.