From c7bc7249c3beacf0094aa5d3535cd21118ec4d1b Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Sun, 15 Feb 2026 19:25:16 -0800 Subject: [PATCH] test (security/line): cover missing webhook auth startup paths --- extensions/line/src/channel.startup.test.ts | 103 ++++++++++++++++++++ src/line/monitor.fail-closed.test.ts | 28 ++++++ 2 files changed, 131 insertions(+) create mode 100644 extensions/line/src/channel.startup.test.ts create mode 100644 src/line/monitor.fail-closed.test.ts diff --git a/extensions/line/src/channel.startup.test.ts b/extensions/line/src/channel.startup.test.ts new file mode 100644 index 0000000000..5102048008 --- /dev/null +++ b/extensions/line/src/channel.startup.test.ts @@ -0,0 +1,103 @@ +import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk"; +import { describe, expect, it, vi } from "vitest"; +import { linePlugin } from "./channel.js"; +import { setLineRuntime } from "./runtime.js"; + +function createRuntime() { + const probeLineBot = vi.fn(async () => ({ ok: false })); + const monitorLineProvider = vi.fn(async () => ({ + account: { accountId: "default" }, + handleWebhook: async () => {}, + stop: () => {}, + })); + + const runtime = { + channel: { + line: { + probeLineBot, + monitorLineProvider, + }, + }, + logging: { + shouldLogVerbose: () => false, + }, + } as unknown as PluginRuntime; + + return { runtime, probeLineBot, monitorLineProvider }; +} + +function createStartAccountCtx(params: { + token: string; + secret: string; + runtime: unknown; +}) { + return { + account: { + accountId: "default", + channelAccessToken: params.token, + channelSecret: params.secret, + config: {}, + }, + cfg: {} as OpenClawConfig, + runtime: params.runtime, + abortSignal: undefined, + log: { info: vi.fn(), debug: vi.fn() }, + }; +} + +describe("linePlugin gateway.startAccount", () => { + it("fails startup when channel secret is missing", async () => { + const { runtime, monitorLineProvider } = createRuntime(); + setLineRuntime(runtime); + + await expect( + linePlugin.gateway.startAccount( + createStartAccountCtx({ + token: "token", + secret: " ", + runtime: {}, + }) as never, + ), + ).rejects.toThrow('LINE webhook mode requires a non-empty channel secret for account "default".'); + expect(monitorLineProvider).not.toHaveBeenCalled(); + }); + + it("fails startup when channel access token is missing", async () => { + const { runtime, monitorLineProvider } = createRuntime(); + setLineRuntime(runtime); + + await expect( + linePlugin.gateway.startAccount( + createStartAccountCtx({ + token: " ", + secret: "secret", + runtime: {}, + }) as never, + ), + ).rejects.toThrow( + 'LINE webhook mode requires a non-empty channel access token for account "default".', + ); + expect(monitorLineProvider).not.toHaveBeenCalled(); + }); + + it("starts provider when token and secret are present", async () => { + const { runtime, monitorLineProvider } = createRuntime(); + setLineRuntime(runtime); + + await linePlugin.gateway.startAccount( + createStartAccountCtx({ + token: "token", + secret: "secret", + runtime: {}, + }) as never, + ); + + expect(monitorLineProvider).toHaveBeenCalledWith( + expect.objectContaining({ + channelAccessToken: "token", + channelSecret: "secret", + accountId: "default", + }), + ); + }); +}); diff --git a/src/line/monitor.fail-closed.test.ts b/src/line/monitor.fail-closed.test.ts new file mode 100644 index 0000000000..ef459a9e5f --- /dev/null +++ b/src/line/monitor.fail-closed.test.ts @@ -0,0 +1,28 @@ +import type { OpenClawConfig } from "../config/config.js"; +import type { RuntimeEnv } from "../runtime.js"; +import { describe, expect, it } from "vitest"; +import { monitorLineProvider } from "./monitor.js"; + +describe("monitorLineProvider fail-closed webhook auth", () => { + it("rejects startup when channel secret is missing", async () => { + await expect( + monitorLineProvider({ + channelAccessToken: "token", + channelSecret: " ", + config: {} as OpenClawConfig, + runtime: {} as RuntimeEnv, + }), + ).rejects.toThrow("LINE webhook mode requires a non-empty channel secret."); + }); + + it("rejects startup when channel access token is missing", async () => { + await expect( + monitorLineProvider({ + channelAccessToken: " ", + channelSecret: "secret", + config: {} as OpenClawConfig, + runtime: {} as RuntimeEnv, + }), + ).rejects.toThrow("LINE webhook mode requires a non-empty channel access token."); + }); +});