async def test_liveness_with_reporting(liveness_url, liveness_registry): def fn1(**kwargs): return {'x': 100} def fn2(**kwargs): return {'y': '200'} liveness_registry.activity_handlers.append( ActivityHandler( fn=fn1, id='id1', activity=Activity.PROBE, errors=None, timeout=None, retries=None, backoff=None, cooldown=None, )) liveness_registry.activity_handlers.append( ActivityHandler( fn=fn2, id='id2', activity=Activity.PROBE, errors=None, timeout=None, retries=None, backoff=None, cooldown=None, )) async with aiohttp.ClientSession() as session: async with session.get(liveness_url) as response: data = await response.json() assert isinstance(data, dict) assert data == {'id1': {'x': 100}, 'id2': {'y': '200'}}
async def test_results_are_returned_on_success(settings, activity): def sample_fn1(**_): return 123 def sample_fn2(**_): return 456 registry = OperatorRegistry() registry._activities.append(ActivityHandler( fn=sample_fn1, id='id1', activity=activity, errors=None, timeout=None, retries=None, backoff=None, )) registry._activities.append(ActivityHandler( fn=sample_fn2, id='id2', activity=activity, errors=None, timeout=None, retries=None, backoff=None, )) results = await run_activity( registry=registry, settings=settings, activity=activity, lifecycle=all_at_once, ) assert set(results.keys()) == {'id1', 'id2'} assert results['id1'] == 123 assert results['id2'] == 456
async def test_errors_are_raised_aggregated(settings, activity): def sample_fn1(**_): raise PermanentError("boo!123") def sample_fn2(**_): raise PermanentError("boo!456") registry = OperatorRegistry() registry._activities.append( ActivityHandler( fn=sample_fn1, id='id1', activity=activity, param=None, errors=None, timeout=None, retries=None, backoff=None, )) registry._activities.append( ActivityHandler( fn=sample_fn2, id='id2', activity=activity, param=None, errors=None, timeout=None, retries=None, backoff=None, )) with pytest.raises(ActivityError) as e: await run_activity( registry=registry, settings=settings, activity=activity, lifecycle=all_at_once, indices=OperatorIndexers().indices, memo=Memo(), ) assert set(e.value.outcomes.keys()) == {'id1', 'id2'} assert e.value.outcomes['id1'].final assert e.value.outcomes['id1'].delay is None assert e.value.outcomes['id1'].result is None assert e.value.outcomes['id1'].exception is not None assert e.value.outcomes['id2'].final assert e.value.outcomes['id2'].delay is None assert e.value.outcomes['id2'].result is None assert e.value.outcomes['id2'].exception is not None assert str(e.value.outcomes['id1'].exception) == "boo!123" assert str(e.value.outcomes['id2'].exception) == "boo!456"
async def test_noreturn_handler_produces_no_credentials(settings): vault = Vault() registry = OperatorRegistry() def login_fn(**_): pass # NB: id auto-detection does not work, as it is local to the test function. registry._activities.append( ActivityHandler( fn=login_fn, id='login_fn', activity=Activity.AUTHENTICATION, param=None, errors=None, timeout=None, retries=None, backoff=None, )) await authenticate( registry=registry, settings=settings, vault=vault, ) assert not vault with pytest.raises(LoginError): async for _, _ in vault: pass
async def test_delays_are_simulated(settings, activity, mocker): def sample_fn(**_): raise TemporaryError('to be retried', delay=123) registry = OperatorRegistry() registry._activities.append(ActivityHandler( fn=sample_fn, id='id', activity=activity, errors=None, timeout=None, retries=3, backoff=None, )) with freezegun.freeze_time() as frozen: async def sleep_or_wait_substitute(*_, **__): frozen.tick(123) sleep_or_wait = mocker.patch('kopf.reactor.effects.sleep_or_wait', wraps=sleep_or_wait_substitute) with pytest.raises(ActivityError) as e: await run_activity( registry=registry, settings=settings, activity=activity, lifecycle=all_at_once, ) assert sleep_or_wait.call_count >= 3 # 3 retries, 1 sleep each assert sleep_or_wait.call_count <= 4 # 3 retries, 1 final success (delay=None), not more if sleep_or_wait.call_count > 3: sleep_or_wait.call_args_list[-1][0][0] is None
def test_activity_handler_with_all_args(mocker): fn = mocker.Mock() id = mocker.Mock() errors = mocker.Mock() timeout = mocker.Mock() retries = mocker.Mock() backoff = mocker.Mock() activity = mocker.Mock() handler = ActivityHandler( fn=fn, id=id, errors=errors, timeout=timeout, retries=retries, backoff=backoff, cooldown=None, # deprecated, but still required activity=activity, ) assert handler.fn is fn assert handler.id is id assert handler.errors is errors assert handler.timeout is timeout assert handler.retries is retries assert handler.backoff is backoff assert handler.activity is activity
async def test_retries_are_simulated(settings, activity, mocker): mock = mocker.MagicMock() def sample_fn(**_): mock() raise TemporaryError('to be retried', delay=0) registry = OperatorRegistry() registry._activities.append( ActivityHandler( fn=sample_fn, id='id', activity=activity, param=None, errors=None, timeout=None, retries=3, backoff=None, )) with pytest.raises(ActivityError) as e: await run_activity( registry=registry, settings=settings, activity=activity, lifecycle=all_at_once, indices=OperatorIndexers().indices, memo=Memo(), ) assert isinstance(e.value.outcomes['id'].exception, PermanentError) assert mock.call_count == 3
async def test_errors_are_cascaded_from_one_of_the_originals( settings, activity): def sample_fn(**_): raise PermanentError("boo!") registry = OperatorRegistry() registry._activities.append( ActivityHandler( fn=sample_fn, id='id', activity=activity, param=None, errors=None, timeout=None, retries=None, backoff=None, )) with pytest.raises(ActivityError) as e: await run_activity( registry=registry, settings=settings, activity=activity, lifecycle=all_at_once, indices=OperatorIndexers().indices, memo=Memo(), ) assert e.value.__cause__ assert type(e.value.__cause__) is PermanentError assert str(e.value.__cause__) == "boo!"
async def test_single_credentials_provided_to_vault(settings): info = ConnectionInfo(server='https://expected/') vault = Vault() registry = OperatorRegistry() def login_fn(**_): return info # NB: id auto-detection does not work, as it is local to the test function. registry._activities.append(ActivityHandler( fn=login_fn, id='login_fn', activity=Activity.AUTHENTICATION, errors=None, timeout=None, retries=None, backoff=None, )) await authenticate( registry=registry, settings=settings, vault=vault, ) assert vault items = [] async for key, info in vault: items.append((key, info)) assert len(items) == 1 assert items[0][0] == 'login_fn' assert items[0][1] is info
async def test_liveness_data_is_cached(liveness_url, liveness_registry): counter = 0 def fn1(**kwargs): nonlocal counter counter += 1 return {'counter': counter} liveness_registry.activity_handlers.append( ActivityHandler( fn=fn1, id='id1', activity=Activity.PROBE, errors=None, timeout=None, retries=None, backoff=None, cooldown=None, )) async with aiohttp.ClientSession() as session: async with session.get(liveness_url) as response: data = await response.json() assert isinstance(data, dict) assert data == {'id1': {'counter': 1}} async with session.get(liveness_url) as response: data = await response.json() assert isinstance(data, dict) assert data == {'id1': {'counter': 1}} # not 2!
def test_activity_handler_with_all_args(mocker): fn = mocker.Mock() id = mocker.Mock() param = mocker.Mock() errors = mocker.Mock() timeout = mocker.Mock() retries = mocker.Mock() backoff = mocker.Mock() activity = mocker.Mock() handler = ActivityHandler( fn=fn, id=id, param=param, errors=errors, timeout=timeout, retries=retries, backoff=backoff, activity=activity, ) assert handler.fn is fn assert handler.id is id assert handler.param is param assert handler.errors is errors assert handler.timeout is timeout assert handler.retries is retries assert handler.backoff is backoff assert handler.activity is activity
def test_activity_handler_with_deprecated_cooldown_instead_of_backoff(mocker): fn = mocker.Mock() id = mocker.Mock() errors = mocker.Mock() timeout = mocker.Mock() retries = mocker.Mock() backoff = mocker.Mock() activity = mocker.Mock() with pytest.deprecated_call(match=r"use backoff="): handler = ActivityHandler( fn=fn, id=id, errors=errors, timeout=timeout, retries=retries, backoff=None, cooldown=backoff, # deprecated, but still required activity=activity, ) assert handler.fn is fn assert handler.id is id assert handler.errors is errors assert handler.timeout is timeout assert handler.retries is retries assert handler.backoff is backoff assert handler.activity is activity with pytest.deprecated_call(match=r"use handler.backoff"): assert handler.cooldown is backoff