|  | 
| 9 | 9 | 
 | 
| 10 | 10 | from airbyte_cdk.models import FailureType | 
| 11 | 11 | from airbyte_cdk.sources.declarative.requesters.error_handlers import HttpResponseFilter | 
|  | 12 | +from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies import ( | 
|  | 13 | +    ConstantBackoffStrategy, | 
|  | 14 | +) | 
| 12 | 15 | from airbyte_cdk.sources.declarative.requesters.error_handlers.composite_error_handler import ( | 
| 13 | 16 |     CompositeErrorHandler, | 
| 14 | 17 | ) | 
| @@ -272,3 +275,77 @@ def test_max_time_is_max_of_underlying_handlers(test_name, max_times, expected_m | 
| 272 | 275 | 
 | 
| 273 | 276 |     max_time = composite_error_handler.max_time | 
| 274 | 277 |     assert max_time == expected_max_time | 
|  | 278 | + | 
|  | 279 | + | 
|  | 280 | +@pytest.mark.parametrize( | 
|  | 281 | +    "test_name, handler_strategies, expected_strategies", | 
|  | 282 | +    [ | 
|  | 283 | +        ("test_empty_strategies", [None, None], None), | 
|  | 284 | +        ( | 
|  | 285 | +            "test_single_handler_with_strategy", | 
|  | 286 | +            [[ConstantBackoffStrategy(5, {}, {})], None], | 
|  | 287 | +            [ConstantBackoffStrategy(5, {}, {})], | 
|  | 288 | +        ), | 
|  | 289 | +        ( | 
|  | 290 | +            "test_multiple_handlers_with_strategies", | 
|  | 291 | +            [[ConstantBackoffStrategy(5, {}, {})], [ConstantBackoffStrategy(10, {}, {})]], | 
|  | 292 | +            [ConstantBackoffStrategy(5, {}, {}), ConstantBackoffStrategy(10, {}, {})], | 
|  | 293 | +        ), | 
|  | 294 | +        ( | 
|  | 295 | +            "test_some_handlers_without_strategies", | 
|  | 296 | +            [[ConstantBackoffStrategy(5, {}, {})], None, [ConstantBackoffStrategy(10, {}, {})]], | 
|  | 297 | +            [ConstantBackoffStrategy(5, {}, {}), ConstantBackoffStrategy(10, {}, {})], | 
|  | 298 | +        ), | 
|  | 299 | +    ], | 
|  | 300 | +) | 
|  | 301 | +def test_composite_error_handler_backoff_strategies( | 
|  | 302 | +    test_name, handler_strategies, expected_strategies | 
|  | 303 | +): | 
|  | 304 | +    parameters = {} | 
|  | 305 | +    config = {} | 
|  | 306 | + | 
|  | 307 | +    error_handlers = [ | 
|  | 308 | +        DefaultErrorHandler(backoff_strategies=strategies, parameters=parameters, config=config) | 
|  | 309 | +        for strategies in handler_strategies | 
|  | 310 | +    ] | 
|  | 311 | + | 
|  | 312 | +    composite_handler = CompositeErrorHandler(error_handlers=error_handlers, parameters=parameters) | 
|  | 313 | + | 
|  | 314 | +    assert composite_handler.backoff_strategies == expected_strategies | 
|  | 315 | + | 
|  | 316 | + | 
|  | 317 | +def test_composite_error_handler_always_uses_first_strategy(): | 
|  | 318 | +    first_handler = DefaultErrorHandler( | 
|  | 319 | +        backoff_strategies=[ConstantBackoffStrategy(5, {}, {})], | 
|  | 320 | +        parameters={}, | 
|  | 321 | +        config={}, | 
|  | 322 | +        response_filters=[ | 
|  | 323 | +            HttpResponseFilter( | 
|  | 324 | +                action=ResponseAction.RETRY, http_codes={429}, config={}, parameters={} | 
|  | 325 | +            ) | 
|  | 326 | +        ], | 
|  | 327 | +    ) | 
|  | 328 | +    second_handler = DefaultErrorHandler( | 
|  | 329 | +        backoff_strategies=[ConstantBackoffStrategy(10, {}, {})], | 
|  | 330 | +        parameters={}, | 
|  | 331 | +        config={}, | 
|  | 332 | +        response_filters=[ | 
|  | 333 | +            HttpResponseFilter( | 
|  | 334 | +                action=ResponseAction.RETRY, http_codes={500}, config={}, parameters={} | 
|  | 335 | +            ) | 
|  | 336 | +        ], | 
|  | 337 | +    ) | 
|  | 338 | + | 
|  | 339 | +    composite_handler = CompositeErrorHandler( | 
|  | 340 | +        error_handlers=[first_handler, second_handler], parameters={} | 
|  | 341 | +    ) | 
|  | 342 | + | 
|  | 343 | +    # Test that even for a 500 error (which matches second handler's filter), | 
|  | 344 | +    # we still get both strategies with first handler's coming first | 
|  | 345 | +    response_mock = create_response(500) | 
|  | 346 | +    assert first_handler.backoff_strategies[0].backoff_time(response_mock, 1) == 5 | 
|  | 347 | + | 
|  | 348 | +    # Verify we get both strategies in the composite handler | 
|  | 349 | +    assert len(composite_handler.backoff_strategies) == 2 | 
|  | 350 | +    assert isinstance(composite_handler.backoff_strategies[0], ConstantBackoffStrategy) | 
|  | 351 | +    assert composite_handler.backoff_strategies[1], ConstantBackoffStrategy | 
0 commit comments