@@ -81,6 +81,11 @@ def _validate_spans(self):
8181 if not self .probe_spans [expected_trace ]:
8282 raise ValueError (f"No spans found for trace { expected_trace } " )
8383
84+ def _assert_keys_present (self , data : dict , required_keys : list ):
85+ missing = [key for key in required_keys if key not in data ]
86+ if missing :
87+ raise KeyError (f"Missing required keys: { ', ' .join (missing )} " )
88+
8489
8590@features .debugger_method_probe
8691@scenarios .debugger_probes_snapshot
@@ -209,6 +214,12 @@ def _validate_snapshots(self):
209214class Test_Debugger_Line_Probe_Snaphots (BaseDebuggerProbeSnaphotTest ):
210215 """Tests for line-level probe snapshots"""
211216
217+ # Default snapshot capture limits
218+ DEFAULT_MAX_REFERENCE_DEPTH = 3
219+ DEFAULT_MAX_COLLECTION_SIZE = 100
220+ DEFAULT_MAX_FIELD_COUNT = 20
221+ DEFAULT_MAX_LENGTH = 255
222+
212223 ### log probe ###
213224 def setup_log_line_snapshot (self ):
214225 self ._setup ("probe_snapshot_log_line" , "/debugger/log" , "log" , lines = None )
@@ -218,6 +229,124 @@ def test_log_line_snapshot(self):
218229 self ._assert ()
219230 self ._validate_snapshots ()
220231
232+ def setup_default_capture_values (self ):
233+ """Setup test with endpoint that generates data exceeding default capture limits"""
234+ test_depth = self .DEFAULT_MAX_REFERENCE_DEPTH + 7
235+ test_fields = self .DEFAULT_MAX_FIELD_COUNT + 30
236+ test_collection_size = self .DEFAULT_MAX_COLLECTION_SIZE + 100
237+ test_string_length = self .DEFAULT_MAX_LENGTH + 245
238+
239+ self ._setup (
240+ "probe_snapshot_default_capture_limits" ,
241+ f"/debugger/snapshot/limits?"
242+ f"depth={ test_depth } &"
243+ f"fields={ test_fields } &"
244+ f"collectionSize={ test_collection_size } &"
245+ f"stringLength={ test_string_length } " ,
246+ "log" ,
247+ lines = None ,
248+ )
249+
250+ @missing_feature (context .library != "nodejs" , reason = "Endpoint only implemented for Node.js" , force_skip = True )
251+ def test_default_capture_values (self ):
252+ """Test that the tracer uses default capture values when capture property is omitted"""
253+ self ._assert ()
254+ self ._validate_snapshots ()
255+
256+ for probe_id in self .probe_ids :
257+ if probe_id not in self .probe_snapshots :
258+ raise ValueError (f"Snapshot { probe_id } was not received." )
259+
260+ snapshots = self .probe_snapshots [probe_id ]
261+ if not snapshots :
262+ raise ValueError (f"No snapshots found for probe { probe_id } " )
263+
264+ snapshot = snapshots [0 ]
265+ debugger_snapshot = snapshot .get ("debugger" , {}).get ("snapshot" ) or snapshot .get ("debugger.snapshot" )
266+
267+ if not debugger_snapshot :
268+ raise ValueError (f"Snapshot data not found in expected format for probe { probe_id } " )
269+ if "captures" not in debugger_snapshot :
270+ raise ValueError (f"No captures found in snapshot for probe { probe_id } " )
271+
272+ captures = debugger_snapshot ["captures" ]
273+ if "lines" in captures :
274+ lines = captures ["lines" ]
275+ if isinstance (lines , dict ) and len (lines ) == 1 :
276+ line_key = next (iter (lines ))
277+ line_data = lines [line_key ]
278+ else :
279+ raise ValueError (f"Expected 'lines' to be a dict with a single key, got: { len (lines )} " )
280+
281+ if line_data and "locals" in line_data :
282+ locals_data = line_data ["locals" ]
283+ self ._assert_keys_present (
284+ locals_data , ["manyFields" , "largeCollection" , "deepObject" , "longString" ]
285+ )
286+
287+ # Validate maxFieldCount
288+ many_fields = locals_data ["manyFields" ]
289+ assert many_fields .get ("notCapturedReason" ) == "fieldCount" , (
290+ f"manyFields should have notCapturedReason='fieldCount' to indicate "
291+ f"maxFieldCount limit was applied. Got: { many_fields .get ('notCapturedReason' )} "
292+ )
293+
294+ actual_size = many_fields .get ("size" )
295+ expected_field_count = self .DEFAULT_MAX_FIELD_COUNT + 30
296+ assert (
297+ actual_size == expected_field_count
298+ ), f"manyFields should report size={ expected_field_count } , got: { actual_size } "
299+
300+ captured_count = len (many_fields ["fields" ])
301+ assert captured_count == self .DEFAULT_MAX_FIELD_COUNT , (
302+ f"manyFields should have exactly { self .DEFAULT_MAX_FIELD_COUNT } fields captured "
303+ f"(maxFieldCount default), got: { captured_count } "
304+ )
305+
306+ # Validate maxCollectionSize
307+ large_collection = locals_data ["largeCollection" ]
308+ assert large_collection .get ("notCapturedReason" ) == "collectionSize" , (
309+ f"largeCollection should have notCapturedReason='collectionSize' to indicate "
310+ f"maxCollectionSize limit was applied. Got: { large_collection .get ('notCapturedReason' )} "
311+ )
312+
313+ actual_size = large_collection .get ("size" )
314+ expected_collection_size = self .DEFAULT_MAX_COLLECTION_SIZE + 100
315+ assert (
316+ actual_size == expected_collection_size
317+ ), f"largeCollection should report size={ expected_collection_size } , got: { actual_size } "
318+
319+ captured_count = len (large_collection ["elements" ])
320+ assert captured_count == self .DEFAULT_MAX_COLLECTION_SIZE , (
321+ f"largeCollection should have exactly { self .DEFAULT_MAX_COLLECTION_SIZE } elements "
322+ f"captured (maxCollectionSize default), got: { captured_count } "
323+ )
324+
325+ # Validate maxReferenceDepth
326+ self ._assert_max_depth (locals_data ["deepObject" ], max_depth = self .DEFAULT_MAX_REFERENCE_DEPTH )
327+
328+ # Validate maxLength
329+ string_value = locals_data ["longString" ]["value" ]
330+ assert len (string_value ) <= self .DEFAULT_MAX_LENGTH , (
331+ f"longString has length { len (string_value )} , exceeds maxLength default "
332+ f"({ self .DEFAULT_MAX_LENGTH } ). String should be truncated."
333+ )
334+
335+ def _assert_max_depth (self , obj : dict , max_depth : int , current_depth : int = 1 ) -> None :
336+ """Asserts that nested objects are truncated at maxReferenceDepth with notCapturedReason='depth'"""
337+ assert "fields" in obj , f"Expected 'fields' to be present in the object, got: { list (obj .keys ())} "
338+ fields = obj ["fields" ]
339+ assert isinstance (fields , dict ), f"Expected 'fields' to be a dict, got: { type (fields )} "
340+ assert "nested" in fields , f"Expected 'nested' to be present in the 'fields' object, got: { list (fields .keys ())} "
341+ nested = fields ["nested" ]
342+ assert isinstance (nested , dict ), f"Expected 'nested' to be a dict, got: { type (nested )} "
343+ if current_depth == max_depth :
344+ assert (
345+ nested .get ("notCapturedReason" ) == "depth"
346+ ), f"Expected 'notCapturedReason' to be 'depth', got: { nested .get ('notCapturedReason' )} "
347+ else :
348+ self ._assert_max_depth (nested , max_depth , current_depth + 1 )
349+
221350 def setup_log_line_snapshot_debug_track (self ):
222351 self .use_debugger_endpoint = True
223352 self ._setup ("probe_snapshot_log_line" , "/debugger/log" , "log" , lines = None )
0 commit comments