mirror of
https://github.com/discordeno/discordeno.git
synced 2026-09-17 08:47:22 +00:00
test(bot,gateway): Fix Bot E2E and Gateway Integration tests (#4084)
* Fix bot e2e, refactor gateway integration tests * use debug for bot e2e log * Discard changes to packages/bot/src/bot.ts
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Intents } from '@discordeno/types'
|
||||
import { delay, logger, snakeToCamelCase } from '@discordeno/utils'
|
||||
import { delay, logger } from '@discordeno/utils'
|
||||
import { use as chaiUse } from 'chai'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
import { describe, it } from 'mocha'
|
||||
@@ -18,24 +18,7 @@ describe('[Bot] Delete any guild owned guilds', () => {
|
||||
ownerId: true,
|
||||
},
|
||||
},
|
||||
gateway: {
|
||||
token,
|
||||
events: {
|
||||
message: async (shard, data) => {
|
||||
// TRIGGER RAW EVENT
|
||||
bot.events.raw?.(data, shard.id)
|
||||
|
||||
if (!data.t) return
|
||||
|
||||
// RUN DISPATCH CHECK
|
||||
await bot.events.dispatchRequirements?.(data, shard.id)
|
||||
|
||||
const eventName = snakeToCamelCase(data.t)
|
||||
bot.events[eventName as keyof typeof bot.events]?.(data.d as never, shard as never)
|
||||
},
|
||||
},
|
||||
intents: Intents.Guilds,
|
||||
},
|
||||
intents: Intents.Guilds,
|
||||
events: {
|
||||
async guildCreate(guild) {
|
||||
if (guild.joinedAt && Date.now() - guild.joinedAt < 360000) {
|
||||
@@ -43,7 +26,7 @@ describe('[Bot] Delete any guild owned guilds', () => {
|
||||
}
|
||||
|
||||
if (bot.rest.applicationId === guild.ownerId) {
|
||||
logger.debug(`Deleting one of the bot created guilds.`, guild.id)
|
||||
logger.debug(`Deleting one of the bot created guilds. ${guild.id}`)
|
||||
await bot.rest.deleteGuild(guild.id)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"test:unit:watch": "mocha --no-warnings --watch --parallel 'tests/unit/**/*.spec.ts'",
|
||||
"test:deno-unit": "deno -A ../../node_modules/mocha/bin/mocha.js --config ../../.mocharc.base.cjs 'tests/unit/**/*.spec.ts'",
|
||||
"test:bun-unit": "bun ../../node_modules/mocha/bin/mocha.js --config ../../.mocharc.base.cjs 'tests/unit/**/*.spec.ts'",
|
||||
"test:integration": "c8 --r lcov mocha --no-warnings 'tests/integration/**/*.spec.ts' && node ../../scripts/coveragePathFixing.js gateway",
|
||||
"test:integration": "c8 --r lcov mocha --no-warnings --t 15000 'tests/integration/**/*.spec.ts' && node ../../scripts/coveragePathFixing.js gateway",
|
||||
"test:type": "tsc --noEmit",
|
||||
"test:test-type": "tsc --project tests/tsconfig.json"
|
||||
},
|
||||
@@ -52,7 +52,6 @@
|
||||
"sinon": "^19.0.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig": "*",
|
||||
"typescript": "^5.7.2",
|
||||
"uWebSockets.js": "uNetworking/uWebSockets.js#54e5db370db746ed1c85021bdf215578a2164901"
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,57 @@
|
||||
import { type DiscordGatewayPayload, GatewayCloseEventCodes, GatewayOpcodes, Intents } from '@discordeno/types'
|
||||
import uWS from 'uWebSockets.js'
|
||||
import { type GatewayManager, createGatewayManager } from '../../src/index.js'
|
||||
import { GatewayOpcodes, Intents } from '@discordeno/types'
|
||||
import { type GatewayManager, ShardSocketCloseCodes, createGatewayManager } from '../../src/index.js'
|
||||
import { creatWSServer, heartbeatInterval } from './websocket.js'
|
||||
|
||||
/**
|
||||
* This value needs to be AT LEAST `1017`
|
||||
*
|
||||
* The reason for this is because the calculation in Shard.calculateSafeRequests will return 0 not allowing any sort of message to the websocket server.
|
||||
* Discord uses a way higher number for this value, but during this test we lower it since it would be annoying and useless make the test last 40+ seconds to test the heartbeat, but to make this work it needs to be at least 1017 so that calculateSafeRequests return 2 allowing for the shard to send messages.
|
||||
*/
|
||||
const heartbeatInterval = 1050
|
||||
describe('Gateway Integration', () => {
|
||||
it('Can connect to server', async () => {
|
||||
const { promise: connected, resolve: resolveConnected } = promiseWithResolvers<void>()
|
||||
|
||||
const { port, close } = creatWSServer({
|
||||
onOpen: resolveConnected,
|
||||
})
|
||||
|
||||
const gateway = createGatewayManagerWithPort(port)
|
||||
await gateway.spawnShards()
|
||||
await connected
|
||||
|
||||
await gateway.shutdown(ShardSocketCloseCodes.TestingFinished, 'Testing finished')
|
||||
|
||||
// To avoid needing to wait 1m to get the bucket refil timer to fire we cancel it
|
||||
clearTimeout(gateway.shards.get(0)?.bucket.timeoutId)
|
||||
|
||||
close()
|
||||
})
|
||||
|
||||
it('Can heartbeat', async () => {
|
||||
const { promise: connected, resolve: resolveConnected } = promiseWithResolvers<void>()
|
||||
const { promise: heartbeated, resolve: resolveHeartbeat, reject: rejectHeartbeat } = promiseWithResolvers<void>()
|
||||
|
||||
const { port, close } = creatWSServer({
|
||||
onOpen: resolveConnected,
|
||||
onMessage: (message) => {
|
||||
if (message.op === GatewayOpcodes.Heartbeat) {
|
||||
resolveHeartbeat()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const gateway = createGatewayManagerWithPort(port)
|
||||
await gateway.spawnShards()
|
||||
await connected
|
||||
|
||||
const timeout = setTimeout(() => rejectHeartbeat(new Error('Not heartbeat in time')), heartbeatInterval)
|
||||
await heartbeated
|
||||
|
||||
clearTimeout(timeout)
|
||||
|
||||
await gateway.shutdown(ShardSocketCloseCodes.TestingFinished, 'Testing finished')
|
||||
|
||||
// To avoid needing to wait 1m to get the bucket refil timer to fire we cancel it
|
||||
clearTimeout(gateway.shards.get(0)?.bucket.timeoutId)
|
||||
|
||||
close()
|
||||
})
|
||||
})
|
||||
|
||||
function createGatewayManagerWithPort(port: number): GatewayManager {
|
||||
return createGatewayManager({
|
||||
@@ -17,164 +60,22 @@ function createGatewayManagerWithPort(port: number): GatewayManager {
|
||||
shards: 1,
|
||||
sessionStartLimit: {
|
||||
total: 1000,
|
||||
remaining: 998,
|
||||
resetAfter: 36579894,
|
||||
remaining: 1000,
|
||||
resetAfter: 0,
|
||||
maxConcurrency: 1,
|
||||
},
|
||||
},
|
||||
token: ' ',
|
||||
token: '',
|
||||
url: `ws://localhost:${port}`,
|
||||
intents: Intents.Guilds,
|
||||
events: {},
|
||||
})
|
||||
}
|
||||
|
||||
async function createUws(options: CreateUwsOptions) {
|
||||
let port: number
|
||||
|
||||
const { promise, resolve, reject } = promiseWithResolvers<{ port: number; uwsToken: uWS.us_listen_socket }>()
|
||||
|
||||
const app = uWS.App()
|
||||
|
||||
app.ws('/*', {
|
||||
open: async (ws) => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
op: GatewayOpcodes.Hello,
|
||||
d: {
|
||||
heartbeat_interval: heartbeatInterval,
|
||||
},
|
||||
}),
|
||||
)
|
||||
options.onOpen?.()
|
||||
},
|
||||
message: async (ws, message, _isBinary) => {
|
||||
const msg = JSON.parse(Buffer.from(message).toString())
|
||||
options.onMessage?.(msg)
|
||||
|
||||
if (msg.op === GatewayOpcodes.Heartbeat) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
op: GatewayOpcodes.HeartbeatACK,
|
||||
}),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
if (msg.op === GatewayOpcodes.Identify) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
t: 'READY',
|
||||
s: 1,
|
||||
op: GatewayOpcodes.Dispatch,
|
||||
d: {
|
||||
v: 10,
|
||||
user_settings: {},
|
||||
user: {
|
||||
verified: true,
|
||||
username: 'testing bot',
|
||||
mfa_enabled: false,
|
||||
id: '000000707882254000',
|
||||
flags: 0,
|
||||
email: null,
|
||||
discriminator: '1687',
|
||||
bot: true,
|
||||
avatar: null,
|
||||
},
|
||||
shard: [0, 1],
|
||||
session_type: 'normal',
|
||||
session_id: '0dff79e1a6f2697388eb08924a0805c8',
|
||||
resume_gateway_url: `ws://localhost:${port}`,
|
||||
relationships: [],
|
||||
private_channels: [],
|
||||
presences: [],
|
||||
guilds: [],
|
||||
guild_join_requests: [],
|
||||
geo_ordered_rtc_regions: [],
|
||||
application: { id: '000000707882254000', flags: 27828224 },
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
resharding: {
|
||||
enabled: false,
|
||||
checkInterval: 0,
|
||||
shardsFullPercentage: 0,
|
||||
},
|
||||
})
|
||||
|
||||
app.listen(0, (token) => {
|
||||
if (!token) {
|
||||
reject('Unable to get the socket token')
|
||||
}
|
||||
|
||||
port = uWS.us_socket_local_port(token)
|
||||
|
||||
resolve({
|
||||
port,
|
||||
uwsToken: token,
|
||||
})
|
||||
})
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
describe('gateway', () => {
|
||||
it('can connect to server', async function () {
|
||||
this.timeout(15000)
|
||||
|
||||
const { promise: connected, resolve: resolveConnected } = promiseWithResolvers<void>()
|
||||
|
||||
const uwsOptions: CreateUwsOptions = {
|
||||
onOpen: resolveConnected,
|
||||
}
|
||||
|
||||
const { port, uwsToken } = await createUws(uwsOptions)
|
||||
|
||||
const gateway = createGatewayManagerWithPort(port)
|
||||
await gateway.spawnShards()
|
||||
await connected
|
||||
|
||||
// TODO: We should use ShardSocketCloseCodes.TestingFinished but there is an issue with sending 3xxx codes to uWS
|
||||
await gateway.shutdown(GatewayCloseEventCodes.InvalidShard, 'User requested bot stop', true)
|
||||
|
||||
uWS.us_listen_socket_close(uwsToken)
|
||||
})
|
||||
|
||||
it('will heartbeat', async function () {
|
||||
this.timeout(15000)
|
||||
|
||||
const { promise: connected, resolve: resolveConnected } = promiseWithResolvers<void>()
|
||||
const { promise: heartbeated, resolve: resolveHeartbeat } = promiseWithResolvers<void>()
|
||||
|
||||
const uwsOptions: CreateUwsOptions = {
|
||||
onOpen: resolveConnected,
|
||||
onMessage: (message) => {
|
||||
if (message.op !== 1) return
|
||||
|
||||
resolveHeartbeat()
|
||||
},
|
||||
}
|
||||
|
||||
const { port, uwsToken } = await createUws(uwsOptions)
|
||||
|
||||
const gateway = createGatewayManagerWithPort(port)
|
||||
await gateway.spawnShards()
|
||||
await connected
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
throw new Error('Not heartbeat in time')
|
||||
}, heartbeatInterval)
|
||||
|
||||
await heartbeated
|
||||
|
||||
clearTimeout(timeout)
|
||||
|
||||
// TODO: We should use ShardSocketCloseCodes.TestingFinished but there is an issue with sending 3xxx codes to uWS
|
||||
await gateway.shutdown(GatewayCloseEventCodes.InvalidShard, 'User requested bot stop', true)
|
||||
|
||||
uWS.us_listen_socket_close(uwsToken)
|
||||
})
|
||||
})
|
||||
|
||||
// Polyfill for https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
|
||||
function promiseWithResolvers<T>() {
|
||||
let resolve!: (value: T | PromiseLike<T>) => void
|
||||
@@ -191,8 +92,3 @@ function promiseWithResolvers<T>() {
|
||||
reject,
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateUwsOptions {
|
||||
onOpen?: () => any
|
||||
onMessage?: (message: DiscordGatewayPayload) => any
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { type DiscordGatewayPayload, GatewayOpcodes } from '@discordeno/types'
|
||||
import { type WebSocket, WebSocketServer } from 'ws'
|
||||
|
||||
/**
|
||||
* This value needs to be AT LEAST `1017`
|
||||
*
|
||||
* The reason for this is because the calculation in Shard.calculateSafeRequests will return 0 not allowing any sort of message to the websocket server.
|
||||
* Discord uses a way higher number for this value, but during this test we lower it since it would be annoying and useless make the test last 40+ seconds to test the heartbeat, but to make this work it needs to be at least 1017 so that calculateSafeRequests return 2 allowing for the shard to send messages.
|
||||
*/
|
||||
export const heartbeatInterval = 1017
|
||||
|
||||
export function creatWSServer(options: CreateWsServerOptions) {
|
||||
// Port 0 according to the node:http docs is for requesting the OS a random unused port
|
||||
const server = new WebSocketServer({ port: 0 })
|
||||
|
||||
const address = server.address()
|
||||
if (typeof address !== 'object' || !address) {
|
||||
throw new TypeError('The address of the WebSocketServer should be an non-null object')
|
||||
}
|
||||
|
||||
server.on('connection', (socket) => {
|
||||
send(socket, {
|
||||
op: GatewayOpcodes.Hello,
|
||||
s: null,
|
||||
d: {
|
||||
heartbeat_interval: heartbeatInterval,
|
||||
},
|
||||
})
|
||||
|
||||
options.onOpen?.()
|
||||
|
||||
socket.on('message', (data) => {
|
||||
const msg = JSON.parse(data.toString('utf-8'))
|
||||
options.onMessage?.(msg)
|
||||
|
||||
switch (msg.op) {
|
||||
case GatewayOpcodes.Heartbeat: {
|
||||
send(socket, {
|
||||
op: GatewayOpcodes.HeartbeatACK,
|
||||
s: null,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case GatewayOpcodes.Identify: {
|
||||
send(socket, {
|
||||
op: GatewayOpcodes.Dispatch,
|
||||
t: 'READY',
|
||||
s: 1,
|
||||
d: {
|
||||
v: 10,
|
||||
user: {
|
||||
id: '0',
|
||||
username: 'test',
|
||||
discriminator: '0',
|
||||
global_name: 'test',
|
||||
avatar: null,
|
||||
bot: true,
|
||||
verified: true,
|
||||
email: null,
|
||||
flags: 0,
|
||||
},
|
||||
shard: [0, 1],
|
||||
session_id: '0',
|
||||
resume_gateway_url: `ws://localhost:${address.port}`,
|
||||
guilds: [],
|
||||
application: { id: '0', flags: 0 },
|
||||
},
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
port: address.port,
|
||||
close: () => server.close(),
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateWsServerOptions {
|
||||
onOpen?: () => any
|
||||
onMessage?: (message: DiscordGatewayPayload) => any
|
||||
}
|
||||
|
||||
function send(ws: WebSocket, payload: object) {
|
||||
ws.send(JSON.stringify(payload))
|
||||
}
|
||||
@@ -159,7 +159,6 @@ __metadata:
|
||||
ts-node: "npm:^10.9.2"
|
||||
tsconfig: "npm:*"
|
||||
typescript: "npm:^5.7.2"
|
||||
uWebSockets.js: "uNetworking/uWebSockets.js#54e5db370db746ed1c85021bdf215578a2164901"
|
||||
ws: "npm:^8.18.0"
|
||||
dependenciesMeta:
|
||||
fzstd:
|
||||
@@ -4326,13 +4325,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uWebSockets.js@uNetworking/uWebSockets.js#54e5db370db746ed1c85021bdf215578a2164901":
|
||||
version: 20.47.0
|
||||
resolution: "uWebSockets.js@https://github.com/uNetworking/uWebSockets.js.git#commit=54e5db370db746ed1c85021bdf215578a2164901"
|
||||
checksum: c6ed6796b1ed86118e7ea32fa5e486a9aa8d2cc5e3757eb20e8a3120779b47130c318c5f84bbdfbb744c816af272ea7c0220e163ff1c7b85bcd848b04d521408
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "uc.micro@npm:2.1.0"
|
||||
|
||||
Reference in New Issue
Block a user