1+ import os
12import signal
23from unittest .mock import patch , MagicMock
34
@@ -79,9 +80,10 @@ def test_cf_ray_header(self):
7980
8081 # Exported spans should exist
8182 spans = memory_exporter .get_finished_spans ()
82- self .assertEqual (spans [0 ].resource .attributes .get ('service.name' ), 'marketing-api' )
83- self .assertEqual (len (spans ), 6 )
84- exported_span = spans [5 ]
83+ # Find the top-level Django HTTP span by name pattern
84+ http_spans = [s for s in spans if s .name .startswith ("GET " )]
85+ self .assertEqual (len (http_spans ), 1 )
86+ exported_span = http_spans [0 ]
8587 # Since no traceparent was injected, parent should be INVALID
8688 self .assertEqual (exported_span .parent , None )
8789 # Our CF-RAY header should be recorded in span attributes
@@ -111,9 +113,10 @@ def test_traceparent_and_baggage(self):
111113
112114 # Verify a span was exported
113115 spans = memory_exporter .get_finished_spans ()
114- self .assertEqual (len (spans ), 6 )
116+ http_spans = [s for s in spans if s .name .startswith ("GET " )]
117+ self .assertEqual (len (http_spans ), 1 )
118+ exported_span = http_spans [0 ]
115119 self .assertEqual (spans [0 ].resource .attributes .get ('service.name' ), 'marketing-api' )
116- exported_span = spans [5 ]
117120 # Check that the trace_id is the same as the injected traceparent
118121 self .assertEqual (format (exported_span .context .trace_id , "032x" ), trace_id )
119122
@@ -126,31 +129,92 @@ def test_traceparent_and_baggage(self):
126129 # And should also show up in span attributes (if your request_hook adds it)
127130 self .assertEqual (exported_span .attributes .get ("baggage.cf.ray_id" ), "xyz" )
128131
132+ @patch .dict (os .environ , {'DB_NAME' : 'my_app_db' })
129133 def test_mysql_span_has_db_name (self ):
130- """Simulate a MySQL query and assert db.name attribute is added ."""
134+ """mysql_hook sets db.system, db.name (env default), and db.statement on the span ."""
131135 with tracer .start_as_current_span ("mysql-test" ) as span :
132- span .set_attribute ("db.name" , "test_db" )
133- span .set_attribute ("db.statement" , "SELECT 1" )
136+ DjangoTelemetry .mysql_hook (span , MagicMock (), MagicMock (), "SELECT 1" , ())
134137
135138 spans = memory_exporter .get_finished_spans ()
136- self .assertEqual (len (spans ), 1 )
137- exported = spans [0 ]
139+ mysql_spans = [s for s in spans if s .name == "mysql-test" ]
140+ self .assertEqual (len (mysql_spans ), 1 )
141+ exported = mysql_spans [0 ]
138142 self .assertEqual (exported .resource .attributes .get ('service.name' ), 'marketing-api' )
139- self .assertEqual (exported .attributes .get ("db.name" ), "test_db" )
140- self .assertIn ("SELECT" , exported .attributes .get ("db.statement" ))
143+ self .assertEqual (exported .attributes .get ("db.system" ), "mysql" )
144+ self .assertEqual (exported .attributes .get ("db.name" ), "my_app_db" )
145+ self .assertEqual (exported .attributes .get ("db.statement" ), "SELECT 1" )
146+
147+ def test_mysql_hook_skips_non_recording_span (self ):
148+ """mysql_hook sets no attributes when span.is_recording() is False."""
149+ span = MagicMock ()
150+ span .is_recording .return_value = False
151+
152+ DjangoTelemetry .mysql_hook (span , MagicMock (), MagicMock (), "SELECT 1" , ())
153+
154+ span .set_attribute .assert_not_called ()
155+
156+ def test_mysql_hook_swallows_exceptions (self ):
157+ """mysql_hook does not propagate exceptions raised by set_attribute."""
158+ span = MagicMock ()
159+ span .is_recording .return_value = True
160+ span .set_attribute .side_effect = RuntimeError ("boom" )
161+
162+ exception_raised = False
163+ try :
164+ DjangoTelemetry .mysql_hook (span , MagicMock (), MagicMock (), "SELECT 1" , ())
165+ except RuntimeError :
166+ exception_raised = True
167+
168+ self .assertFalse (exception_raised , "mysql_hook should catch and not re-raise" )
169+ self .assertTrue (span .set_attribute .called , "mysql_hook should have attempted to set attributes" )
141170
142171 def test_redis_span_has_key (self ):
143- """Simulate a Redis command and assert db. redis.key is added ."""
172+ """redis_hook sets db.system, redis. command, and redis.key on the span ."""
144173 with tracer .start_as_current_span ("redis-test" ) as span :
145- span .set_attribute ("db.redis.command" , "GET" )
146- span .set_attribute ("db.redis.key" , "my_key" )
174+ DjangoTelemetry .redis_hook (span , MagicMock (), ("GET" , "my_key" ), {})
147175
148176 spans = memory_exporter .get_finished_spans ()
149- self .assertEqual (len (spans ), 1 )
150- exported = spans [0 ]
177+ redis_spans = [s for s in spans if s .name .startswith ("redis-test" )]
178+
179+ self .assertEqual (len (redis_spans ), 1 )
180+ exported = redis_spans [0 ]
151181 self .assertEqual (exported .resource .attributes .get ('service.name' ), 'marketing-api' )
152- self .assertEqual (exported .attributes .get ("db.redis.command" ), "GET" )
153- self .assertEqual (exported .attributes .get ("db.redis.key" ), "my_key" )
182+ self .assertEqual (exported .attributes .get ("db.system" ), "redis" )
183+ self .assertEqual (exported .attributes .get ("redis.command" ), "GET" )
184+ self .assertEqual (exported .attributes .get ("redis.key" ), "my_key" )
185+
186+ def test_redis_hook_no_key_in_args (self ):
187+ """redis_hook does not set redis.key when args contains only the command."""
188+ with tracer .start_as_current_span ("redis-nokey-test" ) as span :
189+ DjangoTelemetry .redis_hook (span , MagicMock (), ("DEL" ,), {})
190+
191+ exported = memory_exporter .get_finished_spans ()[0 ]
192+ self .assertEqual (exported .attributes .get ("redis.command" ), "DEL" )
193+ self .assertIsNone (exported .attributes .get ("redis.key" ))
194+
195+ def test_redis_hook_skips_non_recording_span (self ):
196+ """redis_hook sets no attributes when span.is_recording() is False."""
197+ span = MagicMock ()
198+ span .is_recording .return_value = False
199+
200+ DjangoTelemetry .redis_hook (span , MagicMock (), ("GET" , "key" ), {})
201+
202+ span .set_attribute .assert_not_called ()
203+
204+ def test_redis_hook_swallows_exceptions (self ):
205+ """redis_hook does not propagate exceptions raised by set_attribute."""
206+ span = MagicMock ()
207+ span .is_recording .return_value = True
208+ span .set_attribute .side_effect = RuntimeError ("boom" )
209+
210+ exception_raised = False
211+ try :
212+ DjangoTelemetry .redis_hook (span , MagicMock (), ("GET" , "key" ), {})
213+ except RuntimeError :
214+ exception_raised = True
215+
216+ self .assertFalse (exception_raised , "redis_hook should catch and not re-raise" )
217+ self .assertTrue (span .set_attribute .called , "redis_hook should have attempted to set attributes" )
154218
155219 def test_requests_span_has_custom_header (self ):
156220 """Simulate a requests span and assert custom header is captured."""
0 commit comments