@cdklabs/cdk-appmod-catalog-blueprints 1.4.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/.jsii +2579 -194
  2. package/lib/document-processing/adapter/adapter.d.ts +4 -2
  3. package/lib/document-processing/adapter/adapter.js +1 -1
  4. package/lib/document-processing/adapter/queued-s3-adapter.d.ts +9 -2
  5. package/lib/document-processing/adapter/queued-s3-adapter.js +29 -15
  6. package/lib/document-processing/agentic-document-processing.d.ts +4 -0
  7. package/lib/document-processing/agentic-document-processing.js +20 -10
  8. package/lib/document-processing/base-document-processing.d.ts +54 -2
  9. package/lib/document-processing/base-document-processing.js +136 -82
  10. package/lib/document-processing/bedrock-document-processing.d.ts +202 -2
  11. package/lib/document-processing/bedrock-document-processing.js +717 -77
  12. package/lib/document-processing/chunking-config.d.ts +614 -0
  13. package/lib/document-processing/chunking-config.js +5 -0
  14. package/lib/document-processing/default-document-processing-config.js +1 -1
  15. package/lib/document-processing/index.d.ts +1 -0
  16. package/lib/document-processing/index.js +2 -1
  17. package/lib/document-processing/resources/aggregation/handler.py +567 -0
  18. package/lib/document-processing/resources/aggregation/requirements.txt +7 -0
  19. package/lib/document-processing/resources/aggregation/test_handler.py +362 -0
  20. package/lib/document-processing/resources/cleanup/handler.py +276 -0
  21. package/lib/document-processing/resources/cleanup/requirements.txt +5 -0
  22. package/lib/document-processing/resources/cleanup/test_handler.py +436 -0
  23. package/lib/document-processing/resources/default-bedrock-invoke/index.py +85 -3
  24. package/lib/document-processing/resources/default-bedrock-invoke/test_index.py +622 -0
  25. package/lib/document-processing/resources/pdf-chunking/README.md +313 -0
  26. package/lib/document-processing/resources/pdf-chunking/chunking_strategies.py +460 -0
  27. package/lib/document-processing/resources/pdf-chunking/error_handling.py +491 -0
  28. package/lib/document-processing/resources/pdf-chunking/handler.py +958 -0
  29. package/lib/document-processing/resources/pdf-chunking/metrics.py +435 -0
  30. package/lib/document-processing/resources/pdf-chunking/requirements.txt +3 -0
  31. package/lib/document-processing/resources/pdf-chunking/strategy_selection.py +420 -0
  32. package/lib/document-processing/resources/pdf-chunking/structured_logging.py +457 -0
  33. package/lib/document-processing/resources/pdf-chunking/test_chunking_strategies.py +353 -0
  34. package/lib/document-processing/resources/pdf-chunking/test_error_handling.py +487 -0
  35. package/lib/document-processing/resources/pdf-chunking/test_handler.py +609 -0
  36. package/lib/document-processing/resources/pdf-chunking/test_integration.py +694 -0
  37. package/lib/document-processing/resources/pdf-chunking/test_metrics.py +532 -0
  38. package/lib/document-processing/resources/pdf-chunking/test_strategy_selection.py +471 -0
  39. package/lib/document-processing/resources/pdf-chunking/test_structured_logging.py +449 -0
  40. package/lib/document-processing/resources/pdf-chunking/test_token_estimation.py +374 -0
  41. package/lib/document-processing/resources/pdf-chunking/token_estimation.py +189 -0
  42. package/lib/document-processing/tests/agentic-document-processing-nag.test.js +4 -3
  43. package/lib/document-processing/tests/agentic-document-processing.test.js +488 -4
  44. package/lib/document-processing/tests/base-document-processing-nag.test.js +9 -2
  45. package/lib/document-processing/tests/base-document-processing-schema.test.d.ts +1 -0
  46. package/lib/document-processing/tests/base-document-processing-schema.test.js +337 -0
  47. package/lib/document-processing/tests/base-document-processing.test.js +114 -8
  48. package/lib/document-processing/tests/bedrock-document-processing-chunking-nag.test.d.ts +1 -0
  49. package/lib/document-processing/tests/bedrock-document-processing-chunking-nag.test.js +382 -0
  50. package/lib/document-processing/tests/bedrock-document-processing-nag.test.js +4 -3
  51. package/lib/document-processing/tests/bedrock-document-processing-security.test.d.ts +1 -0
  52. package/lib/document-processing/tests/bedrock-document-processing-security.test.js +389 -0
  53. package/lib/document-processing/tests/bedrock-document-processing.test.js +808 -8
  54. package/lib/document-processing/tests/chunking-config.test.d.ts +1 -0
  55. package/lib/document-processing/tests/chunking-config.test.js +238 -0
  56. package/lib/document-processing/tests/queued-s3-adapter-nag.test.js +9 -2
  57. package/lib/document-processing/tests/queued-s3-adapter.test.js +17 -6
  58. package/lib/framework/agents/base-agent.js +1 -1
  59. package/lib/framework/agents/batch-agent.js +1 -1
  60. package/lib/framework/agents/default-agent-config.js +1 -1
  61. package/lib/framework/bedrock/bedrock.js +1 -1
  62. package/lib/framework/custom-resource/default-runtimes.js +1 -1
  63. package/lib/framework/foundation/access-log.js +1 -1
  64. package/lib/framework/foundation/eventbridge-broker.js +1 -1
  65. package/lib/framework/foundation/network.d.ts +4 -2
  66. package/lib/framework/foundation/network.js +52 -41
  67. package/lib/framework/tests/access-log.test.js +5 -2
  68. package/lib/framework/tests/batch-agent.test.js +5 -2
  69. package/lib/framework/tests/bedrock.test.js +5 -2
  70. package/lib/framework/tests/eventbridge-broker.test.js +5 -2
  71. package/lib/framework/tests/framework-nag.test.js +26 -7
  72. package/lib/framework/tests/network.test.js +30 -2
  73. package/lib/tsconfig.tsbuildinfo +1 -1
  74. package/lib/utilities/data-loader.js +1 -1
  75. package/lib/utilities/lambda-iam-utils.js +1 -1
  76. package/lib/utilities/observability/cloudfront-distribution-observability-property-injector.js +1 -1
  77. package/lib/utilities/observability/default-observability-config.js +1 -1
  78. package/lib/utilities/observability/lambda-observability-property-injector.js +1 -1
  79. package/lib/utilities/observability/log-group-data-protection-utils.js +1 -1
  80. package/lib/utilities/observability/powertools-config.d.ts +10 -1
  81. package/lib/utilities/observability/powertools-config.js +19 -3
  82. package/lib/utilities/observability/state-machine-observability-property-injector.js +1 -1
  83. package/lib/utilities/test-utils.d.ts +43 -0
  84. package/lib/utilities/test-utils.js +56 -0
  85. package/lib/utilities/tests/data-loader-nag.test.js +3 -2
  86. package/lib/utilities/tests/data-loader.test.js +3 -2
  87. package/lib/webapp/frontend-construct.js +1 -1
  88. package/lib/webapp/tests/frontend-construct-nag.test.js +3 -2
  89. package/lib/webapp/tests/frontend-construct.test.js +3 -2
  90. package/package.json +6 -5
  91. package/lib/document-processing/resources/default-error-handler/index.js +0 -46
  92. package/lib/document-processing/resources/default-pdf-processor/index.js +0 -46
  93. package/lib/document-processing/resources/default-pdf-validator/index.js +0 -36
@@ -0,0 +1,532 @@
1
+ """
2
+ Unit tests for CloudWatch metrics module.
3
+
4
+ Tests cover:
5
+ - ChunkingOperations metric emission
6
+ - ChunkCount metric emission
7
+ - TokensPerChunk metric emission
8
+ - ChunkProcessingTime metric emission
9
+ - ChunkFailureRate metric emission
10
+ - AggregationTime metric emission
11
+ - StrategyUsage metric emission
12
+ - Convenience function emit_chunking_metrics
13
+ - Timed operation decorator
14
+ - Metrics gating via ENABLE_METRICS environment variable
15
+
16
+ Requirements: 7.4
17
+ """
18
+
19
+ import unittest
20
+ from unittest.mock import Mock, patch, MagicMock
21
+ import os
22
+ import time
23
+
24
+ # Set ENABLE_METRICS before importing metrics module
25
+ os.environ['ENABLE_METRICS'] = 'true'
26
+
27
+ from metrics import (
28
+ emit_chunking_operation,
29
+ emit_chunk_count,
30
+ emit_tokens_per_chunk,
31
+ emit_chunk_processing_time,
32
+ emit_chunk_failure_rate,
33
+ emit_aggregation_time,
34
+ emit_strategy_usage,
35
+ emit_chunking_metrics,
36
+ timed_operation,
37
+ get_metrics,
38
+ _is_metrics_enabled
39
+ )
40
+
41
+
42
+ class TestEmitChunkingOperation(unittest.TestCase):
43
+ """Test cases for emit_chunking_operation function."""
44
+
45
+ @patch('metrics.metrics')
46
+ def test_emit_chunking_operation_with_chunking(self, mock_metrics):
47
+ """Test emitting chunking operation metric when chunking is required."""
48
+ emit_chunking_operation(
49
+ strategy='hybrid',
50
+ requires_chunking=True,
51
+ document_id='doc-123'
52
+ )
53
+
54
+ # Verify dimensions were added
55
+ mock_metrics.add_dimension.assert_any_call(name="Strategy", value="hybrid")
56
+ mock_metrics.add_dimension.assert_any_call(name="RequiresChunking", value="true")
57
+
58
+ # Verify metric was added
59
+ mock_metrics.add_metric.assert_called_once()
60
+ call_args = mock_metrics.add_metric.call_args
61
+ self.assertEqual(call_args[1]['name'], 'ChunkingOperations')
62
+ self.assertEqual(call_args[1]['value'], 1)
63
+
64
+ @patch('metrics.metrics')
65
+ def test_emit_chunking_operation_without_chunking(self, mock_metrics):
66
+ """Test emitting chunking operation metric when chunking is not required."""
67
+ emit_chunking_operation(
68
+ strategy='fixed-pages',
69
+ requires_chunking=False,
70
+ document_id='doc-456'
71
+ )
72
+
73
+ mock_metrics.add_dimension.assert_any_call(name="RequiresChunking", value="false")
74
+
75
+ @patch('metrics.metrics')
76
+ @patch('metrics.logger')
77
+ def test_emit_chunking_operation_handles_error(self, mock_logger, mock_metrics):
78
+ """Test that errors are logged but not raised."""
79
+ mock_metrics.add_metric.side_effect = Exception("Test error")
80
+
81
+ # Should not raise
82
+ emit_chunking_operation(
83
+ strategy='hybrid',
84
+ requires_chunking=True,
85
+ document_id='doc-123'
86
+ )
87
+
88
+ mock_logger.warning.assert_called_once()
89
+
90
+
91
+ class TestEmitChunkCount(unittest.TestCase):
92
+ """Test cases for emit_chunk_count function."""
93
+
94
+ @patch('metrics.metrics')
95
+ def test_emit_chunk_count(self, mock_metrics):
96
+ """Test emitting chunk count metric."""
97
+ emit_chunk_count(
98
+ chunk_count=5,
99
+ strategy='token-based',
100
+ document_id='doc-123'
101
+ )
102
+
103
+ mock_metrics.add_dimension.assert_called_with(name="Strategy", value="token-based")
104
+ mock_metrics.add_metric.assert_called_once()
105
+ call_args = mock_metrics.add_metric.call_args
106
+ self.assertEqual(call_args[1]['name'], 'ChunkCount')
107
+ self.assertEqual(call_args[1]['value'], 5)
108
+
109
+ @patch('metrics.metrics')
110
+ @patch('metrics.logger')
111
+ def test_emit_chunk_count_handles_error(self, mock_logger, mock_metrics):
112
+ """Test that errors are logged but not raised."""
113
+ mock_metrics.add_metric.side_effect = Exception("Test error")
114
+
115
+ emit_chunk_count(chunk_count=5, strategy='hybrid', document_id='doc-123')
116
+
117
+ mock_logger.warning.assert_called_once()
118
+
119
+
120
+ class TestEmitTokensPerChunk(unittest.TestCase):
121
+ """Test cases for emit_tokens_per_chunk function."""
122
+
123
+ @patch('metrics.metrics')
124
+ def test_emit_tokens_per_chunk(self, mock_metrics):
125
+ """Test emitting tokens per chunk metrics."""
126
+ tokens = [1000, 1500, 2000, 1200, 1800]
127
+
128
+ emit_tokens_per_chunk(
129
+ tokens_per_chunk=tokens,
130
+ strategy='hybrid',
131
+ document_id='doc-123'
132
+ )
133
+
134
+ # Should emit avg, p99, and max metrics
135
+ self.assertEqual(mock_metrics.add_metric.call_count, 3)
136
+
137
+ # Verify metric names
138
+ metric_names = [call[1]['name'] for call in mock_metrics.add_metric.call_args_list]
139
+ self.assertIn('TokensPerChunkAvg', metric_names)
140
+ self.assertIn('TokensPerChunkP99', metric_names)
141
+ self.assertIn('TokensPerChunkMax', metric_names)
142
+
143
+ @patch('metrics.metrics')
144
+ def test_emit_tokens_per_chunk_empty_list(self, mock_metrics):
145
+ """Test that empty list does not emit metrics."""
146
+ emit_tokens_per_chunk(
147
+ tokens_per_chunk=[],
148
+ strategy='hybrid',
149
+ document_id='doc-123'
150
+ )
151
+
152
+ mock_metrics.add_metric.assert_not_called()
153
+
154
+ @patch('metrics.metrics')
155
+ def test_emit_tokens_per_chunk_single_value(self, mock_metrics):
156
+ """Test with single token value."""
157
+ emit_tokens_per_chunk(
158
+ tokens_per_chunk=[1500],
159
+ strategy='hybrid',
160
+ document_id='doc-123'
161
+ )
162
+
163
+ # Should still emit all three metrics
164
+ self.assertEqual(mock_metrics.add_metric.call_count, 3)
165
+
166
+
167
+ class TestEmitChunkProcessingTime(unittest.TestCase):
168
+ """Test cases for emit_chunk_processing_time function."""
169
+
170
+ @patch('metrics.metrics')
171
+ def test_emit_chunk_processing_time(self, mock_metrics):
172
+ """Test emitting chunk processing time metric."""
173
+ emit_chunk_processing_time(
174
+ processing_time_ms=1500.5,
175
+ processing_mode='parallel',
176
+ document_id='doc-123'
177
+ )
178
+
179
+ mock_metrics.add_dimension.assert_called_with(name="ProcessingMode", value="parallel")
180
+ mock_metrics.add_metric.assert_called_once()
181
+ call_args = mock_metrics.add_metric.call_args
182
+ self.assertEqual(call_args[1]['name'], 'ChunkProcessingTime')
183
+ self.assertEqual(call_args[1]['value'], 1500.5)
184
+
185
+ @patch('metrics.metrics')
186
+ def test_emit_chunk_processing_time_sequential(self, mock_metrics):
187
+ """Test emitting processing time with sequential mode."""
188
+ emit_chunk_processing_time(
189
+ processing_time_ms=2000.0,
190
+ processing_mode='sequential',
191
+ document_id='doc-123'
192
+ )
193
+
194
+ mock_metrics.add_dimension.assert_called_with(name="ProcessingMode", value="sequential")
195
+
196
+
197
+ class TestEmitChunkFailureRate(unittest.TestCase):
198
+ """Test cases for emit_chunk_failure_rate function."""
199
+
200
+ @patch('metrics.metrics')
201
+ def test_emit_chunk_failure_rate(self, mock_metrics):
202
+ """Test emitting chunk failure rate metric."""
203
+ emit_chunk_failure_rate(
204
+ total_chunks=10,
205
+ failed_chunks=2,
206
+ document_id='doc-123'
207
+ )
208
+
209
+ # Should emit failure rate, failed chunks, and total chunks
210
+ self.assertEqual(mock_metrics.add_metric.call_count, 3)
211
+
212
+ # Verify failure rate calculation (20%)
213
+ call_args_list = mock_metrics.add_metric.call_args_list
214
+ failure_rate_call = [c for c in call_args_list if c[1]['name'] == 'ChunkFailureRate'][0]
215
+ self.assertEqual(failure_rate_call[1]['value'], 20.0)
216
+
217
+ @patch('metrics.metrics')
218
+ def test_emit_chunk_failure_rate_zero_total(self, mock_metrics):
219
+ """Test that zero total chunks does not emit metrics."""
220
+ emit_chunk_failure_rate(
221
+ total_chunks=0,
222
+ failed_chunks=0,
223
+ document_id='doc-123'
224
+ )
225
+
226
+ mock_metrics.add_metric.assert_not_called()
227
+
228
+ @patch('metrics.metrics')
229
+ def test_emit_chunk_failure_rate_no_failures(self, mock_metrics):
230
+ """Test with no failed chunks."""
231
+ emit_chunk_failure_rate(
232
+ total_chunks=10,
233
+ failed_chunks=0,
234
+ document_id='doc-123'
235
+ )
236
+
237
+ call_args_list = mock_metrics.add_metric.call_args_list
238
+ failure_rate_call = [c for c in call_args_list if c[1]['name'] == 'ChunkFailureRate'][0]
239
+ self.assertEqual(failure_rate_call[1]['value'], 0.0)
240
+
241
+
242
+ class TestEmitAggregationTime(unittest.TestCase):
243
+ """Test cases for emit_aggregation_time function."""
244
+
245
+ @patch('metrics.metrics')
246
+ def test_emit_aggregation_time(self, mock_metrics):
247
+ """Test emitting aggregation time metric."""
248
+ emit_aggregation_time(
249
+ aggregation_time_ms=500.25,
250
+ document_id='doc-123'
251
+ )
252
+
253
+ mock_metrics.add_metric.assert_called_once()
254
+ call_args = mock_metrics.add_metric.call_args
255
+ self.assertEqual(call_args[1]['name'], 'AggregationTime')
256
+ self.assertEqual(call_args[1]['value'], 500.25)
257
+
258
+ @patch('metrics.metrics')
259
+ @patch('metrics.logger')
260
+ def test_emit_aggregation_time_handles_error(self, mock_logger, mock_metrics):
261
+ """Test that errors are logged but not raised."""
262
+ mock_metrics.add_metric.side_effect = Exception("Test error")
263
+
264
+ emit_aggregation_time(aggregation_time_ms=500.0, document_id='doc-123')
265
+
266
+ mock_logger.warning.assert_called_once()
267
+
268
+
269
+ class TestEmitStrategyUsage(unittest.TestCase):
270
+ """Test cases for emit_strategy_usage function."""
271
+
272
+ @patch('metrics.metrics')
273
+ def test_emit_strategy_usage_hybrid(self, mock_metrics):
274
+ """Test emitting strategy usage metric for hybrid strategy."""
275
+ emit_strategy_usage(
276
+ strategy='hybrid',
277
+ document_id='doc-123'
278
+ )
279
+
280
+ mock_metrics.add_dimension.assert_called_with(name="Strategy", value="hybrid")
281
+ mock_metrics.add_metric.assert_called_once()
282
+ call_args = mock_metrics.add_metric.call_args
283
+ self.assertEqual(call_args[1]['name'], 'StrategyUsage')
284
+ self.assertEqual(call_args[1]['value'], 1)
285
+
286
+ @patch('metrics.metrics')
287
+ def test_emit_strategy_usage_fixed_pages(self, mock_metrics):
288
+ """Test emitting strategy usage metric for fixed-pages strategy."""
289
+ emit_strategy_usage(
290
+ strategy='fixed-pages',
291
+ document_id='doc-123'
292
+ )
293
+
294
+ mock_metrics.add_dimension.assert_called_with(name="Strategy", value="fixed-pages")
295
+
296
+ @patch('metrics.metrics')
297
+ def test_emit_strategy_usage_token_based(self, mock_metrics):
298
+ """Test emitting strategy usage metric for token-based strategy."""
299
+ emit_strategy_usage(
300
+ strategy='token-based',
301
+ document_id='doc-123'
302
+ )
303
+
304
+ mock_metrics.add_dimension.assert_called_with(name="Strategy", value="token-based")
305
+
306
+
307
+ class TestEmitChunkingMetrics(unittest.TestCase):
308
+ """Test cases for emit_chunking_metrics convenience function."""
309
+
310
+ @patch('metrics.emit_strategy_usage')
311
+ @patch('metrics.emit_chunking_operation')
312
+ def test_emit_chunking_metrics_no_chunking(self, mock_operation, mock_strategy):
313
+ """Test convenience function when chunking is not required."""
314
+ emit_chunking_metrics(
315
+ document_id='doc-123',
316
+ strategy='hybrid',
317
+ requires_chunking=False
318
+ )
319
+
320
+ mock_operation.assert_called_once_with('hybrid', False, 'doc-123')
321
+ mock_strategy.assert_called_once_with('hybrid', 'doc-123')
322
+
323
+ @patch('metrics.emit_chunk_processing_time')
324
+ @patch('metrics.emit_tokens_per_chunk')
325
+ @patch('metrics.emit_chunk_count')
326
+ @patch('metrics.emit_strategy_usage')
327
+ @patch('metrics.emit_chunking_operation')
328
+ def test_emit_chunking_metrics_with_chunking(
329
+ self, mock_operation, mock_strategy, mock_count, mock_tokens, mock_time
330
+ ):
331
+ """Test convenience function when chunking is required."""
332
+ emit_chunking_metrics(
333
+ document_id='doc-123',
334
+ strategy='hybrid',
335
+ requires_chunking=True,
336
+ chunk_count=5,
337
+ tokens_per_chunk=[1000, 1500, 2000],
338
+ processing_time_ms=1500.0,
339
+ processing_mode='parallel'
340
+ )
341
+
342
+ mock_operation.assert_called_once()
343
+ mock_strategy.assert_called_once()
344
+ mock_count.assert_called_once_with(5, 'hybrid', 'doc-123')
345
+ mock_tokens.assert_called_once_with([1000, 1500, 2000], 'hybrid', 'doc-123')
346
+ mock_time.assert_called_once_with(1500.0, 'parallel', 'doc-123')
347
+
348
+ @patch('metrics.emit_chunk_count')
349
+ @patch('metrics.emit_strategy_usage')
350
+ @patch('metrics.emit_chunking_operation')
351
+ def test_emit_chunking_metrics_zero_chunks(
352
+ self, mock_operation, mock_strategy, mock_count
353
+ ):
354
+ """Test that zero chunk count does not emit chunk metrics."""
355
+ emit_chunking_metrics(
356
+ document_id='doc-123',
357
+ strategy='hybrid',
358
+ requires_chunking=True,
359
+ chunk_count=0
360
+ )
361
+
362
+ mock_operation.assert_called_once()
363
+ mock_strategy.assert_called_once()
364
+ mock_count.assert_not_called()
365
+
366
+
367
+ class TestTimedOperation(unittest.TestCase):
368
+ """Test cases for timed_operation decorator."""
369
+
370
+ @patch('metrics.metrics')
371
+ def test_timed_operation_emits_metric(self, mock_metrics):
372
+ """Test that timed operation emits timing metric."""
373
+ @timed_operation(metric_name='TestOperationTime')
374
+ def slow_function():
375
+ time.sleep(0.01) # 10ms
376
+ return "result"
377
+
378
+ result = slow_function()
379
+
380
+ self.assertEqual(result, "result")
381
+ mock_metrics.add_metric.assert_called_once()
382
+ call_args = mock_metrics.add_metric.call_args
383
+ self.assertEqual(call_args[1]['name'], 'TestOperationTime')
384
+ # Verify timing is reasonable (at least 10ms)
385
+ self.assertGreaterEqual(call_args[1]['value'], 10)
386
+
387
+ @patch('metrics.metrics')
388
+ def test_timed_operation_with_exception(self, mock_metrics):
389
+ """Test that timing is emitted even when function raises."""
390
+ @timed_operation(metric_name='TestOperationTime')
391
+ def failing_function():
392
+ raise ValueError("Test error")
393
+
394
+ with self.assertRaises(ValueError):
395
+ failing_function()
396
+
397
+ # Metric should still be emitted
398
+ mock_metrics.add_metric.assert_called_once()
399
+
400
+ @patch('metrics.metrics')
401
+ @patch('metrics.logger')
402
+ def test_timed_operation_handles_metric_error(self, mock_logger, mock_metrics):
403
+ """Test that metric emission errors are logged but not raised."""
404
+ mock_metrics.add_metric.side_effect = Exception("Metric error")
405
+
406
+ @timed_operation(metric_name='TestOperationTime')
407
+ def simple_function():
408
+ return "result"
409
+
410
+ result = simple_function()
411
+
412
+ self.assertEqual(result, "result")
413
+ mock_logger.warning.assert_called_once()
414
+
415
+
416
+ class TestGetMetrics(unittest.TestCase):
417
+ """Test cases for get_metrics function."""
418
+
419
+ def test_get_metrics_returns_instance(self):
420
+ """Test that get_metrics returns a Metrics instance."""
421
+ metrics_instance = get_metrics()
422
+
423
+ self.assertIsNotNone(metrics_instance)
424
+
425
+
426
+ class TestMetricsGating(unittest.TestCase):
427
+ """Test cases for metrics gating via ENABLE_METRICS environment variable."""
428
+
429
+ def test_is_metrics_enabled_returns_true_when_enabled(self):
430
+ """Test that _is_metrics_enabled returns True when ENABLE_METRICS=true."""
431
+ # ENABLE_METRICS is set to 'true' at module import time
432
+ self.assertTrue(_is_metrics_enabled())
433
+
434
+ @patch('metrics.METRICS_ENABLED', False)
435
+ @patch('metrics.metrics')
436
+ def test_emit_chunking_operation_skipped_when_disabled(self, mock_metrics):
437
+ """Test that metrics are not emitted when ENABLE_METRICS=false."""
438
+ emit_chunking_operation(
439
+ strategy='hybrid',
440
+ requires_chunking=True,
441
+ document_id='doc-123'
442
+ )
443
+
444
+ mock_metrics.add_metric.assert_not_called()
445
+ mock_metrics.add_dimension.assert_not_called()
446
+
447
+ @patch('metrics.METRICS_ENABLED', False)
448
+ @patch('metrics.metrics')
449
+ def test_emit_chunk_count_skipped_when_disabled(self, mock_metrics):
450
+ """Test that chunk count metrics are not emitted when disabled."""
451
+ emit_chunk_count(
452
+ chunk_count=5,
453
+ strategy='hybrid',
454
+ document_id='doc-123'
455
+ )
456
+
457
+ mock_metrics.add_metric.assert_not_called()
458
+
459
+ @patch('metrics.METRICS_ENABLED', False)
460
+ @patch('metrics.metrics')
461
+ def test_emit_tokens_per_chunk_skipped_when_disabled(self, mock_metrics):
462
+ """Test that tokens per chunk metrics are not emitted when disabled."""
463
+ emit_tokens_per_chunk(
464
+ tokens_per_chunk=[1000, 1500, 2000],
465
+ strategy='hybrid',
466
+ document_id='doc-123'
467
+ )
468
+
469
+ mock_metrics.add_metric.assert_not_called()
470
+
471
+ @patch('metrics.METRICS_ENABLED', False)
472
+ @patch('metrics.metrics')
473
+ def test_emit_chunk_processing_time_skipped_when_disabled(self, mock_metrics):
474
+ """Test that processing time metrics are not emitted when disabled."""
475
+ emit_chunk_processing_time(
476
+ processing_time_ms=1500.0,
477
+ processing_mode='parallel',
478
+ document_id='doc-123'
479
+ )
480
+
481
+ mock_metrics.add_metric.assert_not_called()
482
+
483
+ @patch('metrics.METRICS_ENABLED', False)
484
+ @patch('metrics.metrics')
485
+ def test_emit_chunk_failure_rate_skipped_when_disabled(self, mock_metrics):
486
+ """Test that failure rate metrics are not emitted when disabled."""
487
+ emit_chunk_failure_rate(
488
+ total_chunks=10,
489
+ failed_chunks=2,
490
+ document_id='doc-123'
491
+ )
492
+
493
+ mock_metrics.add_metric.assert_not_called()
494
+
495
+ @patch('metrics.METRICS_ENABLED', False)
496
+ @patch('metrics.metrics')
497
+ def test_emit_aggregation_time_skipped_when_disabled(self, mock_metrics):
498
+ """Test that aggregation time metrics are not emitted when disabled."""
499
+ emit_aggregation_time(
500
+ aggregation_time_ms=500.0,
501
+ document_id='doc-123'
502
+ )
503
+
504
+ mock_metrics.add_metric.assert_not_called()
505
+
506
+ @patch('metrics.METRICS_ENABLED', False)
507
+ @patch('metrics.metrics')
508
+ def test_emit_strategy_usage_skipped_when_disabled(self, mock_metrics):
509
+ """Test that strategy usage metrics are not emitted when disabled."""
510
+ emit_strategy_usage(
511
+ strategy='hybrid',
512
+ document_id='doc-123'
513
+ )
514
+
515
+ mock_metrics.add_metric.assert_not_called()
516
+
517
+ @patch('metrics._is_metrics_enabled', return_value=False)
518
+ @patch('metrics.metrics')
519
+ def test_timed_operation_skipped_when_disabled(self, mock_metrics, mock_enabled):
520
+ """Test that timed operation does not emit metrics when disabled."""
521
+ @timed_operation(metric_name='TestOperationTime')
522
+ def simple_function():
523
+ return "result"
524
+
525
+ result = simple_function()
526
+
527
+ self.assertEqual(result, "result")
528
+ mock_metrics.add_metric.assert_not_called()
529
+
530
+
531
+ if __name__ == '__main__':
532
+ unittest.main()