@@ -381,3 +381,199 @@ def test_sql_dialect_keywords_max_length(self):
381381 keywords : List [str ] = _get_dialect_keywords ()
382382 for keyword in keywords :
383383 self .assertLessEqual (len (keyword ), MAX_KEYWORD_LENGTH )
384+
385+
386+ class TestOperationPaths (TestCase ):
387+ """Tests for OTEL_AWS_HTTP_OPERATION_PATHS and apply_operation_path_span_name."""
388+
389+ def setUp (self ):
390+ from amazon .opentelemetry .distro ._aws_span_processing_util import reset_operation_paths
391+
392+ reset_operation_paths ()
393+ self .span_mock = MagicMock ()
394+ self .span_mock .attributes = {}
395+ self .span_mock .name = "GET /api"
396+ self .span_mock ._name = "GET /api"
397+
398+ def tearDown (self ):
399+ from amazon .opentelemetry .distro ._aws_span_processing_util import reset_operation_paths
400+
401+ reset_operation_paths ()
402+
403+ # --- _segments_match tests ---
404+
405+ def test_segments_match_exact (self ):
406+ from amazon .opentelemetry .distro ._aws_span_processing_util import _segments_match
407+
408+ self .assertTrue (_segments_match ("/api/contests" .split ("/" ), "/api/contests" .split ("/" )))
409+
410+ def test_segments_match_no_match (self ):
411+ from amazon .opentelemetry .distro ._aws_span_processing_util import _segments_match
412+
413+ self .assertFalse (_segments_match ("/api/players" .split ("/" ), "/api/contests" .split ("/" )))
414+
415+ def test_segments_match_extra_url_segments_allowed (self ):
416+ from amazon .opentelemetry .distro ._aws_span_processing_util import _segments_match
417+
418+ self .assertTrue (_segments_match ("/api/contests/123/extra" .split ("/" ), "/api/contests" .split ("/" )))
419+
420+ def test_segments_match_pattern_longer_than_url (self ):
421+ from amazon .opentelemetry .distro ._aws_span_processing_util import _segments_match
422+
423+ self .assertFalse (_segments_match ("/api" .split ("/" ), "/api/contests/{id}" .split ("/" )))
424+
425+ def test_segments_match_curly_brace_wildcard (self ):
426+ from amazon .opentelemetry .distro ._aws_span_processing_util import _segments_match
427+
428+ self .assertTrue (_segments_match ("/api/contests/123" .split ("/" ), "/api/contests/{id}" .split ("/" )))
429+
430+ def test_segments_match_colon_param_wildcard (self ):
431+ from amazon .opentelemetry .distro ._aws_span_processing_util import _segments_match
432+
433+ self .assertTrue (_segments_match ("/api/users/42" .split ("/" ), "/api/users/:userId" .split ("/" )))
434+
435+ def test_segments_match_star_wildcard (self ):
436+ from amazon .opentelemetry .distro ._aws_span_processing_util import _segments_match
437+
438+ self .assertTrue (
439+ _segments_match ("/api/contests/123/leaderboard" .split ("/" ), "/api/contests/*/leaderboard" .split ("/" ))
440+ )
441+
442+ def test_segments_match_wildcard_does_not_match_empty (self ):
443+ from amazon .opentelemetry .distro ._aws_span_processing_util import _segments_match
444+
445+ self .assertFalse (_segments_match ("/api/contests/" .split ("/" ), "/api/contests/{id}" .split ("/" )))
446+
447+ # --- apply_operation_path_span_name tests ---
448+
449+ @patch .dict (
450+ os .environ ,
451+ {"OTEL_AWS_HTTP_OPERATION_PATHS" : "/api/contests/{id}/leaderboard, /api/contests/{id}, /api/contests" },
452+ )
453+ def test_apply_matches_url_path (self ):
454+ from amazon .opentelemetry .distro ._aws_span_processing_util import (
455+ apply_operation_path_span_name ,
456+ reset_operation_paths ,
457+ )
458+
459+ reset_operation_paths ()
460+ self .span_mock .attributes = {"url.path" : "/api/contests/123/leaderboard" , "http.request.method" : "GET" }
461+ result = apply_operation_path_span_name (self .span_mock )
462+ self .assertEqual (result ._name , "GET /api/contests/{id}/leaderboard" )
463+
464+ @patch .dict (os .environ , {"OTEL_AWS_HTTP_OPERATION_PATHS" : "/api/teams/{id}, /api/teams" })
465+ def test_apply_matches_http_target_with_query (self ):
466+ from amazon .opentelemetry .distro ._aws_span_processing_util import (
467+ apply_operation_path_span_name ,
468+ reset_operation_paths ,
469+ )
470+
471+ reset_operation_paths ()
472+ self .span_mock .attributes = {"http.target" : "/api/teams/5?include=roster" , "http.request.method" : "GET" }
473+ result = apply_operation_path_span_name (self .span_mock )
474+ self .assertEqual (result ._name , "GET /api/teams/{id}" )
475+
476+ @patch .dict (
477+ os .environ ,
478+ {"OTEL_AWS_HTTP_OPERATION_PATHS" : "/api/contests/{id}/leaderboard, /api/contests/{id}, /api/contests, /api" },
479+ )
480+ def test_apply_longest_match_wins (self ):
481+ from amazon .opentelemetry .distro ._aws_span_processing_util import (
482+ apply_operation_path_span_name ,
483+ reset_operation_paths ,
484+ )
485+
486+ reset_operation_paths ()
487+ self .span_mock .attributes = {"url.path" : "/api/contests/42" , "http.request.method" : "GET" }
488+ result = apply_operation_path_span_name (self .span_mock )
489+ self .assertEqual (result ._name , "GET /api/contests/{id}" )
490+
491+ @patch .dict (os .environ , {"OTEL_AWS_HTTP_OPERATION_PATHS" : "/api/contests/{id}" })
492+ def test_apply_no_match_returns_original (self ):
493+ from amazon .opentelemetry .distro ._aws_span_processing_util import (
494+ apply_operation_path_span_name ,
495+ reset_operation_paths ,
496+ )
497+
498+ reset_operation_paths ()
499+ self .span_mock .attributes = {"url.path" : "/unknown/path" , "http.request.method" : "GET" }
500+ result = apply_operation_path_span_name (self .span_mock )
501+ self .assertEqual (result ._name , "GET /api" ) # unchanged
502+
503+ def test_apply_empty_config_returns_original (self ):
504+ from amazon .opentelemetry .distro ._aws_span_processing_util import apply_operation_path_span_name
505+
506+ result = apply_operation_path_span_name (self .span_mock )
507+ self .assertIs (result , self .span_mock )
508+
509+ @patch .dict (os .environ , {"OTEL_AWS_HTTP_OPERATION_PATHS" : "/api/contests" })
510+ def test_apply_no_http_method (self ):
511+ from amazon .opentelemetry .distro ._aws_span_processing_util import (
512+ apply_operation_path_span_name ,
513+ reset_operation_paths ,
514+ )
515+
516+ reset_operation_paths ()
517+ self .span_mock .attributes = {"url.path" : "/api/contests" }
518+ result = apply_operation_path_span_name (self .span_mock )
519+ self .assertEqual (result ._name , "/api/contests" )
520+
521+ @patch .dict (os .environ , {"OTEL_AWS_HTTP_OPERATION_PATHS" : "/api/v1/{userId}, /api/{version}/user1" })
522+ def test_apply_same_length_first_config_wins (self ):
523+ from amazon .opentelemetry .distro ._aws_span_processing_util import (
524+ apply_operation_path_span_name ,
525+ reset_operation_paths ,
526+ )
527+
528+ reset_operation_paths ()
529+ self .span_mock .attributes = {"url.path" : "/api/v1/user1" , "http.request.method" : "GET" }
530+ result = apply_operation_path_span_name (self .span_mock )
531+ self .assertEqual (result ._name , "GET /api/v1/{userId}" )
532+
533+ @patch .dict (os .environ , {"OTEL_AWS_HTTP_OPERATION_PATHS" : "/api/contests" })
534+ def test_apply_trailing_slash_normalized (self ):
535+ from amazon .opentelemetry .distro ._aws_span_processing_util import (
536+ apply_operation_path_span_name ,
537+ reset_operation_paths ,
538+ )
539+
540+ reset_operation_paths ()
541+ self .span_mock .attributes = {"url.path" : "/api/contests/" , "http.request.method" : "GET" }
542+ result = apply_operation_path_span_name (self .span_mock )
543+ self .assertEqual (result ._name , "GET /api/contests" )
544+
545+ @patch .dict (os .environ , {"OTEL_AWS_HTTP_OPERATION_PATHS" : "/api/contests" })
546+ def test_apply_query_string_stripped (self ):
547+ from amazon .opentelemetry .distro ._aws_span_processing_util import (
548+ apply_operation_path_span_name ,
549+ reset_operation_paths ,
550+ )
551+
552+ reset_operation_paths ()
553+ self .span_mock .attributes = {"url.path" : "/api/contests?page=1&size=10" , "http.request.method" : "GET" }
554+ result = apply_operation_path_span_name (self .span_mock )
555+ self .assertEqual (result ._name , "GET /api/contests" )
556+
557+ @patch .dict (os .environ , {"OTEL_AWS_HTTP_OPERATION_PATHS" : "/api/users/:userId/stats" })
558+ def test_apply_colon_param_in_config (self ):
559+ from amazon .opentelemetry .distro ._aws_span_processing_util import (
560+ apply_operation_path_span_name ,
561+ reset_operation_paths ,
562+ )
563+
564+ reset_operation_paths ()
565+ self .span_mock .attributes = {"url.path" : "/api/users/42/stats" , "http.request.method" : "GET" }
566+ result = apply_operation_path_span_name (self .span_mock )
567+ self .assertEqual (result ._name , "GET /api/users/:userId/stats" )
568+
569+ @patch .dict (os .environ , {"OTEL_AWS_HTTP_OPERATION_PATHS" : "/api/*/users/*" })
570+ def test_apply_star_wildcard_in_config (self ):
571+ from amazon .opentelemetry .distro ._aws_span_processing_util import (
572+ apply_operation_path_span_name ,
573+ reset_operation_paths ,
574+ )
575+
576+ reset_operation_paths ()
577+ self .span_mock .attributes = {"url.path" : "/api/v2/users/42" , "http.request.method" : "GET" }
578+ result = apply_operation_path_span_name (self .span_mock )
579+ self .assertEqual (result ._name , "GET /api/*/users/*" )
0 commit comments