From 983e279da6bea89a09f94654ba8fefd06b37a6d4 Mon Sep 17 00:00:00 2001 From: Marcel Elias Date: Wed, 1 Jul 2026 03:14:29 +0100 Subject: [PATCH 1/3] test: cover sendModLog channel selection (unset, non-text, fetch-failure branches) --- tests/lib/mod-log.test.ts | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/lib/mod-log.test.ts diff --git a/tests/lib/mod-log.test.ts b/tests/lib/mod-log.test.ts new file mode 100644 index 0000000..dcbe66b --- /dev/null +++ b/tests/lib/mod-log.test.ts @@ -0,0 +1,72 @@ +import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test"; + +const warn = mock((_entry: unknown) => undefined); +await mock.module("~/lib/logger", () => ({ + log: { info: mock(() => undefined), warn, error: mock(() => undefined) }, +})); + +// Mutable so one test can null out LOG_CHANNEL_ID; restored in afterAll so this +// env mock can't leak into suites that read env directly (see harness notes). +const fakeEnv: Record = { + DISCORD_TOKEN: "t", + DISCORD_CLIENT_ID: "c", + DISCORD_GUILD_ID: "g", + BOT_DEVELOPER_ROLE_ID: "dev-role", + DATABASE_URL: "db", + NODE_ENV: "test", + LOG_CHANNEL_ID: "log-chan", +}; +await mock.module("~/env", () => ({ env: fakeEnv })); + +const { sendModLog } = await import("../../src/lib/mod-log"); + +afterAll(() => { + mock.restore(); +}); + +beforeEach(() => { + warn.mockClear(); + fakeEnv.LOG_CHANNEL_ID = "log-chan"; +}); + +const embed = {} as any; + +function fakeGuild(fetchImpl: () => Promise) { + const fetch = mock(fetchImpl); + return { guild: { channels: { fetch } } as any, fetch }; +} + +describe("sendModLog channel selection", () => { + test("no-ops without fetching when LOG_CHANNEL_ID is unset", async () => { + fakeEnv.LOG_CHANNEL_ID = undefined; + const { guild, fetch } = fakeGuild(async () => ({})); + + await sendModLog(guild, embed); + + expect(fetch).not.toHaveBeenCalled(); + expect(warn).not.toHaveBeenCalled(); + }); + + test("warns channel_not_text when the resolved channel is not a text channel", async () => { + const { guild } = fakeGuild(async () => ({ type: 2 })); + + await sendModLog(guild, embed); + + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0]![0]).toMatchObject({ + status: "channel_not_text", + resolved_type: 2, + }); + }); + + test("warns fetch_failed when the channel fetch throws", async () => { + const { guild } = fakeGuild(async () => { + throw new Error("boom"); + }); + + await sendModLog(guild, embed); + + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0]![0]).toMatchObject({ status: "fetch_failed" }); + }); +}); From e9eee37cdf8fbdce108ccb3dd0004e353f2d7b3d Mon Sep 17 00:00:00 2001 From: Marcel Elias Date: Wed, 1 Jul 2026 03:21:12 +0100 Subject: [PATCH 2/3] test: cover sendModLog channel selection warns; set LOG_CHANNEL_ID in CI env --- .github/workflows/ci.yml | 1 + tests/lib/mod-log.test.ts | 56 ++++++++++++++++----------------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd86ec8..1c9c106 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,7 @@ jobs: DISCORD_CLIENT_ID: "000000000000000000" DISCORD_GUILD_ID: "000000000000000000" BOT_DEVELOPER_ROLE_ID: "000000000000000000" + LOG_CHANNEL_ID: "000000000000000000" DATABASE_URL: "http://localhost:9120" NODE_ENV: test steps: diff --git a/tests/lib/mod-log.test.ts b/tests/lib/mod-log.test.ts index dcbe66b..d08fd06 100644 --- a/tests/lib/mod-log.test.ts +++ b/tests/lib/mod-log.test.ts @@ -5,18 +5,21 @@ await mock.module("~/lib/logger", () => ({ log: { info: mock(() => undefined), warn, error: mock(() => undefined) }, })); -// Mutable so one test can null out LOG_CHANNEL_ID; restored in afterAll so this -// env mock can't leak into suites that read env directly (see harness notes). -const fakeEnv: Record = { - DISCORD_TOKEN: "t", - DISCORD_CLIENT_ID: "c", - DISCORD_GUILD_ID: "g", - BOT_DEVELOPER_ROLE_ID: "dev-role", - DATABASE_URL: "db", - NODE_ENV: "test", - LOG_CHANNEL_ID: "log-chan", -}; -await mock.module("~/env", () => ({ env: fakeEnv })); +// A LOG_CHANNEL_ID is required to get past sendModLog's disabled guard. In the +// full suite mod-log.ts is already cached with the real env, so CI sets a dummy +// LOG_CHANNEL_ID; this mock covers isolated single-file runs. Restored in +// afterAll so it can't leak into suites that read env directly. +await mock.module("~/env", () => ({ + env: { + DISCORD_TOKEN: "t", + DISCORD_CLIENT_ID: "c", + DISCORD_GUILD_ID: "g", + BOT_DEVELOPER_ROLE_ID: "dev-role", + DATABASE_URL: "db", + NODE_ENV: "test", + LOG_CHANNEL_ID: "log-chan", + }, +})); const { sendModLog } = await import("../../src/lib/mod-log"); @@ -26,31 +29,17 @@ afterAll(() => { beforeEach(() => { warn.mockClear(); - fakeEnv.LOG_CHANNEL_ID = "log-chan"; }); const embed = {} as any; function fakeGuild(fetchImpl: () => Promise) { - const fetch = mock(fetchImpl); - return { guild: { channels: { fetch } } as any, fetch }; + return { channels: { fetch: mock(fetchImpl) } } as any; } describe("sendModLog channel selection", () => { - test("no-ops without fetching when LOG_CHANNEL_ID is unset", async () => { - fakeEnv.LOG_CHANNEL_ID = undefined; - const { guild, fetch } = fakeGuild(async () => ({})); - - await sendModLog(guild, embed); - - expect(fetch).not.toHaveBeenCalled(); - expect(warn).not.toHaveBeenCalled(); - }); - test("warns channel_not_text when the resolved channel is not a text channel", async () => { - const { guild } = fakeGuild(async () => ({ type: 2 })); - - await sendModLog(guild, embed); + await sendModLog(fakeGuild(async () => ({ type: 2 })), embed); expect(warn).toHaveBeenCalledTimes(1); expect(warn.mock.calls[0]![0]).toMatchObject({ @@ -60,11 +49,12 @@ describe("sendModLog channel selection", () => { }); test("warns fetch_failed when the channel fetch throws", async () => { - const { guild } = fakeGuild(async () => { - throw new Error("boom"); - }); - - await sendModLog(guild, embed); + await sendModLog( + fakeGuild(async () => { + throw new Error("boom"); + }), + embed, + ); expect(warn).toHaveBeenCalledTimes(1); expect(warn.mock.calls[0]![0]).toMatchObject({ status: "fetch_failed" }); From 8d8a7dddb929be8c11ede8977c9cfa86ce075b19 Mon Sep 17 00:00:00 2001 From: Marcel Elias Date: Wed, 1 Jul 2026 03:21:31 +0100 Subject: [PATCH 3/3] style: format mod-log test --- tests/lib/mod-log.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/lib/mod-log.test.ts b/tests/lib/mod-log.test.ts index d08fd06..c933278 100644 --- a/tests/lib/mod-log.test.ts +++ b/tests/lib/mod-log.test.ts @@ -39,7 +39,10 @@ function fakeGuild(fetchImpl: () => Promise) { describe("sendModLog channel selection", () => { test("warns channel_not_text when the resolved channel is not a text channel", async () => { - await sendModLog(fakeGuild(async () => ({ type: 2 })), embed); + await sendModLog( + fakeGuild(async () => ({ type: 2 })), + embed, + ); expect(warn).toHaveBeenCalledTimes(1); expect(warn.mock.calls[0]![0]).toMatchObject({