From f91c21499dada635ab98078df18a5b0407f1325e Mon Sep 17 00:00:00 2001 From: Lars Knol Date: Sun, 30 Jul 2023 16:11:38 +0200 Subject: [PATCH] feat!: implement leveling module (#49) --- .env.example | 1 - apps/barry/README.md | 1 - apps/barry/tests/Application.test.ts | 6 ++--- apps/barry/tests/mocks/application.ts | 27 +++++++++++++++---- apps/barry/tests/mocks/index.ts | 1 + apps/barry/tests/mocks/redis.ts | 10 +++++++ .../barry/tests/modules/general/index.test.ts | 1 - .../leveling/events/messageCreate.test.ts | 6 ++--- .../leveling/events/voiceStateUpdate.test.ts | 27 +++++++++---------- packages/core/src/Client.ts | 2 ++ 10 files changed, 51 insertions(+), 31 deletions(-) create mode 100644 apps/barry/tests/mocks/redis.ts diff --git a/.env.example b/.env.example index 021d165..76ce530 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,5 @@ # Discord application DISCORD_CLIENT_ID= -DISCORD_PUBLIC_KEY= DISCORD_TOKEN= # Database credentials diff --git a/apps/barry/README.md b/apps/barry/README.md index 81bd3b9..b49cfe6 100644 --- a/apps/barry/README.md +++ b/apps/barry/README.md @@ -103,7 +103,6 @@ The following environment variables **must be** set in order to run the applicat | Environment Variable | Description | |----------------------|-------------------------------------------------------------------------------------| | DISCORD_CLIENT_ID | The ID of your Discord application. | -| DISCORD_PUBLIC_KEY | The public key of your Discord application. | | DISCORD_TOKEN | The token of your Discord bot. | | POSTGRES_HOST | The hostname or IP address of the database. (default: `localhost`) | | POSTGRES_PORT | The port of the database. (default: `5432`) | diff --git a/apps/barry/tests/Application.test.ts b/apps/barry/tests/Application.test.ts index 18b053d..fbdb03a 100644 --- a/apps/barry/tests/Application.test.ts +++ b/apps/barry/tests/Application.test.ts @@ -4,8 +4,6 @@ import { createMockApplication, mockAppOptions } from "./mocks/application.js"; import { API } from "@discordjs/core"; import { Application } from "../src/Application.js"; import { Client } from "@barry/core"; -import { Logger } from "@barry/logger"; -import { WebSocketManager } from "@discordjs/ws"; describe("Application", () => { let app: Application; @@ -23,8 +21,8 @@ describe("Application", () => { it("should initialize with the provided options", () => { expect(app.api).toBeInstanceOf(API); expect(app.applicationID).toBe(mockAppOptions.discord.applicationID); - expect(app.gateway).toBeInstanceOf(WebSocketManager); - expect(app.logger).toBeInstanceOf(Logger); + expect(app.gateway).toBeDefined(); + expect(app.logger).toBeDefined(); expect(app.prisma).toBeDefined(); expect(app.redis).toBeDefined(); }); diff --git a/apps/barry/tests/mocks/application.ts b/apps/barry/tests/mocks/application.ts index 21891b3..ada12d0 100644 --- a/apps/barry/tests/mocks/application.ts +++ b/apps/barry/tests/mocks/application.ts @@ -1,13 +1,24 @@ -import type { Redis } from "ioredis"; - import { Module, UserCommand } from "@barry/core"; import { Application } from "../../src/Application.js"; import { GatewayIntentBits } from "@discordjs/core"; -import { mockDeep } from "vitest-mock-extended"; import { prisma } from "./prisma.js"; +import { redis } from "./redis.js"; import { vi } from "vitest"; +vi.mock("ioredis", () => ({ + Redis: vi.fn(() => redis) +})); + +vi.mock("@prisma/client", async (importOriginal) => { + const original = await importOriginal(); + + return { + ...original, + PrismaClient: vi.fn(() => prisma) + }; +}); + export class MockCommand extends UserCommand { constructor(module: Module) { super(module, { @@ -52,13 +63,19 @@ export const mockAppOptions = { */ export function createMockApplication(override: Record = {}): Application { const app = new Application({ + logger: { + debug: vi.fn(), + error: vi.fn(), + fatal: vi.fn(), + info: vi.fn(), + trace: vi.fn(), + warn: vi.fn() + }, ...mockAppOptions, ...override }); app.gateway.connect = vi.fn(); - app.redis = mockDeep(); - app.prisma = prisma; return app; } diff --git a/apps/barry/tests/mocks/index.ts b/apps/barry/tests/mocks/index.ts index d29731c..428a65b 100644 --- a/apps/barry/tests/mocks/index.ts +++ b/apps/barry/tests/mocks/index.ts @@ -1,2 +1,3 @@ export * from "./application.js"; export * from "./prisma.js"; +export * from "./redis.js"; diff --git a/apps/barry/tests/mocks/redis.ts b/apps/barry/tests/mocks/redis.ts new file mode 100644 index 0000000..1d447fe --- /dev/null +++ b/apps/barry/tests/mocks/redis.ts @@ -0,0 +1,10 @@ +import type { Redis } from "ioredis"; +import { mockDeep, mockReset } from "vitest-mock-extended"; + +import { beforeEach } from "vitest"; + +beforeEach(() => { + mockReset(redis); +}); + +export const redis = mockDeep(); diff --git a/apps/barry/tests/modules/general/index.test.ts b/apps/barry/tests/modules/general/index.test.ts index c993b96..d547acb 100644 --- a/apps/barry/tests/modules/general/index.test.ts +++ b/apps/barry/tests/modules/general/index.test.ts @@ -15,7 +15,6 @@ describe("GeneralModule", () => { beforeEach(() => { const client = createMockApplication(); - client.logger.error = vi.fn(); Module.prototype.initialize = vi.fn(() => { module.commands = [command]; diff --git a/apps/barry/tests/modules/leveling/events/messageCreate.test.ts b/apps/barry/tests/modules/leveling/events/messageCreate.test.ts index d512ac0..9097e3e 100644 --- a/apps/barry/tests/modules/leveling/events/messageCreate.test.ts +++ b/apps/barry/tests/modules/leveling/events/messageCreate.test.ts @@ -121,7 +121,6 @@ describe("MessageCreate Event", () => { }); it("should ignore 'Cannot send messages to this user' errors", async () => { - const loggerSpy = vi.spyOn(event.client.logger, "error"); const response = { code: 50007, message: "Cannot send messages to this user" @@ -132,18 +131,17 @@ describe("MessageCreate Event", () => { await event.execute(message); - expect(loggerSpy).not.toHaveBeenCalled(); + expect(event.client.logger.error).not.toHaveBeenCalled(); }); it("should handle errors during execution to prevent cooldowns not being set", async () => { - const loggerSpy = vi.spyOn(event.client.logger, "error"); const cooldownSpy = vi.spyOn(event.client.cooldowns, "set"); vi.spyOn(event.module, "checkLevel").mockRejectedValue(new Error("Oh no!")); await event.execute(message); - expect(loggerSpy).toHaveBeenCalledOnce(); + expect(event.client.logger.error).toHaveBeenCalledOnce(); expect(cooldownSpy).toHaveBeenCalledOnce(); }); }); diff --git a/apps/barry/tests/modules/leveling/events/voiceStateUpdate.test.ts b/apps/barry/tests/modules/leveling/events/voiceStateUpdate.test.ts index c3ad876..ae871d0 100644 --- a/apps/barry/tests/modules/leveling/events/voiceStateUpdate.test.ts +++ b/apps/barry/tests/modules/leveling/events/voiceStateUpdate.test.ts @@ -1,11 +1,11 @@ import type { GatewayVoiceState } from "@discordjs/core"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { prisma, redis } from "../../../mocks/index.js"; import { DiscordAPIError } from "@discordjs/rest"; import { createMockApplication } from "../../../mocks/application.js"; import { mockMember } from "@barry/testing"; -import { prisma } from "../../../mocks/index.js"; import LevelingModule from "../../../../src/modules/leveling/index.js"; import VoiceStateUpdateEvent from "../../../../src/modules/leveling/events/voiceStateUpdate.js"; @@ -68,7 +68,7 @@ describe("VoiceStateUpdate Event", () => { await event.execute(state, channelID); expect(incrementSpy).not.toHaveBeenCalled(); - expect(event.client.redis.set).not.toHaveBeenCalled(); + expect(redis.set).not.toHaveBeenCalled(); }); it("should ignore if the user has not left, joined, or moved to a different channel", async () => { @@ -77,17 +77,17 @@ describe("VoiceStateUpdate Event", () => { await event.execute({ ...state, channel_id: channelID }, channelID); expect(incrementSpy).not.toHaveBeenCalled(); - expect(event.client.redis.set).not.toHaveBeenCalled(); + expect(redis.set).not.toHaveBeenCalled(); }); it("should set the voice start time when a user joins a voice channel", async () => { await event.execute({ ...state, channel_id: channelID }); - expect(event.client.redis.set).toHaveBeenCalledOnce(); + expect(redis.set).toHaveBeenCalledOnce(); }); it("should update voice minutes for the user when they leave a voice channel", async () => { - vi.mocked(event.client.redis.get).mockResolvedValue("1690543918340"); + vi.mocked(redis.get).mockResolvedValue("1690543918340"); const incrementSpy = vi.spyOn(event.module.memberActivity, "increment"); await event.execute(state, channelID); @@ -100,7 +100,7 @@ describe("VoiceStateUpdate Event", () => { }); it("should check if the user has leveled up if the previous channel is known", async () => { - vi.mocked(event.client.redis.get).mockResolvedValue("1690543918340"); + vi.mocked(redis.get).mockResolvedValue("1690543918340"); const checkLevelSpy = vi.spyOn(event.module, "checkLevel"); const incrementSpy = vi.spyOn(event.module.memberActivity, "increment"); @@ -111,7 +111,7 @@ describe("VoiceStateUpdate Event", () => { }); it("should not check if the user has leveled up if the previous channel is unknown", async () => { - vi.mocked(event.client.redis.get).mockResolvedValue("1690543918340"); + vi.mocked(redis.get).mockResolvedValue("1690543918340"); const checkLevelSpy = vi.spyOn(event.module, "checkLevel"); const incrementSpy = vi.spyOn(event.module.memberActivity, "increment"); @@ -152,7 +152,7 @@ describe("VoiceStateUpdate Event", () => { }); it("should not update voice minutes if the start time is not cached", async () => { - vi.mocked(event.client.redis.get).mockResolvedValue(null); + vi.mocked(redis.get).mockResolvedValue(null); const incrementSpy = vi.spyOn(event.module.memberActivity, "increment"); await event.execute(state, channelID); @@ -161,7 +161,6 @@ describe("VoiceStateUpdate Event", () => { }); it("should ignore 'Cannot send messages to this user' errors", async () => { - const loggerSpy = vi.spyOn(event.client.logger, "error"); const response = { code: 50007, message: "Cannot send messages to this user" @@ -169,23 +168,21 @@ describe("VoiceStateUpdate Event", () => { const error = new DiscordAPIError(response, 50007, 200, "GET", "", {}); - vi.mocked(event.client.redis.get).mockResolvedValue("1690543918340"); + vi.mocked(redis.get).mockResolvedValue("1690543918340"); vi.spyOn(event.module, "checkLevel").mockRejectedValue(error); await event.execute(state, channelID); - expect(loggerSpy).not.toHaveBeenCalledOnce(); + expect(event.client.logger.error).not.toHaveBeenCalledOnce(); }); it("should handle errors during execution", async () => { - const loggerSpy = vi.spyOn(event.client.logger, "error"); - - vi.mocked(event.client.redis.get).mockResolvedValue("1690543918340"); + vi.mocked(redis.get).mockResolvedValue("1690543918340"); vi.spyOn(event.module, "checkLevel").mockRejectedValue(new Error("Oh no!")); await event.execute(state, channelID); - expect(loggerSpy).toHaveBeenCalledOnce(); + expect(event.client.logger.error).toHaveBeenCalledOnce(); }); }); }); diff --git a/packages/core/src/Client.ts b/packages/core/src/Client.ts index 08a7782..59f39c0 100644 --- a/packages/core/src/Client.ts +++ b/packages/core/src/Client.ts @@ -15,6 +15,8 @@ import { import { type API, type APIInteraction, + type GatewayDispatchPayload, + type GatewayVoiceState, type MappedEvents, type WithIntrinsicProps, type GatewayVoiceState,