Spaces:
Paused
Paused
| import crypto from "node:crypto"; | |
| import { describe, expect, it, vi } from "vitest"; | |
| import { createLineWebhookMiddleware } from "./webhook.js"; | |
| const sign = (body: string, secret: string) => | |
| crypto.createHmac("SHA256", secret).update(body).digest("base64"); | |
| const createRes = () => { | |
| const res = { | |
| status: vi.fn(), | |
| json: vi.fn(), | |
| headersSent: false, | |
| } as any; | |
| res.status.mockReturnValue(res); | |
| res.json.mockReturnValue(res); | |
| return res; | |
| }; | |
| describe("createLineWebhookMiddleware", () => { | |
| it("parses JSON from raw string body", async () => { | |
| const onEvents = vi.fn(async () => {}); | |
| const secret = "secret"; | |
| const rawBody = JSON.stringify({ events: [{ type: "message" }] }); | |
| const middleware = createLineWebhookMiddleware({ channelSecret: secret, onEvents }); | |
| const req = { | |
| headers: { "x-line-signature": sign(rawBody, secret) }, | |
| body: rawBody, | |
| } as any; | |
| const res = createRes(); | |
| await middleware(req, res, {} as any); | |
| expect(res.status).toHaveBeenCalledWith(200); | |
| expect(onEvents).toHaveBeenCalledWith(expect.objectContaining({ events: expect.any(Array) })); | |
| }); | |
| it("parses JSON from raw buffer body", async () => { | |
| const onEvents = vi.fn(async () => {}); | |
| const secret = "secret"; | |
| const rawBody = JSON.stringify({ events: [{ type: "follow" }] }); | |
| const middleware = createLineWebhookMiddleware({ channelSecret: secret, onEvents }); | |
| const req = { | |
| headers: { "x-line-signature": sign(rawBody, secret) }, | |
| body: Buffer.from(rawBody, "utf-8"), | |
| } as any; | |
| const res = createRes(); | |
| await middleware(req, res, {} as any); | |
| expect(res.status).toHaveBeenCalledWith(200); | |
| expect(onEvents).toHaveBeenCalledWith(expect.objectContaining({ events: expect.any(Array) })); | |
| }); | |
| it("rejects invalid JSON payloads", async () => { | |
| const onEvents = vi.fn(async () => {}); | |
| const secret = "secret"; | |
| const rawBody = "not json"; | |
| const middleware = createLineWebhookMiddleware({ channelSecret: secret, onEvents }); | |
| const req = { | |
| headers: { "x-line-signature": sign(rawBody, secret) }, | |
| body: rawBody, | |
| } as any; | |
| const res = createRes(); | |
| await middleware(req, res, {} as any); | |
| expect(res.status).toHaveBeenCalledWith(400); | |
| expect(onEvents).not.toHaveBeenCalled(); | |
| }); | |
| it("rejects webhooks with invalid signatures", async () => { | |
| const onEvents = vi.fn(async () => {}); | |
| const secret = "secret"; | |
| const rawBody = JSON.stringify({ events: [{ type: "message" }] }); | |
| const middleware = createLineWebhookMiddleware({ channelSecret: secret, onEvents }); | |
| const req = { | |
| headers: { "x-line-signature": "invalid-signature" }, | |
| body: rawBody, | |
| } as any; | |
| const res = createRes(); | |
| await middleware(req, res, {} as any); | |
| expect(res.status).toHaveBeenCalledWith(401); | |
| expect(onEvents).not.toHaveBeenCalled(); | |
| }); | |
| it("rejects webhooks with signatures computed using wrong secret", async () => { | |
| const onEvents = vi.fn(async () => {}); | |
| const correctSecret = "correct-secret"; | |
| const wrongSecret = "wrong-secret"; | |
| const rawBody = JSON.stringify({ events: [{ type: "message" }] }); | |
| const middleware = createLineWebhookMiddleware({ channelSecret: correctSecret, onEvents }); | |
| const req = { | |
| headers: { "x-line-signature": sign(rawBody, wrongSecret) }, | |
| body: rawBody, | |
| } as any; | |
| const res = createRes(); | |
| await middleware(req, res, {} as any); | |
| expect(res.status).toHaveBeenCalledWith(401); | |
| expect(onEvents).not.toHaveBeenCalled(); | |
| }); | |
| }); | |