@@ -242,4 +242,103 @@ describe('API Security Sampler', () => {
242242 assert . strictEqual ( keepTraceStub . calledOnceWith ( span , 'asm' ) , true )
243243 } )
244244 } )
245+
246+ describe ( 'http.endpoint' , ( ) => {
247+ beforeEach ( ( ) => {
248+ apiSecuritySampler . configure ( { appsec : { apiSecurity : { enabled : true , sampleDelay : 30 } } } )
249+ } )
250+
251+ it ( 'should use http.endpoint when http.route is not available' , ( ) => {
252+ const spanWithEndpoint = {
253+ context : sinon . stub ( ) . returns ( {
254+ _sampling : { priority : AUTO_KEEP } ,
255+ _tags : { 'http.endpoint' : '/api/users' } ,
256+ } ) ,
257+ }
258+ webStub . root . returns ( spanWithEndpoint )
259+ webStub . getContext . returns ( { paths : [ ] , span : spanWithEndpoint } )
260+
261+ const key = apiSecuritySampler . computeKey ( req , res )
262+ assert . equal ( key , 'GET/api/users200' )
263+ } )
264+
265+ it ( 'should not use http.endpoint for 404 status codes' , ( ) => {
266+ const res404 = { statusCode : 404 }
267+ const spanWithEndpoint = {
268+ context : sinon . stub ( ) . returns ( {
269+ _sampling : { priority : AUTO_KEEP } ,
270+ _tags : { 'http.endpoint' : '/api/users' } ,
271+ } ) ,
272+ }
273+ webStub . root . returns ( spanWithEndpoint )
274+ webStub . getContext . returns ( { paths : [ ] , span : spanWithEndpoint } )
275+
276+ const key = apiSecuritySampler . computeKey ( req , res404 )
277+ assert . equal ( key , 'GET404' )
278+ } )
279+
280+ it ( 'should prefer http.route over http.endpoint when both are available' , ( ) => {
281+ const spanWithBoth = {
282+ context : sinon . stub ( ) . returns ( {
283+ _sampling : { priority : AUTO_KEEP } ,
284+ _tags : { 'http.endpoint' : '/api/users' } ,
285+ } ) ,
286+ }
287+ webStub . root . returns ( spanWithBoth )
288+ webStub . getContext . returns ( { paths : [ '/users/:id' ] , span : spanWithBoth } )
289+
290+ const key = apiSecuritySampler . computeKey ( req , res )
291+ assert . equal ( key , 'GET/users/:id200' )
292+ } )
293+
294+ it ( 'should handle missing http.endpoint gracefully' , ( ) => {
295+ const spanWithoutEndpoint = {
296+ context : sinon . stub ( ) . returns ( {
297+ _sampling : { priority : AUTO_KEEP } ,
298+ _tags : { } ,
299+ } ) ,
300+ }
301+ webStub . root . returns ( spanWithoutEndpoint )
302+ webStub . getContext . returns ( { paths : [ ] , span : spanWithoutEndpoint } )
303+
304+ const key = apiSecuritySampler . computeKey ( req , res )
305+ assert . equal ( key , 'GET200' )
306+ } )
307+
308+ it ( 'should handle missing span gracefully' , ( ) => {
309+ webStub . getContext . returns ( { paths : [ ] , span : null } )
310+
311+ const key = apiSecuritySampler . computeKey ( req , res )
312+ assert . equal ( key , 'GET200' )
313+ } )
314+
315+ it ( 'should sample different endpoints separately' , ( ) => {
316+ const span1 = {
317+ context : sinon . stub ( ) . returns ( {
318+ _sampling : { priority : AUTO_KEEP } ,
319+ _tags : { 'http.endpoint' : '/api/users' } ,
320+ } ) ,
321+ }
322+ const span2 = {
323+ context : sinon . stub ( ) . returns ( {
324+ _sampling : { priority : AUTO_KEEP } ,
325+ _tags : { 'http.endpoint' : '/api/products' } ,
326+ } ) ,
327+ }
328+
329+ webStub . root . returns ( span1 )
330+ webStub . getContext . returns ( { paths : [ ] , span : span1 } )
331+ assert . ok ( apiSecuritySampler . sampleRequest ( req , res , true ) )
332+
333+ webStub . root . returns ( span2 )
334+ webStub . getContext . returns ( { paths : [ ] , span : span2 } )
335+ assert . ok ( apiSecuritySampler . sampleRequest ( req , res , true ) )
336+
337+ const key1 = apiSecuritySampler . computeKey ( req , res )
338+ webStub . getContext . returns ( { paths : [ ] , span : span1 } )
339+ const key2 = apiSecuritySampler . computeKey ( req , res )
340+
341+ assert . notEqual ( key1 , key2 )
342+ } )
343+ } )
245344} )
0 commit comments