@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.
- package/.jsii +2579 -194
- package/lib/document-processing/adapter/adapter.d.ts +4 -2
- package/lib/document-processing/adapter/adapter.js +1 -1
- package/lib/document-processing/adapter/queued-s3-adapter.d.ts +9 -2
- package/lib/document-processing/adapter/queued-s3-adapter.js +29 -15
- package/lib/document-processing/agentic-document-processing.d.ts +4 -0
- package/lib/document-processing/agentic-document-processing.js +20 -10
- package/lib/document-processing/base-document-processing.d.ts +54 -2
- package/lib/document-processing/base-document-processing.js +136 -82
- package/lib/document-processing/bedrock-document-processing.d.ts +202 -2
- package/lib/document-processing/bedrock-document-processing.js +717 -77
- package/lib/document-processing/chunking-config.d.ts +614 -0
- package/lib/document-processing/chunking-config.js +5 -0
- package/lib/document-processing/default-document-processing-config.js +1 -1
- package/lib/document-processing/index.d.ts +1 -0
- package/lib/document-processing/index.js +2 -1
- package/lib/document-processing/resources/aggregation/handler.py +567 -0
- package/lib/document-processing/resources/aggregation/requirements.txt +7 -0
- package/lib/document-processing/resources/aggregation/test_handler.py +362 -0
- package/lib/document-processing/resources/cleanup/handler.py +276 -0
- package/lib/document-processing/resources/cleanup/requirements.txt +5 -0
- package/lib/document-processing/resources/cleanup/test_handler.py +436 -0
- package/lib/document-processing/resources/default-bedrock-invoke/index.py +85 -3
- package/lib/document-processing/resources/default-bedrock-invoke/test_index.py +622 -0
- package/lib/document-processing/resources/pdf-chunking/README.md +313 -0
- package/lib/document-processing/resources/pdf-chunking/chunking_strategies.py +460 -0
- package/lib/document-processing/resources/pdf-chunking/error_handling.py +491 -0
- package/lib/document-processing/resources/pdf-chunking/handler.py +958 -0
- package/lib/document-processing/resources/pdf-chunking/metrics.py +435 -0
- package/lib/document-processing/resources/pdf-chunking/requirements.txt +3 -0
- package/lib/document-processing/resources/pdf-chunking/strategy_selection.py +420 -0
- package/lib/document-processing/resources/pdf-chunking/structured_logging.py +457 -0
- package/lib/document-processing/resources/pdf-chunking/test_chunking_strategies.py +353 -0
- package/lib/document-processing/resources/pdf-chunking/test_error_handling.py +487 -0
- package/lib/document-processing/resources/pdf-chunking/test_handler.py +609 -0
- package/lib/document-processing/resources/pdf-chunking/test_integration.py +694 -0
- package/lib/document-processing/resources/pdf-chunking/test_metrics.py +532 -0
- package/lib/document-processing/resources/pdf-chunking/test_strategy_selection.py +471 -0
- package/lib/document-processing/resources/pdf-chunking/test_structured_logging.py +449 -0
- package/lib/document-processing/resources/pdf-chunking/test_token_estimation.py +374 -0
- package/lib/document-processing/resources/pdf-chunking/token_estimation.py +189 -0
- package/lib/document-processing/tests/agentic-document-processing-nag.test.js +4 -3
- package/lib/document-processing/tests/agentic-document-processing.test.js +488 -4
- package/lib/document-processing/tests/base-document-processing-nag.test.js +9 -2
- package/lib/document-processing/tests/base-document-processing-schema.test.d.ts +1 -0
- package/lib/document-processing/tests/base-document-processing-schema.test.js +337 -0
- package/lib/document-processing/tests/base-document-processing.test.js +114 -8
- package/lib/document-processing/tests/bedrock-document-processing-chunking-nag.test.d.ts +1 -0
- package/lib/document-processing/tests/bedrock-document-processing-chunking-nag.test.js +382 -0
- package/lib/document-processing/tests/bedrock-document-processing-nag.test.js +4 -3
- package/lib/document-processing/tests/bedrock-document-processing-security.test.d.ts +1 -0
- package/lib/document-processing/tests/bedrock-document-processing-security.test.js +389 -0
- package/lib/document-processing/tests/bedrock-document-processing.test.js +808 -8
- package/lib/document-processing/tests/chunking-config.test.d.ts +1 -0
- package/lib/document-processing/tests/chunking-config.test.js +238 -0
- package/lib/document-processing/tests/queued-s3-adapter-nag.test.js +9 -2
- package/lib/document-processing/tests/queued-s3-adapter.test.js +17 -6
- package/lib/framework/agents/base-agent.js +1 -1
- package/lib/framework/agents/batch-agent.js +1 -1
- package/lib/framework/agents/default-agent-config.js +1 -1
- package/lib/framework/bedrock/bedrock.js +1 -1
- package/lib/framework/custom-resource/default-runtimes.js +1 -1
- package/lib/framework/foundation/access-log.js +1 -1
- package/lib/framework/foundation/eventbridge-broker.js +1 -1
- package/lib/framework/foundation/network.d.ts +4 -2
- package/lib/framework/foundation/network.js +52 -41
- package/lib/framework/tests/access-log.test.js +5 -2
- package/lib/framework/tests/batch-agent.test.js +5 -2
- package/lib/framework/tests/bedrock.test.js +5 -2
- package/lib/framework/tests/eventbridge-broker.test.js +5 -2
- package/lib/framework/tests/framework-nag.test.js +26 -7
- package/lib/framework/tests/network.test.js +30 -2
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utilities/data-loader.js +1 -1
- package/lib/utilities/lambda-iam-utils.js +1 -1
- package/lib/utilities/observability/cloudfront-distribution-observability-property-injector.js +1 -1
- package/lib/utilities/observability/default-observability-config.js +1 -1
- package/lib/utilities/observability/lambda-observability-property-injector.js +1 -1
- package/lib/utilities/observability/log-group-data-protection-utils.js +1 -1
- package/lib/utilities/observability/powertools-config.d.ts +10 -1
- package/lib/utilities/observability/powertools-config.js +19 -3
- package/lib/utilities/observability/state-machine-observability-property-injector.js +1 -1
- package/lib/utilities/test-utils.d.ts +43 -0
- package/lib/utilities/test-utils.js +56 -0
- package/lib/utilities/tests/data-loader-nag.test.js +3 -2
- package/lib/utilities/tests/data-loader.test.js +3 -2
- package/lib/webapp/frontend-construct.js +1 -1
- package/lib/webapp/tests/frontend-construct-nag.test.js +3 -2
- package/lib/webapp/tests/frontend-construct.test.js +3 -2
- package/package.json +6 -5
- package/lib/document-processing/resources/default-error-handler/index.js +0 -46
- package/lib/document-processing/resources/default-pdf-processor/index.js +0 -46
- 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()
|