1+ import { InvokeStore } from "@aws/lambda-invoke-store" ;
12import { requireRequestsFrom } from "@aws-sdk/aws-util-test/src" ;
23import { Lambda } from "@aws-sdk/client-lambda" ;
3- import { describe , expect , test as it } from "vitest" ;
4+ import { afterEach , beforeEach , describe , expect , test as it , vi } from "vitest" ;
45
56const ENV_LAMBDA_FUNCTION_NAME = "AWS_LAMBDA_FUNCTION_NAME" ;
67const ENV_TRACE_ID = "_X_AMZN_TRACE_ID" ;
78
89describe ( "middleware-recursion-detection" , ( ) => {
10+ const originEnv = process . env ;
11+
12+ beforeEach ( ( ) => {
13+ vi . spyOn ( InvokeStore , "getInstanceAsync" ) . mockResolvedValue ( undefined as any ) ;
14+ } ) ;
15+
16+ afterEach ( ( ) => {
17+ vi . restoreAllMocks ( ) ;
18+ process . env = originEnv ;
19+ } ) ;
20+
921 describe ( Lambda . name , ( ) => {
1022 it ( "should create recursion detection headers" , async ( ) => {
1123 process . env [ ENV_LAMBDA_FUNCTION_NAME ] = "MyLambdaFunction" ;
@@ -27,5 +39,203 @@ describe("middleware-recursion-detection", () => {
2739
2840 expect . hasAssertions ( ) ;
2941 } ) ;
42+
43+ it ( "should propagate W3C traceparent header from InvokeStore" , async ( ) => {
44+ const mockTraceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" ;
45+ vi . spyOn ( InvokeStore , "getInstanceAsync" ) . mockResolvedValue ( {
46+ getXRayTraceId : ( ) => undefined ,
47+ getTraceparent : ( ) => mockTraceparent ,
48+ getTracestate : ( ) => undefined ,
49+ getBaggage : ( ) => undefined ,
50+ } as any ) ;
51+
52+ const client = new Lambda ( {
53+ region : "us-west-2" ,
54+ } ) ;
55+
56+ requireRequestsFrom ( client ) . toMatch ( {
57+ headers : {
58+ traceparent : new RegExp ( `^${ mockTraceparent . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) } $` ) ,
59+ } ,
60+ } ) ;
61+
62+ await client . invoke ( {
63+ FunctionName : "my-function" ,
64+ } ) ;
65+
66+ expect . hasAssertions ( ) ;
67+ } ) ;
68+
69+ it ( "should propagate all W3C trace context headers from InvokeStore" , async ( ) => {
70+ const mockTraceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" ;
71+ const mockTracestate = "congo=t61rcWkgMzE" ;
72+ const mockBaggage = "userId=alice,serverNode=DF%2028" ;
73+ vi . spyOn ( InvokeStore , "getInstanceAsync" ) . mockResolvedValue ( {
74+ getXRayTraceId : ( ) => undefined ,
75+ getTraceparent : ( ) => mockTraceparent ,
76+ getTracestate : ( ) => mockTracestate ,
77+ getBaggage : ( ) => mockBaggage ,
78+ } as any ) ;
79+
80+ const client = new Lambda ( {
81+ region : "us-west-2" ,
82+ } ) ;
83+
84+ requireRequestsFrom ( client ) . toMatch ( {
85+ headers : {
86+ traceparent : / ^ 0 0 - 4 b f 9 2 f 3 5 7 7 b 3 4 d a 6 a 3 c e 9 2 9 d 0 e 0 e 4 7 3 6 - 0 0 f 0 6 7 a a 0 b a 9 0 2 b 7 - 0 1 $ / ,
87+ tracestate : / ^ c o n g o = t 6 1 r c W k g M z E $ / ,
88+ baggage : / ^ u s e r I d = a l i c e , s e r v e r N o d e = D F % 2 0 2 8 $ / ,
89+ } ,
90+ } ) ;
91+
92+ await client . invoke ( {
93+ FunctionName : "my-function" ,
94+ } ) ;
95+
96+ expect . hasAssertions ( ) ;
97+ } ) ;
98+
99+ it ( "should propagate both X-Amzn-Trace-Id and W3C headers when in Lambda" , async ( ) => {
100+ const mockXRayTraceId = "Root=1-abc-def;Parent=123;Sampled=1" ;
101+ const mockTraceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" ;
102+ const mockTracestate = "congo=t61rcWkgMzE" ;
103+ process . env [ ENV_LAMBDA_FUNCTION_NAME ] = "MyLambdaFunction" ;
104+
105+ vi . spyOn ( InvokeStore , "getInstanceAsync" ) . mockResolvedValue ( {
106+ getXRayTraceId : ( ) => mockXRayTraceId ,
107+ getTraceparent : ( ) => mockTraceparent ,
108+ getTracestate : ( ) => mockTracestate ,
109+ getBaggage : ( ) => undefined ,
110+ } as any ) ;
111+
112+ const client = new Lambda ( {
113+ region : "us-west-2" ,
114+ } ) ;
115+
116+ requireRequestsFrom ( client ) . toMatch ( {
117+ headers : {
118+ "X-Amzn-Trace-Id" : / ^ R o o t = 1 - a b c - d e f ; P a r e n t = 1 2 3 ; S a m p l e d = 1 $ / ,
119+ traceparent : / ^ 0 0 - 4 b f 9 2 f 3 5 7 7 b 3 4 d a 6 a 3 c e 9 2 9 d 0 e 0 e 4 7 3 6 - 0 0 f 0 6 7 a a 0 b a 9 0 2 b 7 - 0 1 $ / ,
120+ tracestate : / ^ c o n g o = t 6 1 r c W k g M z E $ / ,
121+ } ,
122+ } ) ;
123+
124+ await client . invoke ( {
125+ FunctionName : "my-function" ,
126+ } ) ;
127+
128+ expect . hasAssertions ( ) ;
129+ } ) ;
130+
131+ it ( "should NOT overwrite existing traceparent or add tracestate/baggage from InvokeStore when traceparent is already set" , async ( ) => {
132+ const existingTraceparent = "00-abcdef1234567890abcdef1234567890-1234567890abcdef-01" ;
133+ const mockTraceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" ;
134+ const mockTracestate = "congo=t61rcWkgMzE" ;
135+ const mockBaggage = "userId=alice,serverNode=DF%2028" ;
136+ vi . spyOn ( InvokeStore , "getInstanceAsync" ) . mockResolvedValue ( {
137+ getXRayTraceId : ( ) => undefined ,
138+ getTraceparent : ( ) => mockTraceparent ,
139+ getTracestate : ( ) => mockTracestate ,
140+ getBaggage : ( ) => mockBaggage ,
141+ } as any ) ;
142+
143+ const client = new Lambda ( {
144+ region : "us-west-2" ,
145+ } ) ;
146+
147+ // Add middleware that sets traceparent before recursion detection runs.
148+ // recursionDetectionMiddleware runs at build step with low priority,
149+ // so we add at build step with high priority to run first.
150+ client . middlewareStack . add (
151+ ( next ) => async ( args : any ) => {
152+ args . request . headers [ "traceparent" ] = existingTraceparent ;
153+ return next ( args ) ;
154+ } ,
155+ { step : "build" , priority : "high" , name : "testSetTraceparent" , override : true }
156+ ) ;
157+
158+ requireRequestsFrom ( client ) . toMatch ( {
159+ headers : {
160+ traceparent : new RegExp ( `^${ existingTraceparent . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) } $` ) ,
161+ tracestate : ( value : any ) => expect ( value ) . toBeUndefined ( ) ,
162+ baggage : ( value : any ) => expect ( value ) . toBeUndefined ( ) ,
163+ } ,
164+ } ) ;
165+
166+ await client . invoke ( {
167+ FunctionName : "my-function" ,
168+ } ) ;
169+
170+ expect . hasAssertions ( ) ;
171+ } ) ;
172+
173+ it ( "should preserve existing traceparent, tracestate, and baggage without overwriting from InvokeStore" , async ( ) => {
174+ const existingTraceparent = "00-abcdef1234567890abcdef1234567890-1234567890abcdef-01" ;
175+ const existingTracestate = "vendor1=opaqueValue1" ;
176+ const existingBaggage = "key1=value1" ;
177+ const mockTraceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" ;
178+ const mockTracestate = "congo=t61rcWkgMzE" ;
179+ const mockBaggage = "userId=alice,serverNode=DF%2028" ;
180+ vi . spyOn ( InvokeStore , "getInstanceAsync" ) . mockResolvedValue ( {
181+ getXRayTraceId : ( ) => undefined ,
182+ getTraceparent : ( ) => mockTraceparent ,
183+ getTracestate : ( ) => mockTracestate ,
184+ getBaggage : ( ) => mockBaggage ,
185+ } as any ) ;
186+
187+ const client = new Lambda ( {
188+ region : "us-west-2" ,
189+ } ) ;
190+
191+ client . middlewareStack . add (
192+ ( next ) => async ( args : any ) => {
193+ args . request . headers [ "traceparent" ] = existingTraceparent ;
194+ args . request . headers [ "tracestate" ] = existingTracestate ;
195+ args . request . headers [ "baggage" ] = existingBaggage ;
196+ return next ( args ) ;
197+ } ,
198+ { step : "build" , priority : "high" , name : "testSetTraceHeaders" , override : true }
199+ ) ;
200+
201+ requireRequestsFrom ( client ) . toMatch ( {
202+ headers : {
203+ traceparent : new RegExp ( `^${ existingTraceparent . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) } $` ) ,
204+ tracestate : new RegExp ( `^${ existingTracestate . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) } $` ) ,
205+ baggage : new RegExp ( `^${ existingBaggage . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) } $` ) ,
206+ } ,
207+ } ) ;
208+
209+ await client . invoke ( {
210+ FunctionName : "my-function" ,
211+ } ) ;
212+
213+ expect . hasAssertions ( ) ;
214+ } ) ;
215+
216+ it ( "should NOT add W3C headers when InvokeStore has no traceparent" , async ( ) => {
217+ vi . spyOn ( InvokeStore , "getInstanceAsync" ) . mockResolvedValue ( {
218+ getXRayTraceId : ( ) => undefined ,
219+ getTraceparent : ( ) => undefined ,
220+ getTracestate : ( ) => "congo=t61rcWkgMzE" ,
221+ getBaggage : ( ) => "userId=alice" ,
222+ } as any ) ;
223+
224+ const client = new Lambda ( {
225+ region : "us-west-2" ,
226+ } ) ;
227+
228+ requireRequestsFrom ( client ) . toMatch ( {
229+ headers : {
230+ traceparent : ( value : any ) => expect ( value ) . toBeUndefined ( ) ,
231+ } ,
232+ } ) ;
233+
234+ await client . invoke ( {
235+ FunctionName : "my-function" ,
236+ } ) ;
237+
238+ expect . hasAssertions ( ) ;
239+ } ) ;
30240 } ) ;
31241} ) ;
0 commit comments