@@ -5,19 +5,53 @@ import { garminWebhookFailureStatus, verifyGarminWebhookSignature } from "./webh
55class TrackingRequest extends Request {
66 bodyWasRead = false ;
77
8- override async text ( ) : Promise < string > {
8+ private trackBodyRead ( ) : void {
99 this . bodyWasRead = true ;
10+ }
11+
12+ override get body ( ) {
13+ this . trackBodyRead ( ) ;
14+ return super . body ;
15+ }
16+
17+ override async arrayBuffer ( ) : Promise < ArrayBuffer > {
18+ this . trackBodyRead ( ) ;
19+ return await super . arrayBuffer ( ) ;
20+ }
21+
22+ override async blob ( ) : Promise < Blob > {
23+ this . trackBodyRead ( ) ;
24+ return await super . blob ( ) ;
25+ }
26+
27+ override clone ( ) : Request {
28+ this . trackBodyRead ( ) ;
29+ return super . clone ( ) ;
30+ }
31+
32+ override async formData ( ) : Promise < FormData > {
33+ this . trackBodyRead ( ) ;
34+ return await super . formData ( ) ;
35+ }
36+
37+ override async json ( ) : Promise < unknown > {
38+ this . trackBodyRead ( ) ;
39+ return await super . json ( ) ;
40+ }
41+
42+ override async text ( ) : Promise < string > {
43+ this . trackBodyRead ( ) ;
1044 return await super . text ( ) ;
1145 }
1246}
1347
14- async function runGarminRoute ( req : Request ) : Promise < Response > {
48+ async function runGarminRoute ( req : Request , ctx : object = { } ) : Promise < Response > {
1549 const route = http . lookup ( new URL ( req . url ) . pathname , "POST" ) ;
1650 if ( ! route ) throw new Error ( "Expected Garmin path-secret route" ) ;
1751 const handler = route [ 0 ] as unknown as {
1852 _handler : ( ctx : object , request : Request ) => Promise < Response > ;
1953 } ;
20- return await handler . _handler ( { } , req ) ;
54+ return await handler . _handler ( ctx , req ) ;
2155}
2256
2357describe ( "Garmin path-secret webhook routes" , ( ) => {
@@ -31,6 +65,35 @@ describe("Garmin path-secret webhook routes", () => {
3165 expect ( pathSecretRoute ) . not . toBeNull ( ) ;
3266 } ) ;
3367
68+ it ( "processes an authenticated path-secret delivery through the registered handler" , async ( ) => {
69+ vi . stubEnv ( "GARMIN_WEBHOOK_SECRET" , "secret-1" ) ;
70+ const store = vi . fn ( ) . mockResolvedValue ( "storage-1" ) ;
71+ const runMutation = vi . fn ( ) . mockResolvedValue ( "event-1" ) ;
72+ const runAfter = vi . fn ( ) . mockResolvedValue ( undefined ) ;
73+ const req = new Request ( "https://example.com/garmin/webhook/activities/secret-1" , {
74+ method : "POST" ,
75+ body : '{"activities":[]}' ,
76+ } ) ;
77+
78+ const response = await runGarminRoute ( req , {
79+ storage : { store } ,
80+ runMutation,
81+ scheduler : { runAfter } ,
82+ } ) ;
83+
84+ expect ( response . status ) . toBe ( 200 ) ;
85+ expect ( store ) . toHaveBeenCalledOnce ( ) ;
86+ expect ( runMutation ) . toHaveBeenCalledWith ( expect . anything ( ) , {
87+ eventType : "activities" ,
88+ rawPayloadStorageId : "storage-1" ,
89+ } ) ;
90+ expect ( runAfter ) . toHaveBeenCalledWith ( 0 , expect . anything ( ) , {
91+ eventId : "event-1" ,
92+ eventType : "activities" ,
93+ rawPayloadStorageId : "storage-1" ,
94+ } ) ;
95+ } ) ;
96+
3497 it ( "does not route path-secret requests for unsupported event types" , ( ) => {
3598 const req = new TrackingRequest ( "https://example.com/garmin/webhook/notSupported/secret-1" , {
3699 method : "POST" ,
@@ -89,6 +152,13 @@ describe("verifyGarminWebhookSignature", () => {
89152 await expect ( verifyGarminWebhookSignature ( req , "{}" ) ) . resolves . toEqual ( { valid : true } ) ;
90153 } ) ;
91154
155+ it ( "decodes the path secret before comparing it" , async ( ) => {
156+ vi . stubEnv ( "GARMIN_WEBHOOK_SECRET" , "secret/1" ) ;
157+ const req = new Request ( "https://example.com/garmin/webhook/activities/secret%2F1" ) ;
158+
159+ await expect ( verifyGarminWebhookSignature ( req , "{}" ) ) . resolves . toEqual ( { valid : true } ) ;
160+ } ) ;
161+
92162 it ( "rejects a valid secret when the path contains an extra segment" , async ( ) => {
93163 vi . stubEnv ( "GARMIN_WEBHOOK_SECRET" , "secret-1" ) ;
94164 const req = new Request ( "https://example.com/garmin/webhook/activities/extra/secret-1" ) ;
0 commit comments