e2e test stuff (#2754)

* fix: readme runtime list

* Fix code style issues with ESLint

* node 18

* fix: websocket import type

* fix: body for interaction requests

* fix: perma fix for type error in ci

* fix: followupmessage option type

* fix: e2e tests exit bug

* fix: color console logger

* fix: image file sending

* Fix code style issues with ESLint

* guild and role methods

* Fix code style issues with ESLint

* fix: dont send heartbeat if socket is not open

* fix: remove logs

* fox: remove more logs

* fix some bugs in role tests

* Switch to after hook style

* hoti's speed snaker

* auto convert objects for discord

* Fix code style issues with ESLint

* fix: remove dup imports

* fix: i hate linters

* speeder

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
This commit is contained in:
Skillz4Killz
2023-02-01 14:20:49 -06:00
committed by GitHub
parent e05a65c4de
commit 9d5b8df60d
8 changed files with 145 additions and 124 deletions

View File

@@ -496,6 +496,8 @@ export class Shard {
// Reference: https://discord.com/developers/docs/topics/gateway#heartbeating
const jitter = Math.ceil(this.heart.interval * (Math.random() || 0.5))
this.heart.timeoutId = setTimeout(() => {
if (!this.isOpen()) return;
// Using a direct socket.send call here because heartbeat requests are reserved by us.
this.socket?.send(
JSON.stringify({
@@ -509,6 +511,7 @@ export class Shard {
// After the random heartbeat jitter we can start a normal interval.
this.heart.intervalId = setInterval(async () => {
if (!this.isOpen()) return;
// gateway.debug("GW DEBUG", `Running setInterval in heartbeat file. Shard: ${shardId}`);
// gateway.debug("GW HEARTBEATING", { shardId, shard: currentShard });

View File

@@ -1,5 +1,5 @@
import { InteractionResponseTypes } from '@discordeno/types'
import { camelize, delay, findFiles, getBotIdFromToken, logger, urlToBase64 } from '@discordeno/utils'
import { camelize, camelToSnakeCase, delay, findFiles, getBotIdFromToken, logger, urlToBase64 } from '@discordeno/utils'
import { createInvalidRequestBucket } from './invalidBucket.js'
import { Queue } from './queue.js'
@@ -12,8 +12,10 @@ import type {
CreateAutoModerationRuleOptions,
CreateChannelInvite,
CreateForumPostWithMessage,
CreateGuild,
CreateGuildChannel,
CreateGuildEmoji,
CreateGuildRole,
CreateMessageOptions,
CreateScheduledEvent,
CreateStageInstance,
@@ -30,6 +32,7 @@ import type {
DiscordFollowAnnouncementChannel,
DiscordFollowedChannel,
DiscordGetGatewayBot,
DiscordGuild,
DiscordIntegration,
DiscordInvite,
DiscordInviteMetadata,
@@ -38,6 +41,7 @@ import type {
DiscordMember,
DiscordMemberWithUser,
DiscordMessage,
DiscordRole,
DiscordScheduledEvent,
DiscordStageInstance,
DiscordStickerPack,
@@ -47,6 +51,7 @@ import type {
DiscordWebhook,
EditAutoModerationRuleOptions,
EditChannelPermissionOverridesOptions,
EditGuildRole,
EditMessage,
EditScheduledEvent,
EditStageInstanceOptions,
@@ -64,18 +69,13 @@ import type {
ModifyChannel,
ModifyGuildChannelPositions,
ModifyGuildEmoji,
ModifyRolePositions,
ModifyWebhook,
SearchMembers,
StartThreadWithMessage,
StartThreadWithoutMessage,
WithReason,
CreateGuild,
CreateGuildRole,
DiscordGuild,
DiscordRole,
EditGuildRole,
ModifyRolePositions} from '@discordeno/types'
} from '@discordeno/types'
import type { InvalidRequestBucket } from './invalidBucket.js'
// TODO: make dynamic based on package.json file
@@ -536,6 +536,33 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
return false
},
changeToDiscordFormat(obj: any): any {
if (obj === null) return null
if (typeof obj === 'object') {
if (Array.isArray(obj)) {
return obj.map((item) => rest.changeToDiscordFormat(item))
}
const newObj: any = {}
for (const key of Object.keys(obj)) {
if (key === 'permissions') {
newObj.permissions = '1234567890'
continue
}
newObj[camelToSnakeCase(key)] = rest.changeToDiscordFormat(obj[key])
}
return newObj
}
if (typeof obj === 'bigint') return obj.toString()
return obj
},
createRequest(options) {
const headers: Record<string, string> = {
'user-agent': `DiscordBot (https://github.com/discordeno/discordeno, v${version})`,
@@ -561,32 +588,36 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
options.body.reason = undefined
}
if (options.body?.file) {
const files = findFiles(options.body.file)
const form = new FormData()
if (options.body) {
const { file } = options.body
if (file) {
const files = findFiles(file)
const form = new FormData()
// WHEN CREATING A STICKER, DISCORD WANTS FORM DATA ONLY
if (options.url?.endsWith('/stickers') && options.method === 'POST') {
form.append('file', files[0].blob, files[0].name)
form.append('name', options.body.name as string)
form.append('description', options.body.description as string)
form.append('tags', options.body.tags as string)
} else {
for (let i = 0; i < files.length; i++) {
form.append(`file${i}`, files[i].blob, files[i].name)
// WHEN CREATING A STICKER, DISCORD WANTS FORM DATA ONLY
if (options.url?.endsWith('/stickers') && options.method === 'POST') {
form.append('file', files[0].blob, files[0].name)
form.append('name', options.body.name as string)
form.append('description', options.body.description as string)
form.append('tags', options.body.tags as string)
} else {
for (let i = 0; i < files.length; i++) {
form.append(`file${i}`, files[i].blob, files[i].name)
}
if (file) options.body.file = undefined
form.append('payload_json', JSON.stringify(rest.changeToDiscordFormat(options.body)))
}
form.append('payload_json', JSON.stringify({ ...options.body, file: undefined }))
options.body.file = form
} else if (options.body && !['GET', 'DELETE'].includes(options.method)) {
headers['Content-Type'] = 'application/json'
}
options.body.file = form
} else if (options.body && !['GET', 'DELETE'].includes(options.method)) {
headers['Content-Type'] = 'application/json'
}
return {
headers,
body: (options.body?.file ?? JSON.stringify(options.body)) as FormData | string,
body: (options.body?.file ?? JSON.stringify(rest.changeToDiscordFormat(options.body))) as FormData | string,
method: options.method,
}
},
@@ -698,11 +729,9 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async sendRequest(options) {
console.log('in send request')
const url = options.url.startsWith('https://') ? options.url : `${rest.baseUrl}/v${rest.version}${options.url}`
const payload = rest.createRequest({ method: options.method, url: options.url, body: options.body, ...options.options })
console.log(`sending request to ${url}`, 'with payload:', { ...payload, headers: { ...payload.headers, authorization: 'Bot tokenhere' } })
logger.debug(`sending request to ${url}`, 'with payload:', { ...payload, headers: { ...payload.headers, authorization: 'Bot tokenhere' } })
const response = await fetch(url, payload)
logger.debug(`request fetched from ${url} with status ${response.status} & ${response.statusText}`)
@@ -736,6 +765,8 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
return await options.retryRequest?.(options)
}
return options.reject(await response.json())
}
// Discord sometimes sends no response
@@ -1407,7 +1438,6 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async createGuild(options) {
console.log('in create guild')
return await rest.post<DiscordGuild>(rest.routes.guilds.all(), options)
},
@@ -2170,6 +2200,8 @@ export interface RestManager {
}
/** Check the rate limits for a url or a bucket. */
checkRateLimits: (url: string) => number | false
/** Reshapes and modifies the obj as needed to make it ready for discords api. */
changeToDiscordFormat: (obj: any) => any
/** Creates the request body and headers that are necessary to send a request. Will handle different types of methods and everything necessary for discord. */
createRequest: (options: CreateRequestBodyOptions) => RequestBody
/** This will create a infinite loop running in 1 seconds using tail recursion to keep rate limits clean. When a rate limit resets, this will remove it so the queue can proceed. */

View File

@@ -1,6 +1,19 @@
import { expect } from 'chai'
import { describe, it } from 'mocha'
import { rest } from './utils.js'
import { e2ecache, rest } from './utils.js'
before(async () => {
if (!e2ecache.guild) {
e2ecache.guild = await rest.createGuild({
name: 'Discordeno-test',
})
}
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id) await rest.deleteGuild(e2ecache.guild.id)
})
describe('[rest] Message related tests', () => {
describe('Send a message', () => {

View File

@@ -4,13 +4,18 @@ import { expect } from 'chai'
import { afterEach, before, beforeEach, describe, it } from 'mocha'
import { e2ecache, rest } from './utils.js'
// before(async () => {
// if (!e2ecache.guild) {
// e2ecache.guild = await rest.createGuild({
// name: 'Discordeno-test'
// })
// }
// })
before(async () => {
if (!e2ecache.guild) {
e2ecache.guild = await rest.createGuild({
name: 'Discordeno-test',
})
}
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id) await rest.deleteGuild(e2ecache.guild.id)
})
describe('[role] Role tests', async () => {
// Create a role with a reason
@@ -18,9 +23,9 @@ describe('[role] Role tests', async () => {
const role = await rest.createRole(
e2ecache.guild?.id,
{
name: `test role ${Date.now()}`
name: `test role ${Date.now()}`,
},
'test reason'
'test reason',
)
expect(role.id).to.exist
@@ -31,7 +36,7 @@ describe('[role] Role tests', async () => {
// Create a role without a reason
it('Create a role without a reason', async () => {
const role = await rest.createRole(e2ecache.guild.id, {
name: `test role ${Date.now()}`
name: `test role ${Date.now()}`,
})
expect(role.id).to.exist
@@ -42,11 +47,11 @@ describe('[role] Role tests', async () => {
// Delete a role
it('Delete a role', async () => {
const role = await rest.createRole(e2ecache.guild.id, {
name: `test role ${Date.now()}`
name: `test role ${Date.now()}`,
})
await rest.deleteRole(e2ecache.guild.id, role.id)
const deletedRoles = await rest.getRoles(e2ecache.guild.id)
expect(deletedRoles.some(r => r.id === role.id)).to.equal(false)
expect(deletedRoles.some((r) => r.id === role.id)).to.equal(false)
})
// Edit a role
@@ -55,7 +60,7 @@ describe('[role] Role tests', async () => {
beforeEach(async () => {
role = await rest.createRole(e2ecache.guild.id, {
name: `test role ${Date.now()}`
name: `test role ${Date.now()}`,
})
})
@@ -66,7 +71,7 @@ describe('[role] Role tests', async () => {
// Edit the roles name
it('Edit the roles name', async () => {
const edited = await rest.editRole(e2ecache.guild.id, role.id, {
name: 'test role 4'
name: 'test role 4',
})
expect(edited.name).to.equal('test role 4')
})
@@ -74,7 +79,7 @@ describe('[role] Role tests', async () => {
// Edit the roles color
it('Edit the roles color', async () => {
const edited = await rest.editRole(e2ecache.guild.id, role.id, {
color: 0x0000ff
color: 0x0000ff,
})
expect(edited.color).to.equal(0x0000ff)
})
@@ -121,11 +126,9 @@ describe('[role] Role tests', async () => {
// Edit the roles permissions
it('Edit the roles permissions', async () => {
const edited = await rest.editRole(e2ecache.guild.id, role.id, {
permissions: ['SEND_MESSAGES', 'VIEW_CHANNEL']
permissions: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
})
expect(edited.permissions.toString()).to.equal(
calculateBits(['SEND_MESSAGES', 'VIEW_CHANNEL'])
)
expect(edited.permissions.toString()).to.equal(calculateBits(['SEND_MESSAGES', 'VIEW_CHANNEL']))
})
})
@@ -134,13 +137,9 @@ describe('[role] Role tests', async () => {
beforeEach(async () => {
role = await rest.createRole(e2ecache.guild.id, {
name: `test role ${Date.now()}`
name: `test role ${Date.now()}`,
})
await rest.addRole(
e2ecache.guild.id,
130136895395987456n,
role.id
)
await rest.addRole(e2ecache.guild.id, rest.applicationId, role.id)
})
afterEach(async () => {
@@ -148,29 +147,15 @@ describe('[role] Role tests', async () => {
})
it('without a reason', async () => {
await rest.removeRole(
e2ecache.guild.id,
130136895395987456n,
role.id
)
const member = await rest.getMember(
e2ecache.guild.id,
130136895395987456n
)
await rest.removeRole(e2ecache.guild.id, rest.applicationId, role.id)
const member = await rest.getMember(e2ecache.guild.id, rest.applicationId)
// console.log('member', member.errors.userId.Errors)
expect(member?.roles.includes(role.id)).to.equal(false)
})
it('with a reason', async () => {
await rest.removeRole(
e2ecache.guild.id,
130136895395987456n,
role.id,
'test reason'
)
const member = await rest.getMember(
e2ecache.guild.id,
130136895395987456n
)
await rest.removeRole(e2ecache.guild.id, rest.applicationId, role.id, 'test reason')
const member = await rest.getMember(e2ecache.guild.id, rest.applicationId)
expect(member?.roles.includes(role.id)).to.equal(false)
})
})
@@ -180,45 +165,26 @@ describe('[role] Role tests', async () => {
beforeEach(async () => {
role = await rest.createRole(e2ecache.guild.id, {
name: `test role ${Date.now()}`
name: `test role ${Date.now()}`,
})
})
afterEach(async () => {
await rest.removeRole(
e2ecache.guild.id,
130136895395987456n,
role.id
)
await rest.removeRole(e2ecache.guild.id, rest.applicationId, role.id)
await rest.deleteRole(e2ecache.guild.id, role.id)
})
it('Without a reason.', async () => {
// Assign the role to the user
await rest.addRole(
e2ecache.guild.id,
130136895395987456n,
role.id
)
const member = await rest.getMember(
e2ecache.guild.id,
130136895395987456n
)
await rest.addRole(e2ecache.guild.id, rest.applicationId, role.id)
const member = await rest.getMember(e2ecache.guild.id, rest.applicationId)
expect(member?.roles.includes(role.id)).to.equal(true)
})
// Add the role to the user with a reason
it('With a reason', async () => {
await rest.addRole(
e2ecache.guild.id,
130136895395987456n,
role.id,
'test reason'
)
const member = await rest.getMember(
e2ecache.guild.id,
130136895395987456n
)
await rest.addRole(e2ecache.guild.id, rest.applicationId, role.id, 'test reason')
const member = await rest.getMember(e2ecache.guild.id, rest.applicationId)
expect(member?.roles.includes(role.id)).to.equal(true)
})
})

View File

@@ -1,9 +1,22 @@
import chai, { expect } from 'chai'
import chaiAsPromised from 'chai-as-promised'
import { describe, it } from 'mocha'
import { rest } from './utils.js'
import { e2ecache, rest } from './utils.js'
chai.use(chaiAsPromised)
before(async () => {
if (!e2ecache.guild) {
e2ecache.guild = await rest.createGuild({
name: 'Discordeno-test',
})
}
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id) await rest.deleteGuild(e2ecache.guild.id)
})
describe('[rest] User related tests', () => {
describe('Get a user from the api', () => {
it('With a valid user id', async () => {

View File

@@ -1,18 +1,16 @@
import { logger, LogLevels } from '@discordeno/utils'
import { LogDepth, logger, LogLevels } from '@discordeno/utils'
import { createRestManager } from '../../src/manager.js'
import { token } from './constants.js'
// For debugging purposes
logger.setLevel(LogLevels.Debug)
// logger.setLevel(LogLevels.Debug)
// logger.setDepth(LogDepth.Full)
export const rest = createRestManager({
token,
})
rest.deleteQueueDelay = 10000
console.log('CREATING GUILD')
export const e2ecache = {
guild: await rest.createGuild({ name: 'ddenotester' }),
}
console.log('CACHED check', e2ecache.guild)

View File

@@ -1,19 +0,0 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import { describe, it } from 'mocha'
import { e2ecache, rest } from './utils.js'
chai.use(chaiAsPromised)
// The xyz.spec.ts file name will make this test run last as tests are ran in alphabetical file name order.
describe('[rest] Cleanup tests', () => {
describe('Remove the timers', () => {
it('In the invalid bucket', async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
})
})
it('Delete the created guild', async () => {
if (e2ecache.guild.id) await rest.deleteGuild(e2ecache.guild.id);
})
})

View File

@@ -32,3 +32,18 @@ export function snakeToCamelCase(str: string): string {
return result
}
export function camelToSnakeCase(str: string): string {
let result = "";
for (let i = 0, len = str.length; i < len; ++i) {
if (str[i] >= "A" && str[i] <= "Z") {
result += `_${str[i].toLowerCase()}`;
continue;
}
result += str[i];
}
return result;
}