@@ -34,225 +34,105 @@ public FunctionTests(ITestOutputHelper testOutputHelper)
3434 // await TestIdempotencyHandler(functionName);
3535 // }
3636
37- [ Theory ]
38- [ MemberData ( nameof ( TestData . Inline ) , MemberType = typeof ( TestData ) ) ]
39- public async Task IdempotencyHandlerTest ( string functionName , string tableName )
40- {
41- _tableName = tableName ;
42- await TestIdempotencyHandler ( functionName ) ;
43- }
44-
45- [ Theory ]
46- [ MemberData ( nameof ( TestData . Inline ) , MemberType = typeof ( TestData ) ) ]
47- public async Task IdempotencyAttributeTest ( string functionName , string tableName )
48- {
49- _tableName = tableName ;
50- await TestIdempotencyAttribute ( functionName ) ;
51- }
52-
5337 [ Theory ]
5438 [ MemberData ( nameof ( TestData . Inline ) , MemberType = typeof ( TestData ) ) ]
5539 public async Task IdempotencyPayloadSubsetTest ( string functionName , string tableName )
5640 {
5741 _tableName = tableName ;
58- await TestIdempotencyPayloadSubset ( functionName ) ;
59- }
60-
61- private async Task TestIdempotencyPayloadSubset ( string functionName )
62- {
6342 await UpdateFunctionHandler ( functionName , "Function::IdempotencyPayloadSubsetTest.Function::FunctionHandler" ) ;
6443
65- var initialGuid = string . Empty ;
66-
67- for ( int i = 0 ; i < 2 ; i ++ )
68- {
69- var productId = Guid . NewGuid ( ) . ToString ( ) ;
70- var apiGatewayRequest = new APIGatewayProxyRequest
71- {
72- Body = $ "{{\" user_id\" :\" xyz\" ,\" product_id\" :\" { productId } \" }}"
73- } ;
74-
75- var payload = JsonSerializer . Serialize ( apiGatewayRequest ) ;
76-
77- var request = new InvokeRequest
78- {
79- FunctionName = functionName ,
80- InvocationType = InvocationType . RequestResponse ,
81- Payload = payload ,
82- LogType = LogType . Tail ,
83- } ;
84-
85- // run two times with the same request
86- for ( int j = 0 ; j < 2 ; j ++ )
87- {
88- var response = await _lambdaClient . InvokeAsync ( request ) ;
89-
90- if ( string . IsNullOrEmpty ( response . LogResult ) )
91- {
92- Assert . Fail ( "No LogResult field returned in the response of Lambda invocation." ) ;
93- }
94-
95- var responsePayload = System . Text . Encoding . UTF8 . GetString ( response . Payload . ToArray ( ) ) ;
96- var parsedPayload = JsonSerializer . Deserialize < APIGatewayProxyResponse > ( responsePayload ) ;
97-
98- if ( parsedPayload == null )
99- {
100- Assert . Fail ( "Failed to parse payload." ) ;
101- }
102-
103- Assert . Equal ( 200 , parsedPayload . StatusCode ) ;
104-
105- var parsedResponse = JsonSerializer . Deserialize < Response > ( parsedPayload . Body ) ;
106-
107- if ( parsedResponse == null )
108- {
109- Assert . Fail ( "Failed to parse response." ) ;
110- }
111-
112- if ( j == 0 )
113- {
114- // first call should return a new guid
115- if ( parsedResponse . Guid == initialGuid )
116- {
117- Assert . Fail ( "Idempotency failed to clear cache." ) ;
118- }
119-
120- initialGuid = parsedResponse . Guid ;
121- }
122-
123- Assert . Equal ( initialGuid , parsedResponse . Guid ) ;
124-
125- // Query DynamoDB and assert results
126- var hashRequest = Helpers . HashRequest ( $ "[\" xyz\" ,\" { productId } \" ]") ;
127-
128- var id = $ "{ functionName } .FunctionHandler#{ hashRequest } ";
129- await AssertDynamoDbData ( id , initialGuid ) ;
130- }
131- }
44+ // First unique request
45+ var firstProductId = Guid . NewGuid ( ) . ToString ( ) ;
46+ var ( firstResponse1 , firstGuid1 ) = await ExecutePayloadSubsetRequest ( functionName , "xyz" , firstProductId ) ;
47+ var ( firstResponse2 , firstGuid2 ) = await ExecutePayloadSubsetRequest ( functionName , "xyz" , firstProductId ) ;
48+
49+ // Assert first request pair
50+ Assert . Equal ( 200 , firstResponse1 . StatusCode ) ;
51+ Assert . Equal ( 200 , firstResponse2 . StatusCode ) ;
52+ Assert . Equal ( firstGuid1 , firstGuid2 ) ; // Idempotency check
53+ await AssertDynamoDbData (
54+ $ "{ functionName } .FunctionHandler#{ Helpers . HashRequest ( $ "[\" xyz\" ,\" { firstProductId } \" ]") } ",
55+ firstGuid1 ) ;
56+
57+ // Second unique request
58+ var secondProductId = Guid . NewGuid ( ) . ToString ( ) ;
59+ var ( secondResponse1 , secondGuid1 ) = await ExecutePayloadSubsetRequest ( functionName , "xyz" , secondProductId ) ;
60+ var ( secondResponse2 , secondGuid2 ) = await ExecutePayloadSubsetRequest ( functionName , "xyz" , secondProductId ) ;
61+
62+ // Assert second request pair
63+ Assert . Equal ( 200 , secondResponse1 . StatusCode ) ;
64+ Assert . Equal ( 200 , secondResponse2 . StatusCode ) ;
65+ Assert . Equal ( secondGuid1 , secondGuid2 ) ; // Idempotency check
66+ Assert . NotEqual ( firstGuid1 , secondGuid1 ) ; // Different requests should have different GUIDs
67+ await AssertDynamoDbData (
68+ $ "{ functionName } .FunctionHandler#{ Helpers . HashRequest ( $ "[\" xyz\" ,\" { secondProductId } \" ]") } ",
69+ secondGuid1 ) ;
13270 }
13371
134- private async Task TestIdempotencyAttribute ( string functionName )
72+ [ Theory ]
73+ [ MemberData ( nameof ( TestData . Inline ) , MemberType = typeof ( TestData ) ) ]
74+ public async Task IdempotencyAttributeTest ( string functionName , string tableName )
13575 {
76+ _tableName = tableName ;
13677 await UpdateFunctionHandler ( functionName , "Function::IdempotencyAttributeTest.Function::FunctionHandler" ) ;
13778
138- var initialGuid = string . Empty ;
139-
140- for ( int i = 0 ; i < 2 ; i ++ )
141- {
142- var requestId = Guid . NewGuid ( ) . ToString ( ) ;
143- var apiGatewayRequest = new APIGatewayProxyRequest
144- {
145- Body = "{\" user_id\" :\" xyz\" ,\" product_id\" :\" 123456789\" }" ,
146- RequestContext = new APIGatewayProxyRequest . ProxyRequestContext
147- {
148- AccountId = "123456789012" ,
149- RequestId = requestId , // requestId is used to invalidate the cache
150- }
151- } ;
152-
153- var payload = JsonSerializer . Serialize ( apiGatewayRequest ) ;
154-
155- var request = new InvokeRequest
156- {
157- FunctionName = functionName ,
158- InvocationType = InvocationType . RequestResponse ,
159- Payload = payload ,
160- LogType = LogType . Tail ,
161- } ;
162-
163- // run two times with the same request
164- for ( int j = 0 ; j < 2 ; j ++ )
165- {
166- var response = await _lambdaClient . InvokeAsync ( request ) ;
167-
168- if ( string . IsNullOrEmpty ( response . LogResult ) )
169- {
170- Assert . Fail ( "No LogResult field returned in the response of Lambda invocation." ) ;
171- }
172-
173- var responsePayload = System . Text . Encoding . UTF8 . GetString ( response . Payload . ToArray ( ) ) ;
174- var parsedPayload = JsonSerializer . Deserialize < APIGatewayProxyResponse > ( responsePayload ) ;
175-
176- if ( parsedPayload == null )
177- {
178- Assert . Fail ( "Failed to parse payload." ) ;
179- }
180-
181- Assert . Equal ( 200 , parsedPayload . StatusCode ) ;
182-
183- if ( j == 0 )
184- {
185- // first call should return a new guid
186- if ( parsedPayload . Body == initialGuid )
187- {
188- Assert . Fail ( "Idempotency failed to clear cache." ) ;
189- }
190-
191- initialGuid = parsedPayload . Body ;
192- }
193-
194- Assert . Equal ( initialGuid , parsedPayload . Body ) ;
195-
196- // Query DynamoDB and assert results
197- var hashRequestId = Helpers . HashRequest ( requestId ) ;
198- var id = $ "{ functionName } .MyInternalMethod#{ hashRequestId } ";
199- await AssertDynamoDbData ( id , initialGuid , true ) ;
200- }
201- }
79+ // First unique request
80+ var requestId1 = Guid . NewGuid ( ) . ToString ( ) ;
81+ var ( firstResponse1 , firstGuid1 ) = await ExecuteAttributeRequest ( functionName , requestId1 ) ;
82+ var ( firstResponse2 , firstGuid2 ) = await ExecuteAttributeRequest ( functionName , requestId1 ) ;
83+
84+ // Assert first request pair
85+ Assert . Equal ( 200 , firstResponse1 . StatusCode ) ;
86+ Assert . Equal ( 200 , firstResponse2 . StatusCode ) ;
87+ Assert . Equal ( firstGuid1 , firstGuid2 ) ; // Idempotency check
88+ await AssertDynamoDbData (
89+ $ "{ functionName } .MyInternalMethod#{ Helpers . HashRequest ( requestId1 ) } ",
90+ firstGuid1 ,
91+ true ) ;
92+
93+ // Second unique request
94+ var requestId2 = Guid . NewGuid ( ) . ToString ( ) ;
95+ var ( secondResponse1 , secondGuid1 ) = await ExecuteAttributeRequest ( functionName , requestId2 ) ;
96+ var ( secondResponse2 , secondGuid2 ) = await ExecuteAttributeRequest ( functionName , requestId2 ) ;
97+
98+ // Assert second request pair
99+ Assert . Equal ( 200 , secondResponse1 . StatusCode ) ;
100+ Assert . Equal ( 200 , secondResponse2 . StatusCode ) ;
101+ Assert . Equal ( secondGuid1 , secondGuid2 ) ; // Idempotency check
102+ Assert . NotEqual ( firstGuid1 , secondGuid1 ) ; // Different requests should have different GUIDs
103+ await AssertDynamoDbData (
104+ $ "{ functionName } .MyInternalMethod#{ Helpers . HashRequest ( requestId2 ) } ",
105+ secondGuid1 ,
106+ true ) ;
202107 }
203108
204- internal async Task TestIdempotencyHandler ( string functionName )
109+ [ Theory ]
110+ [ MemberData ( nameof ( TestData . Inline ) , MemberType = typeof ( TestData ) ) ]
111+ public async Task IdempotencyHandlerTest ( string functionName , string tableName )
205112 {
113+ _tableName = tableName ;
206114 await UpdateFunctionHandler ( functionName , "Function::Function.Function::FunctionHandler" ) ;
207115
208- var request = new InvokeRequest
209- {
210- FunctionName = functionName ,
211- InvocationType = InvocationType . RequestResponse ,
212- Payload = await File . ReadAllTextAsync ( "../../../../../../../payload.json" ) ,
213- LogType = LogType . Tail ,
214- } ;
215-
216- var initialGuid = string . Empty ;
217-
218- // run three times to test idempotency
219- for ( int i = 0 ; i < 3 ; i ++ )
220- {
221- var response = await _lambdaClient . InvokeAsync ( request ) ;
222-
223- if ( string . IsNullOrEmpty ( response . LogResult ) )
224- {
225- Assert . Fail ( "No LogResult field returned in the response of Lambda invocation." ) ;
226- }
227-
228- var payload = System . Text . Encoding . UTF8 . GetString ( response . Payload . ToArray ( ) ) ;
229- var parsedPayload = JsonSerializer . Deserialize < APIGatewayProxyResponse > ( payload ) ;
230-
231- if ( parsedPayload == null )
232- {
233- Assert . Fail ( "Failed to parse payload." ) ;
234- }
235-
236- Assert . Equal ( 200 , parsedPayload . StatusCode ) ;
237-
238- var parsedResponse = JsonSerializer . Deserialize < Response > ( parsedPayload . Body ) ;
239-
240- if ( parsedResponse == null )
241- {
242- Assert . Fail ( "Failed to parse response." ) ;
243- }
244-
245- if ( i == 0 )
246- {
247- initialGuid = parsedResponse . Guid ;
248- }
249-
250- Assert . Equal ( initialGuid , parsedResponse . Guid ) ;
251- }
252-
253- // Query DynamoDB and assert results
254- var id = $ "{ functionName } .FunctionHandler#35973cf447e6cc11008d603c791a232f";
255- await AssertDynamoDbData ( id , initialGuid ) ;
116+ var payload = await File . ReadAllTextAsync ( "../../../../../../../payload.json" ) ;
117+
118+ // Execute three identical requests
119+ var ( response1 , guid1 ) = await ExecuteHandlerRequest ( functionName , payload ) ;
120+ var ( response2 , guid2 ) = await ExecuteHandlerRequest ( functionName , payload ) ;
121+ var ( response3 , guid3 ) = await ExecuteHandlerRequest ( functionName , payload ) ;
122+
123+ // Assert all responses
124+ Assert . Equal ( 200 , response1 . StatusCode ) ;
125+ Assert . Equal ( 200 , response2 . StatusCode ) ;
126+ Assert . Equal ( 200 , response3 . StatusCode ) ;
127+
128+ // Assert idempotency
129+ Assert . Equal ( guid1 , guid2 ) ;
130+ Assert . Equal ( guid2 , guid3 ) ;
131+
132+ // Assert DynamoDB
133+ await AssertDynamoDbData (
134+ $ "{ functionName } .FunctionHandler#35973cf447e6cc11008d603c791a232f",
135+ guid1 ) ;
256136 }
257137
258138 private async Task UpdateFunctionHandler ( string functionName , string handler )
@@ -334,6 +214,87 @@ private async Task AssertDynamoDbData(string id, string requestId, bool isSavedD
334214 }
335215 }
336216 }
217+
218+ // Helper methods for executing requests
219+ private async Task < ( APIGatewayProxyResponse Response , string Guid ) > ExecutePayloadSubsetRequest (
220+ string functionName , string userId , string productId )
221+ {
222+ var request = new InvokeRequest
223+ {
224+ FunctionName = functionName ,
225+ InvocationType = InvocationType . RequestResponse ,
226+ Payload = JsonSerializer . Serialize ( new APIGatewayProxyRequest
227+ {
228+ Body = $ "{{\" user_id\" :\" { userId } \" ,\" product_id\" :\" { productId } \" }}"
229+ } ) ,
230+ LogType = LogType . Tail
231+ } ;
232+
233+ return await ExecuteRequest ( request ) ;
234+ }
235+
236+ private async Task < ( APIGatewayProxyResponse Response , string Guid ) > ExecuteAttributeRequest (
237+ string functionName , string requestId )
238+ {
239+ var request = new InvokeRequest
240+ {
241+ FunctionName = functionName ,
242+ InvocationType = InvocationType . RequestResponse ,
243+ Payload = JsonSerializer . Serialize ( new APIGatewayProxyRequest
244+ {
245+ Body = "{\" user_id\" :\" ***\" ,\" product_id\" :\" 123456789\" }" ,
246+ RequestContext = new APIGatewayProxyRequest . ProxyRequestContext
247+ {
248+ AccountId = "123456789012" ,
249+ RequestId = requestId
250+ }
251+ } ) ,
252+ LogType = LogType . Tail
253+ } ;
254+
255+ return await ExecuteRequest ( request ) ;
256+ }
257+
258+ private async Task < ( APIGatewayProxyResponse Response , string Guid ) > ExecuteHandlerRequest (
259+ string functionName , string payload )
260+ {
261+ var request = new InvokeRequest
262+ {
263+ FunctionName = functionName ,
264+ InvocationType = InvocationType . RequestResponse ,
265+ Payload = payload ,
266+ LogType = LogType . Tail
267+ } ;
268+
269+ return await ExecuteRequest ( request ) ;
270+ }
271+
272+ private async Task < ( APIGatewayProxyResponse Response , string Guid ) > ExecuteRequest ( InvokeRequest request )
273+ {
274+ var response = await _lambdaClient . InvokeAsync ( request ) ;
275+
276+ if ( string . IsNullOrEmpty ( response . LogResult ) )
277+ Assert . Fail ( "No LogResult field returned in the response of Lambda invocation." ) ;
278+
279+ var responsePayload = System . Text . Encoding . UTF8 . GetString ( response . Payload . ToArray ( ) ) ;
280+ var parsedResponse = JsonSerializer . Deserialize < APIGatewayProxyResponse > ( responsePayload )
281+ ?? throw new Exception ( "Failed to parse payload." ) ;
282+
283+ string guid ;
284+ try
285+ {
286+ // The GUID is inside the Response object
287+ var parsedBody = JsonSerializer . Deserialize < Response > ( parsedResponse . Body ) ;
288+ guid = parsedBody ? . Guid ?? parsedResponse . Body ;
289+ }
290+ catch ( JsonException )
291+ {
292+ // For scenarios where the Body is already the GUID
293+ guid = parsedResponse . Body ;
294+ }
295+
296+ return ( parsedResponse , guid ) ;
297+ }
337298}
338299
339300public record Response ( string Greeting , string Guid ) ;
0 commit comments