@@ -105,6 +105,7 @@ const runSignedPost = async (params: {
105105 ) ;
106106
107107async function invokeWebhook ( params : {
108+ ackPolicy ?: "after_agent_dispatch" | "after_receive_record" ;
108109 body : unknown ;
109110 headers ?: Record < string , string > ;
110111 onEvents ?: ReturnType < typeof vi . fn > ;
@@ -115,6 +116,7 @@ async function invokeWebhook(params: {
115116 const middleware = createLineWebhookMiddleware ( {
116117 channelSecret : SECRET ,
117118 onEvents : onEventsMock as never ,
119+ ackPolicy : params . ackPolicy ,
118120 runtime : params . runtime ,
119121 } ) ;
120122
@@ -475,6 +477,70 @@ describe("createLineNodeWebhookHandler", () => {
475477 releaseAuthenticated ( ) ;
476478 } ) ;
477479
480+ it ( "supports after_receive_record acknowledgement before event processing completes" , async ( ) => {
481+ const rawBody = JSON . stringify ( { events : [ { type : "message" } ] } ) ;
482+ let releaseAuthenticated : ( ( ) => void ) | undefined ;
483+ const bot = {
484+ handleWebhook : vi . fn (
485+ async ( ) =>
486+ await new Promise < void > ( ( resolve ) => {
487+ releaseAuthenticated = resolve ;
488+ } ) ,
489+ ) ,
490+ } ;
491+ const runtime = createRuntimeMock ( ) ;
492+ const handler = createLineNodeWebhookHandler ( {
493+ channelSecret : SECRET ,
494+ bot,
495+ runtime,
496+ readBody : async ( ) => rawBody ,
497+ ackPolicy : "after_receive_record" ,
498+ } ) ;
499+
500+ const { res } = createRes ( ) ;
501+ await runSignedPost ( { handler, rawBody, secret : SECRET , res } ) ;
502+
503+ expect ( bot . handleWebhook ) . toHaveBeenCalledTimes ( 1 ) ;
504+ expect ( res . statusCode ) . toBe ( 200 ) ;
505+ expect ( res . headersSent ) . toBe ( true ) ;
506+ if ( ! releaseAuthenticated ) {
507+ throw new Error ( "Expected LINE authenticated request release callback to be initialized" ) ;
508+ }
509+ releaseAuthenticated ( ) ;
510+ } ) ;
511+
512+ it ( "normalizes unsupported acknowledgement policies to dispatch acceptance" , async ( ) => {
513+ const rawBody = JSON . stringify ( { events : [ { type : "message" } ] } ) ;
514+ let releaseAuthenticated : ( ( ) => void ) | undefined ;
515+ const bot = {
516+ handleWebhook : vi . fn (
517+ async ( ) =>
518+ await new Promise < void > ( ( resolve ) => {
519+ releaseAuthenticated = resolve ;
520+ } ) ,
521+ ) ,
522+ } ;
523+ const runtime = createRuntimeMock ( ) ;
524+ const handler = createLineNodeWebhookHandler ( {
525+ channelSecret : SECRET ,
526+ bot,
527+ runtime,
528+ readBody : async ( ) => rawBody ,
529+ ackPolicy : "manual" as never ,
530+ } ) ;
531+
532+ const { res } = createRes ( ) ;
533+ await runSignedPost ( { handler, rawBody, secret : SECRET , res } ) ;
534+
535+ expect ( bot . handleWebhook ) . toHaveBeenCalledTimes ( 1 ) ;
536+ expect ( res . statusCode ) . toBe ( 200 ) ;
537+ expect ( res . headersSent ) . toBe ( true ) ;
538+ if ( ! releaseAuthenticated ) {
539+ throw new Error ( "Expected LINE authenticated request release callback to be initialized" ) ;
540+ }
541+ releaseAuthenticated ( ) ;
542+ } ) ;
543+
478544 it ( "returns 400 for invalid JSON payload even when signature is valid" , async ( ) => {
479545 const rawBody = "not json" ;
480546 const { bot, handler, secret } = createPostWebhookTestHarness ( rawBody ) ;
@@ -572,6 +638,29 @@ describe("createLineWebhookMiddleware", () => {
572638 } ) ;
573639 } ) ;
574640
641+ it ( "supports middleware after_receive_record acknowledgement before event processing completes" , async ( ) => {
642+ let releaseEvents : ( ( ) => void ) | undefined ;
643+ const onEvents = vi . fn (
644+ async ( ) =>
645+ await new Promise < void > ( ( resolve ) => {
646+ releaseEvents = resolve ;
647+ } ) ,
648+ ) ;
649+
650+ const { res } = await invokeWebhook ( {
651+ ackPolicy : "after_receive_record" ,
652+ body : JSON . stringify ( { events : [ { type : "message" } ] } ) ,
653+ onEvents,
654+ } ) ;
655+
656+ expect ( onEvents ) . toHaveBeenCalledTimes ( 1 ) ;
657+ expect ( res . status ) . toHaveBeenCalledWith ( 200 ) ;
658+ if ( ! releaseEvents ) {
659+ throw new Error ( "expected pending LINE middleware handler" ) ;
660+ }
661+ releaseEvents ( ) ;
662+ } ) ;
663+
575664 it ( "rejects invalid signed raw JSON even when req.body is a valid object" , async ( ) => {
576665 const onEvents = vi . fn ( async ( ) => { } ) ;
577666 const rawBody = "not-json" ;
0 commit comments