-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest_output_error_handling.py
More file actions
373 lines (292 loc) · 12.9 KB
/
Copy pathtest_output_error_handling.py
File metadata and controls
373 lines (292 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
"""Test classification output error handling functionality."""
import pytest
from resolver_athena_client.generated.athena.models_pb2 import (
Classification,
ClassificationError,
ClassificationOutput,
ClassifyResponse,
ErrorCode,
)
from resolver_athena_client.client.exceptions import ClassificationOutputError
from resolver_athena_client.client.utils import (
get_output_error_summary,
get_successful_outputs,
has_output_errors,
log_output_errors,
process_classification_outputs,
)
# Test constants
EXPECTED_SUCCESS_COUNT = 2
EXPECTED_ERROR_COUNT = 2
def create_successful_output(
correlation_id: str = "test-123",
) -> ClassificationOutput:
"""Create a successful classification output for testing."""
output = ClassificationOutput()
output.correlation_id = correlation_id
# Add some classifications
classification1 = Classification()
classification1.label = "cat"
classification1.weight = 0.95
output.classifications.append(classification1)
classification2 = Classification()
classification2.label = "animal"
classification2.weight = 0.87
output.classifications.append(classification2)
return output
def create_error_output(
correlation_id: str = "error-456",
error_code: ErrorCode.ValueType = ErrorCode.ERROR_CODE_MODEL_ERROR,
message: str = "Model inference failed",
details: str = "",
) -> ClassificationOutput:
"""Create a classification output with an error for testing."""
output = ClassificationOutput()
output.correlation_id = correlation_id
# Set the error
output.error.code = error_code
output.error.message = message
if details:
output.error.details = details
return output
def create_response_with_mixed_outputs() -> ClassifyResponse:
"""Create a response with both successful and error outputs."""
response = ClassifyResponse()
# Add successful outputs
response.outputs.append(create_successful_output("success-1"))
response.outputs.append(create_successful_output("success-2"))
# Add error outputs
response.outputs.append(
create_error_output(
"error-1",
ErrorCode.ERROR_CODE_IMAGE_TOO_LARGE,
"Image exceeds size limit",
"Max size: 10MB",
)
)
response.outputs.append(
create_error_output(
"error-2",
ErrorCode.ERROR_CODE_MODEL_ERROR,
"Model failed to process image",
)
)
return response
class TestClassificationOutputError:
"""Test the ClassificationOutputError exception."""
def test_error_creation_with_all_fields(self) -> None:
"""Test creating error with all fields."""
error = ClassificationError()
error.code = ErrorCode.ERROR_CODE_IMAGE_TOO_LARGE
error.message = "Image too large"
error.details = "Maximum size exceeded"
exception = ClassificationOutputError("test-123", error)
assert exception.correlation_id == "test-123"
assert exception.error_code == ErrorCode.ERROR_CODE_IMAGE_TOO_LARGE
assert exception.error_message == "Image too large"
assert exception.error_details == "Maximum size exceeded"
assert "test-123" in str(exception)
assert "Image too large" in str(exception)
def test_error_creation_with_custom_message(self) -> None:
"""Test creating error with custom message."""
error = ClassificationError()
error.code = ErrorCode.ERROR_CODE_MODEL_ERROR
error.message = "Model failed"
exception = ClassificationOutputError(
"test-456", error, "Custom error message"
)
assert str(exception) == "Custom error message"
def test_error_creation_with_details(self) -> None:
"""Test error message includes details when present."""
error = ClassificationError()
error.code = ErrorCode.ERROR_CODE_MODEL_ERROR
error.message = "Model failed"
error.details = "GPU memory exhausted"
exception = ClassificationOutputError("test-789", error)
assert "Model failed" in str(exception)
assert "GPU memory exhausted" in str(exception)
class TestHasOutputErrors:
"""Test the has_output_errors function."""
def test_response_with_no_errors(self) -> None:
"""Test response with only successful outputs."""
response = ClassifyResponse()
response.outputs.append(create_successful_output("test-1"))
response.outputs.append(create_successful_output("test-2"))
assert not has_output_errors(response)
def test_response_with_errors(self) -> None:
"""Test response with error outputs."""
response = create_response_with_mixed_outputs()
assert has_output_errors(response)
def test_empty_response(self) -> None:
"""Test empty response."""
response = ClassifyResponse()
assert not has_output_errors(response)
def test_response_with_empty_error_message(self) -> None:
"""Test output with error object but empty message."""
response = ClassifyResponse()
output = ClassificationOutput()
output.correlation_id = "test-123"
output.error.code = ErrorCode.ERROR_CODE_MODEL_ERROR
output.error.message = "" # Empty message
response.outputs.append(output)
assert not has_output_errors(response)
class TestGetSuccessfulOutputs:
"""Test the get_successful_outputs function."""
def test_mixed_response(self) -> None:
"""Test filtering successful outputs from mixed response."""
response = create_response_with_mixed_outputs()
successful = get_successful_outputs(response)
assert len(successful) == EXPECTED_SUCCESS_COUNT
assert all(
not (output.error and output.error.message) for output in successful
)
assert successful[0].correlation_id == "success-1"
assert successful[1].correlation_id == "success-2"
def test_all_successful_response(self) -> None:
"""Test response with only successful outputs."""
response = ClassifyResponse()
response.outputs.append(create_successful_output("test-1"))
response.outputs.append(create_successful_output("test-2"))
successful = get_successful_outputs(response)
assert len(successful) == EXPECTED_SUCCESS_COUNT
def test_all_error_response(self) -> None:
"""Test response with only error outputs."""
response = ClassifyResponse()
response.outputs.append(create_error_output("error-1"))
response.outputs.append(create_error_output("error-2"))
successful = get_successful_outputs(response)
assert len(successful) == 0
def test_empty_response(self) -> None:
"""Test empty response."""
response = ClassifyResponse()
successful = get_successful_outputs(response)
assert len(successful) == 0
class TestGetOutputErrorSummary:
"""Test the get_output_error_summary function."""
def test_mixed_response_error_summary(self) -> None:
"""Test error summary for mixed response."""
response = create_response_with_mixed_outputs()
summary = get_output_error_summary(response)
expected_summary = {
str(ErrorCode.ERROR_CODE_IMAGE_TOO_LARGE): 1,
str(ErrorCode.ERROR_CODE_MODEL_ERROR): 1,
}
assert summary == expected_summary
def test_no_errors_summary(self) -> None:
"""Test error summary for response with no errors."""
response = ClassifyResponse()
response.outputs.append(create_successful_output("test-1"))
response.outputs.append(create_successful_output("test-2"))
summary = get_output_error_summary(response)
assert summary == {}
def test_multiple_same_errors(self) -> None:
"""Test error summary with multiple errors of same type."""
response = ClassifyResponse()
response.outputs.append(
create_error_output("error-1", ErrorCode.ERROR_CODE_MODEL_ERROR)
)
response.outputs.append(
create_error_output("error-2", ErrorCode.ERROR_CODE_MODEL_ERROR)
)
response.outputs.append(
create_error_output("error-3", ErrorCode.ERROR_CODE_MODEL_ERROR)
)
summary = get_output_error_summary(response)
assert summary == {str(ErrorCode.ERROR_CODE_MODEL_ERROR): 3}
class TestProcessClassificationOutputs:
"""Test the process_classification_outputs function."""
def test_process_mixed_outputs_raise_false(self) -> None:
"""Test processing mixed outputs without raising exceptions."""
response = create_response_with_mixed_outputs()
successful = process_classification_outputs(
response, raise_on_error=False, log_errors=False
)
assert len(successful) == EXPECTED_SUCCESS_COUNT
assert successful[0].correlation_id == "success-1"
assert successful[1].correlation_id == "success-2"
def test_process_mixed_outputs_raise_true(self) -> None:
"""Test processing mixed outputs with raising exceptions."""
response = create_response_with_mixed_outputs()
with pytest.raises(ClassificationOutputError) as exc_info:
_ = process_classification_outputs(response, raise_on_error=True)
assert exc_info.value.correlation_id == "error-1"
assert exc_info.value.error_code == ErrorCode.ERROR_CODE_IMAGE_TOO_LARGE
def test_process_all_successful_outputs(self) -> None:
"""Test processing response with only successful outputs."""
response = ClassifyResponse()
response.outputs.append(create_successful_output("test-1"))
response.outputs.append(create_successful_output("test-2"))
successful = process_classification_outputs(
response, raise_on_error=True
)
assert len(successful) == EXPECTED_SUCCESS_COUNT
def test_process_all_error_outputs_raise_false(self) -> None:
"""Test processing response with only error outputs, no raising."""
response = ClassifyResponse()
response.outputs.append(create_error_output("error-1"))
response.outputs.append(create_error_output("error-2"))
successful = process_classification_outputs(
response, raise_on_error=False, log_errors=False
)
assert len(successful) == 0
def test_process_all_error_outputs_raise_true(self) -> None:
"""Test processing response with only error outputs, with raising."""
response = ClassifyResponse()
response.outputs.append(create_error_output("error-1"))
with pytest.raises(ClassificationOutputError):
_ = process_classification_outputs(response, raise_on_error=True)
def test_process_empty_response(self) -> None:
"""Test processing empty response."""
response = ClassifyResponse()
successful = process_classification_outputs(
response, raise_on_error=True
)
assert len(successful) == 0
class TestLogOutputErrors:
"""Test the log_output_errors function."""
def test_log_errors_in_mixed_response(
self, caplog: pytest.LogCaptureFixture
) -> None:
"""Test logging errors in mixed response."""
response = create_response_with_mixed_outputs()
with caplog.at_level("ERROR"):
log_output_errors(response)
# Should have logged 2 errors
error_records = [
record for record in caplog.records if record.levelname == "ERROR"
]
assert len(error_records) == EXPECTED_ERROR_COUNT
# Check error messages contain correlation IDs
assert "error-1" in error_records[0].message
assert "error-2" in error_records[1].message
def test_log_no_errors(self, caplog: pytest.LogCaptureFixture) -> None:
"""Test logging when there are no errors."""
response = ClassifyResponse()
response.outputs.append(create_successful_output("test-1"))
with caplog.at_level("ERROR"):
log_output_errors(response)
error_records = [
record for record in caplog.records if record.levelname == "ERROR"
]
assert len(error_records) == 0
def test_log_error_with_details(
self, caplog: pytest.LogCaptureFixture
) -> None:
"""Test logging error with details."""
response = ClassifyResponse()
response.outputs.append(
create_error_output(
"test-123",
ErrorCode.ERROR_CODE_IMAGE_TOO_LARGE,
"Image too large",
"Max size: 10MB",
)
)
with caplog.at_level("DEBUG"):
log_output_errors(response)
# Check that details are logged at DEBUG level
debug_records = [
record for record in caplog.records if record.levelname == "DEBUG"
]
assert len(debug_records) == 1
assert "Max size: 10MB" in debug_records[0].message