1+ import static datadog.trace.api.gateway.Events.EVENTS
2+
13import datadog.trace.agent.test.naming.VersionedNamingTestBase
24import datadog.trace.api.DDSpanTypes
3- import java.nio.charset.StandardCharsets
5+ import datadog.trace.api.function.TriConsumer
6+ import datadog.trace.api.function.TriFunction
7+ import datadog.trace.api.gateway.Flow
8+ import datadog.trace.api.gateway.RequestContext
9+ import datadog.trace.api.gateway.RequestContextSlot
10+ import datadog.trace.bootstrap.ActiveSubsystems
11+ import datadog.trace.bootstrap.instrumentation.api.AgentTracer
12+ import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter
413import com.amazonaws.services.lambda.runtime.Context
14+ import java.nio.charset.StandardCharsets
15+ import java.util.function.BiFunction
16+ import java.util.function.Function
17+ import java.util.function.Supplier
518
619abstract class LambdaHandlerInstrumentationTest extends VersionedNamingTestBase {
720 def requestId = " test-request-id"
@@ -17,6 +30,53 @@ abstract class LambdaHandlerInstrumentationTest extends VersionedNamingTestBase
1730 null
1831 }
1932
33+ def ig
34+ def appSecStarted = false
35+ def capturedMethod = null
36+ def capturedPath = null
37+ def capturedHeaders = [:]
38+ def capturedBody = null
39+ def appSecEnded = false
40+
41+ def setup () {
42+ ig = AgentTracer . get(). getCallbackProvider(RequestContextSlot . APPSEC )
43+ ActiveSubsystems . APPSEC_ACTIVE = true
44+ appSecStarted = false
45+ capturedMethod = null
46+ capturedPath = null
47+ capturedHeaders = [:]
48+ capturedBody = null
49+ appSecEnded = false
50+ ig. registerCallback(EVENTS . requestStarted(), {
51+ appSecStarted = true
52+ new Flow.ResultFlow (new Object ())
53+ } as Supplier )
54+ ig. registerCallback(EVENTS . requestMethodUriRaw(), { RequestContext ctx , String method , URIDataAdapter uri ->
55+ capturedMethod = method
56+ capturedPath = uri. path()
57+ Flow.ResultFlow . empty()
58+ } as TriFunction )
59+ ig. registerCallback(EVENTS . requestHeader(), { RequestContext ctx , String name , String value ->
60+ capturedHeaders[name] = value
61+ } as TriConsumer )
62+ ig. registerCallback(EVENTS . requestHeaderDone(), { RequestContext ctx ->
63+ Flow.ResultFlow . empty()
64+ } as Function )
65+ ig. registerCallback(EVENTS . requestBodyProcessed(), { RequestContext ctx , Object body ->
66+ capturedBody = body
67+ Flow.ResultFlow . empty()
68+ } as BiFunction )
69+ ig. registerCallback(EVENTS . requestEnded(), { RequestContext ctx , Object spanInfo ->
70+ appSecEnded = true
71+ Flow.ResultFlow . empty()
72+ } as BiFunction )
73+ }
74+
75+ def cleanup () {
76+ ig. reset()
77+ ActiveSubsystems . APPSEC_ACTIVE = false
78+ }
79+
2080 def " test lambda streaming handler" () {
2181 when :
2282 def input = new ByteArrayInputStream (StandardCharsets . UTF_8 . encode(" Hello" ). array())
@@ -76,6 +136,114 @@ abstract class LambdaHandlerInstrumentationTest extends VersionedNamingTestBase
76136 }
77137 }
78138 }
139+
140+ def " appsec callbacks are invoked for API Gateway v1 event" () {
141+ given :
142+ def eventJson = """ {
143+ "path": "/api/users/123",
144+ "headers": {"content-type": "application/json", "x-forwarded-for": "203.0.113.1"},
145+ "body": "{\\ "key\\ ": \\ "value\\ "}",
146+ "requestContext": {
147+ "httpMethod": "GET",
148+ "requestId": "req-abc",
149+ "identity": {"sourceIp": "203.0.113.1"}
150+ }
151+ }"""
152+
153+ when :
154+ def input = new ByteArrayInputStream (eventJson. getBytes(StandardCharsets . UTF_8 ))
155+ def output = new ByteArrayOutputStream ()
156+ def ctx = Stub (Context ) { getAwsRequestId() >> requestId }
157+ new HandlerStreaming (). handleRequest(input, output, ctx)
158+
159+ then :
160+ appSecStarted
161+ capturedMethod == " GET"
162+ capturedPath == " /api/users/123"
163+ capturedHeaders[" content-type" ] == " application/json"
164+ capturedBody instanceof Map
165+ appSecEnded
166+ assertTraces(1 ) {
167+ trace(1 ) {
168+ span {
169+ operationName operation()
170+ spanType DDSpanTypes . SERVERLESS
171+ errored false
172+ }
173+ }
174+ }
175+ }
176+
177+ def " appsec callbacks are invoked for API Gateway v2 HTTP event" () {
178+ given :
179+ def eventJson = """ {
180+ "version": "2.0",
181+ "headers": {"content-type": "application/json", "accept": "application/json"},
182+ "cookies": ["session=abc123"],
183+ "body": "{\\ "key\\ ": \\ "value\\ "}",
184+ "requestContext": {
185+ "http": {
186+ "method": "POST",
187+ "path": "/api/items",
188+ "sourceIp": "198.51.100.1"
189+ },
190+ "domainName": "api.example.com"
191+ }
192+ }"""
193+
194+ when :
195+ def input = new ByteArrayInputStream (eventJson. getBytes(StandardCharsets . UTF_8 ))
196+ def output = new ByteArrayOutputStream ()
197+ def ctx = Stub (Context ) { getAwsRequestId() >> requestId }
198+ new HandlerStreaming (). handleRequest(input, output, ctx)
199+
200+ then :
201+ appSecStarted
202+ capturedMethod == " POST"
203+ capturedPath == " /api/items"
204+ capturedHeaders[" content-type" ] == " application/json"
205+ capturedHeaders[" cookie" ] == " session=abc123"
206+ capturedBody instanceof Map
207+ appSecEnded
208+ assertTraces(1 ) {
209+ trace(1 ) {
210+ span {
211+ operationName operation()
212+ spanType DDSpanTypes . SERVERLESS
213+ errored false
214+ }
215+ }
216+ }
217+ }
218+
219+ def " appsec callbacks are not invoked when appsec is disabled" () {
220+ given :
221+ ActiveSubsystems . APPSEC_ACTIVE = false
222+
223+ when :
224+ def eventJson = """ {
225+ "path": "/api/test",
226+ "requestContext": {"httpMethod": "GET", "requestId": "req-xyz"}
227+ }"""
228+ def input = new ByteArrayInputStream (eventJson. getBytes(StandardCharsets . UTF_8 ))
229+ def output = new ByteArrayOutputStream ()
230+ def ctx = Stub (Context ) { getAwsRequestId() >> requestId }
231+ new HandlerStreaming (). handleRequest(input, output, ctx)
232+
233+ then :
234+ ! appSecStarted
235+ capturedMethod == null
236+ ! appSecEnded
237+ assertTraces(1 ) {
238+ trace(1 ) {
239+ span {
240+ operationName operation()
241+ spanType DDSpanTypes . SERVERLESS
242+ errored false
243+ }
244+ }
245+ }
246+ }
79247}
80248
81249
0 commit comments