diff --git a/src/gateway/server/ws-connection/auth-context.test.ts b/src/gateway/server/ws-connection/auth-context.test.ts index d743f3bb3..a598a963f 100644 --- a/src/gateway/server/ws-connection/auth-context.test.ts +++ b/src/gateway/server/ws-connection/auth-context.test.ts @@ -34,6 +34,24 @@ function createBaseState(overrides?: Partial): ConnectAuthStat }; } +async function resolveDeviceTokenDecision(params: { + verifyDeviceToken: ReturnType; + stateOverrides?: Partial; + rateLimiter?: AuthRateLimiter; + clientIp?: string; +}) { + return await resolveConnectAuthDecision({ + state: createBaseState(params.stateOverrides), + hasDeviceIdentity: true, + deviceId: "dev-1", + role: "operator", + scopes: ["operator.read"], + verifyDeviceToken: params.verifyDeviceToken, + ...(params.rateLimiter ? { rateLimiter: params.rateLimiter } : {}), + ...(params.clientIp ? { clientIp: params.clientIp } : {}), + }); +} + describe("resolveConnectAuthDecision", () => { it("keeps shared-secret mismatch when fallback device-token check fails", async () => { const verifyDeviceToken = vi.fn(async () => ({ ok: false })); @@ -69,15 +87,10 @@ describe("resolveConnectAuthDecision", () => { it("accepts valid device tokens and marks auth method as device-token", async () => { const rateLimiter = createRateLimiter(); const verifyDeviceToken = vi.fn(async () => ({ ok: true })); - const decision = await resolveConnectAuthDecision({ - state: createBaseState(), - hasDeviceIdentity: true, - deviceId: "dev-1", - role: "operator", - scopes: ["operator.read"], + const decision = await resolveDeviceTokenDecision({ + verifyDeviceToken, rateLimiter: rateLimiter.limiter, clientIp: "203.0.113.20", - verifyDeviceToken, }); expect(decision.authOk).toBe(true); expect(decision.authMethod).toBe("device-token"); @@ -88,15 +101,10 @@ describe("resolveConnectAuthDecision", () => { it("returns rate-limited auth result without verifying device token", async () => { const rateLimiter = createRateLimiter({ allowed: false, retryAfterMs: 60_000 }); const verifyDeviceToken = vi.fn(async () => ({ ok: true })); - const decision = await resolveConnectAuthDecision({ - state: createBaseState(), - hasDeviceIdentity: true, - deviceId: "dev-1", - role: "operator", - scopes: ["operator.read"], + const decision = await resolveDeviceTokenDecision({ + verifyDeviceToken, rateLimiter: rateLimiter.limiter, clientIp: "203.0.113.20", - verifyDeviceToken, }); expect(decision.authOk).toBe(false); expect(decision.authResult.reason).toBe("rate_limited"); diff --git a/src/infra/outbound/message-action-runner.test.ts b/src/infra/outbound/message-action-runner.test.ts index e3f9a798a..516176941 100644 --- a/src/infra/outbound/message-action-runner.test.ts +++ b/src/infra/outbound/message-action-runner.test.ts @@ -78,6 +78,32 @@ const runDrySend = (params: { action: "send", }); +async function expectSandboxMediaRewrite(params: { + sandboxDir: string; + media?: string; + message?: string; + expectedRelativePath: string; +}) { + const result = await runDrySend({ + cfg: slackConfig, + actionParams: { + channel: "slack", + target: "#C12345678", + ...(params.media ? { media: params.media } : {}), + ...(params.message ? { message: params.message } : {}), + }, + sandboxRoot: params.sandboxDir, + }); + + expect(result.kind).toBe("send"); + if (result.kind !== "send") { + throw new Error("expected send result"); + } + expect(result.sendResult?.mediaUrl).toBe( + path.join(params.sandboxDir, params.expectedRelativePath), + ); +} + function createAlwaysConfiguredPluginConfig(account: Record = { enabled: true }) { return { listAccountIds: () => ["default"], @@ -566,63 +592,33 @@ describe("runMessageAction sandboxed media validation", () => { it("rewrites sandbox-relative media paths", async () => { await withSandbox(async (sandboxDir) => { - const result = await runDrySend({ - cfg: slackConfig, - actionParams: { - channel: "slack", - target: "#C12345678", - media: "./data/file.txt", - message: "", - }, - sandboxRoot: sandboxDir, + await expectSandboxMediaRewrite({ + sandboxDir, + media: "./data/file.txt", + message: "", + expectedRelativePath: path.join("data", "file.txt"), }); - - expect(result.kind).toBe("send"); - if (result.kind !== "send") { - throw new Error("expected send result"); - } - expect(result.sendResult?.mediaUrl).toBe(path.join(sandboxDir, "data", "file.txt")); }); }); it("rewrites /workspace media paths to host sandbox root", async () => { await withSandbox(async (sandboxDir) => { - const result = await runDrySend({ - cfg: slackConfig, - actionParams: { - channel: "slack", - target: "#C12345678", - media: "/workspace/data/file.txt", - message: "", - }, - sandboxRoot: sandboxDir, + await expectSandboxMediaRewrite({ + sandboxDir, + media: "/workspace/data/file.txt", + message: "", + expectedRelativePath: path.join("data", "file.txt"), }); - - expect(result.kind).toBe("send"); - if (result.kind !== "send") { - throw new Error("expected send result"); - } - expect(result.sendResult?.mediaUrl).toBe(path.join(sandboxDir, "data", "file.txt")); }); }); it("rewrites MEDIA directives under sandbox", async () => { await withSandbox(async (sandboxDir) => { - const result = await runDrySend({ - cfg: slackConfig, - actionParams: { - channel: "slack", - target: "#C12345678", - message: "Hello\nMEDIA: ./data/note.ogg", - }, - sandboxRoot: sandboxDir, + await expectSandboxMediaRewrite({ + sandboxDir, + message: "Hello\nMEDIA: ./data/note.ogg", + expectedRelativePath: path.join("data", "note.ogg"), }); - - expect(result.kind).toBe("send"); - if (result.kind !== "send") { - throw new Error("expected send result"); - } - expect(result.sendResult?.mediaUrl).toBe(path.join(sandboxDir, "data", "note.ogg")); }); });