generated from nhcarrigan/template
6bf1ac5e7d
## Summary - Grants the Elysian Discord role to players on login/registration and persists an `inGuild` flag on the Player record - Connects to the Discord Gateway via WebSocket to keep `inGuild` in sync as players join or leave the server - Shows a dismissible "Join our community" modal to players who are not yet in the guild - Hardens `inGuild` exposure through the load endpoint and game context - Moves all non-secret Discord IDs (guild, role, client, redirect URI) out of env vars and into hardcoded constants; removes them from `prod.env` ## Test plan - [ ] Lint, build, and test pipeline passes (100% coverage maintained) - [ ] New player auth grants Elysian role and sets `inGuild: true` - [ ] Existing player auth re-attempts role grant and updates `inGuild` - [ ] Join community modal appears for players not in the guild - [ ] Modal does not reappear within the same browser session after dismissal - [ ] Gateway correctly sets `inGuild: true/false` on member add/remove events ✨ This issue was created with help from Hikari~ 🌸 Reviewed-on: #134 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
106 lines
4.2 KiB
TypeScript
106 lines
4.2 KiB
TypeScript
/* eslint-disable max-lines-per-function -- Test suites naturally have many cases */
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("../../src/db/client.js", () => ({
|
|
prisma: {
|
|
player: { updateMany: vi.fn() },
|
|
},
|
|
}));
|
|
|
|
vi.mock("../../src/services/logger.js", () => ({
|
|
logger: {
|
|
error: vi.fn().mockResolvedValue(undefined),
|
|
log: vi.fn().mockResolvedValue(undefined),
|
|
},
|
|
}));
|
|
|
|
import { prisma } from "../../src/db/client.js";
|
|
|
|
const discordGuildId = "1354624415861833870";
|
|
|
|
describe("gateway service", () => {
|
|
beforeEach(() => {
|
|
vi.resetAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("handleGuildMemberAdd", () => {
|
|
it("sets inGuild to true for the matching guild", async () => {
|
|
vi.mocked(prisma.player.updateMany).mockResolvedValueOnce({ count: 1 });
|
|
const { handleGuildMemberAdd } = await import("../../src/services/gateway.js");
|
|
await handleGuildMemberAdd("user123", discordGuildId);
|
|
expect(prisma.player.updateMany).toHaveBeenCalledWith({
|
|
data: { inGuild: true },
|
|
where: { discordId: "user123" },
|
|
});
|
|
});
|
|
|
|
it("no-ops when guild id does not match the configured guild", async () => {
|
|
const { handleGuildMemberAdd } = await import("../../src/services/gateway.js");
|
|
await handleGuildMemberAdd("user123", "other_guild");
|
|
expect(prisma.player.updateMany).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("logs error when prisma throws an Error", async () => {
|
|
const dbError = new Error("DB failure");
|
|
vi.mocked(prisma.player.updateMany).mockRejectedValueOnce(dbError);
|
|
const { handleGuildMemberAdd } = await import("../../src/services/gateway.js");
|
|
const { logger } = await import("../../src/services/logger.js");
|
|
await handleGuildMemberAdd("user123", discordGuildId);
|
|
expect(logger.error).toHaveBeenCalledWith("gateway_member_add", dbError);
|
|
});
|
|
|
|
it("logs error when prisma throws a non-Error", async () => {
|
|
vi.mocked(prisma.player.updateMany).mockRejectedValueOnce("raw error");
|
|
const { handleGuildMemberAdd } = await import("../../src/services/gateway.js");
|
|
const { logger } = await import("../../src/services/logger.js");
|
|
await handleGuildMemberAdd("user123", discordGuildId);
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
"gateway_member_add",
|
|
new Error("raw error"),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("handleGuildMemberRemove", () => {
|
|
it("sets inGuild to false for the matching guild", async () => {
|
|
vi.mocked(prisma.player.updateMany).mockResolvedValueOnce({ count: 1 });
|
|
const { handleGuildMemberRemove } = await import("../../src/services/gateway.js");
|
|
await handleGuildMemberRemove("user123", discordGuildId);
|
|
expect(prisma.player.updateMany).toHaveBeenCalledWith({
|
|
data: { inGuild: false },
|
|
where: { discordId: "user123" },
|
|
});
|
|
});
|
|
|
|
it("no-ops when guild id does not match the configured guild", async () => {
|
|
const { handleGuildMemberRemove } = await import("../../src/services/gateway.js");
|
|
await handleGuildMemberRemove("user123", "other_guild");
|
|
expect(prisma.player.updateMany).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("logs error when prisma throws an Error", async () => {
|
|
const dbError = new Error("DB failure");
|
|
vi.mocked(prisma.player.updateMany).mockRejectedValueOnce(dbError);
|
|
const { handleGuildMemberRemove } = await import("../../src/services/gateway.js");
|
|
const { logger } = await import("../../src/services/logger.js");
|
|
await handleGuildMemberRemove("user123", discordGuildId);
|
|
expect(logger.error).toHaveBeenCalledWith("gateway_member_remove", dbError);
|
|
});
|
|
|
|
it("logs error when prisma throws a non-Error", async () => {
|
|
vi.mocked(prisma.player.updateMany).mockRejectedValueOnce("raw error");
|
|
const { handleGuildMemberRemove } = await import("../../src/services/gateway.js");
|
|
const { logger } = await import("../../src/services/logger.js");
|
|
await handleGuildMemberRemove("user123", discordGuildId);
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
"gateway_member_remove",
|
|
new Error("raw error"),
|
|
);
|
|
});
|
|
});
|
|
});
|