11import crypto from "node:crypto" ;
22import type { IncomingMessage , ServerResponse } from "node:http" ;
3+ import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env" ;
34import { createMockIncomingRequest } from "openclaw/plugin-sdk/test-env" ;
45import { describe , expect , it , vi } from "vitest" ;
56import { createLineNodeWebhookHandler , readLineWebhookRequestBody } from "./webhook-node.js" ;
@@ -29,6 +30,20 @@ function createRes() {
2930
3031const SECRET = "secret" ;
3132
33+ type RuntimeEnvMock = RuntimeEnv & {
34+ error : ReturnType < typeof vi . fn < ( ...args : unknown [ ] ) => void > > ;
35+ exit : ReturnType < typeof vi . fn < ( code : number ) => void > > ;
36+ log : ReturnType < typeof vi . fn < ( ...args : unknown [ ] ) => void > > ;
37+ } ;
38+
39+ function createRuntimeMock ( ) : RuntimeEnvMock {
40+ return {
41+ error : vi . fn < ( ...args : unknown [ ] ) => void > ( ) ,
42+ exit : vi . fn < ( code : number ) => void > ( ) ,
43+ log : vi . fn < ( ...args : unknown [ ] ) => void > ( ) ,
44+ } ;
45+ }
46+
3247function createMiddlewareRes ( ) {
3348 const res = {
3449 status : vi . fn ( ) ,
@@ -42,7 +57,7 @@ function createMiddlewareRes() {
4257
4358function createPostWebhookTestHarness ( rawBody : string , secret = "secret" ) {
4459 const bot = { handleWebhook : vi . fn ( async ( ) => { } ) } ;
45- const runtime = { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ;
60+ const runtime = createRuntimeMock ( ) ;
4661 const handler = createLineNodeWebhookHandler ( {
4762 channelSecret : secret ,
4863 bot,
@@ -71,11 +86,7 @@ async function invokeWebhook(params: {
7186 headers ?: Record < string , string > ;
7287 onEvents ?: ReturnType < typeof vi . fn > ;
7388 autoSign ?: boolean ;
74- runtime ?: {
75- log : ReturnType < typeof vi . fn > ;
76- error : ReturnType < typeof vi . fn > ;
77- exit : ReturnType < typeof vi . fn > ;
78- } ;
89+ runtime ?: RuntimeEnv ;
7990} ) {
8091 const onEventsMock = params . onEvents ?? vi . fn ( async ( ) => { } ) ;
8192 const middleware = createLineWebhookMiddleware ( {
@@ -138,7 +149,7 @@ async function invokeNodePostContract(params: {
138149 throw params . failWith ;
139150 }
140151 } ) ;
141- const runtime = { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ;
152+ const runtime = createRuntimeMock ( ) ;
142153 const handler = createLineNodeWebhookHandler ( {
143154 channelSecret : SECRET ,
144155 bot : { handleWebhook : dispatched } ,
@@ -167,7 +178,7 @@ async function invokeMiddlewarePostContract(params: {
167178 rawBody : string ;
168179 signed : boolean ;
169180} ) {
170- const runtime = { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ;
181+ const runtime = createRuntimeMock ( ) ;
171182 const onEvents = vi . fn ( async ( ) => {
172183 if ( params . failWith ) {
173184 throw params . failWith ;
@@ -182,6 +193,7 @@ async function invokeMiddlewarePostContract(params: {
182193 } ) ;
183194 return {
184195 body : res . json . mock . calls . at ( - 1 ) ?. [ 0 ] ,
196+ contentType : undefined ,
185197 dispatched,
186198 runtimeError : runtime . error ,
187199 status : res . status . mock . calls . at ( - 1 ) ?. [ 0 ] ,
@@ -288,7 +300,7 @@ describe("LINE webhook shared POST contract", () => {
288300describe ( "createLineNodeWebhookHandler" , ( ) => {
289301 it ( "returns 200 for GET" , async ( ) => {
290302 const bot = { handleWebhook : vi . fn ( async ( ) => { } ) } ;
291- const runtime = { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ;
303+ const runtime = createRuntimeMock ( ) ;
292304 const handler = createLineNodeWebhookHandler ( {
293305 channelSecret : "secret" ,
294306 bot,
@@ -305,7 +317,7 @@ describe("createLineNodeWebhookHandler", () => {
305317
306318 it ( "returns 204 for HEAD" , async ( ) => {
307319 const bot = { handleWebhook : vi . fn ( async ( ) => { } ) } ;
308- const runtime = { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ;
320+ const runtime = createRuntimeMock ( ) ;
309321 const handler = createLineNodeWebhookHandler ( {
310322 channelSecret : "secret" ,
311323 bot,
@@ -333,7 +345,7 @@ describe("createLineNodeWebhookHandler", () => {
333345
334346 it ( "rejects unsigned POST requests before reading the body" , async ( ) => {
335347 const bot = { handleWebhook : vi . fn ( async ( ) => { } ) } ;
336- const runtime = { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ;
348+ const runtime = createRuntimeMock ( ) ;
337349 const readBody = vi . fn ( async ( ) => JSON . stringify ( { events : [ { type : "message" } ] } ) ) ;
338350 const handler = createLineNodeWebhookHandler ( {
339351 channelSecret : "secret" ,
@@ -353,7 +365,7 @@ describe("createLineNodeWebhookHandler", () => {
353365 it ( "uses strict pre-auth limits for signed POST requests" , async ( ) => {
354366 const rawBody = JSON . stringify ( { events : [ { type : "message" } ] } ) ;
355367 const bot = { handleWebhook : vi . fn ( async ( ) => { } ) } ;
356- const runtime = { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ;
368+ const runtime = createRuntimeMock ( ) ;
357369 const readBody = vi . fn ( async ( _req : IncomingMessage , maxBytes : number , timeoutMs ?: number ) => {
358370 expect ( maxBytes ) . toBe ( 64 * 1024 ) ;
359371 expect ( timeoutMs ) . toBe ( 5_000 ) ;
@@ -414,7 +426,7 @@ describe("createLineNodeWebhookHandler", () => {
414426 ) ,
415427 } ;
416428 const onRequestAuthenticated = vi . fn ( ) ;
417- const runtime = { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ;
429+ const runtime = createRuntimeMock ( ) ;
418430 const handler = createLineNodeWebhookHandler ( {
419431 channelSecret : SECRET ,
420432 bot,
0 commit comments