def test_field_with_value(mocker, cause_factory, decorator, causeargs, handlers_prop): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') old = {'field': {'subfield': 'old'}} new = {'field': {'subfield': 'new'}} cause = cause_factory(resource=resource, old=old, new=new, body=new, **causeargs) mocker.patch('kopf.reactor.registries.match', return_value=True) @decorator('group', 'version', 'plural', registry=registry, field='spec.field', value='value') def fn(**_): pass handlers_registry = getattr(registry, handlers_prop) handlers = handlers_registry[resource].get_handlers(cause) assert len(handlers) == 1 assert handlers[0].field == ('spec', 'field') assert handlers[0].value == 'value'
async def test_single_credentials_provided_to_vault(): info = ConnectionInfo(server='https://expected/') vault = Vault() registry = OperatorRegistry() def login_fn(**_): return info registry.register_activity_handler( fn=login_fn, id= 'login_fn', # auto-detection does not work, as it is local to the test function. activity=Activity.AUTHENTICATION, ) await authenticate( registry=registry, vault=vault, ) assert vault.readiness.is_set() assert not vault.emptiness.is_set() 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_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_on_field_with_most_kwargs(mocker, cause_factory): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') old = {'field': {'subfield': 'old'}} new = {'field': {'subfield': 'new'}} cause = cause_factory(resource=resource, reason=Reason.UPDATE, old=old, new=new, body=new) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.field('group', 'version', 'plural', field='field.subfield', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass handlers = registry.resource_changing_handlers.get_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].id == 'id/field.subfield' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when assert handlers[0].field == ('field', 'subfield') assert handlers[0].value is None assert handlers[0].old is None assert handlers[0].new is None
async def test_noreturn_handler_produces_no_credentials(): vault = Vault() registry = OperatorRegistry() def login_fn(**_): pass registry.register_activity_handler( fn=login_fn, id= 'login_fn', # auto-detection does not work, as it is local to the test function. activity=Activity.AUTHENTICATION, ) await authenticate( registry=registry, vault=vault, ) assert vault.readiness.is_set() assert not vault.emptiness.is_set() assert not vault with pytest.raises(LoginError): async for _, _ in vault: pass
def test_on_field_with_all_kwargs(mocker): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') diff = [('op', ('field', 'subfield'), 'old', 'new')] cause = mocker.MagicMock(resource=resource, reason=Reason.UPDATE, diff=diff) mocker.patch('kopf.reactor.registries.match', return_value=True) @kopf.on.field('group', 'version', 'plural', 'field.subfield', id='id', timeout=123, registry=registry, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}) def fn(**_): pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field == ('field', 'subfield') assert handlers[0].id == 'id/field.subfield' assert handlers[0].timeout == 123 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'}
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(activity, mocker): def sample_fn(**_): raise TemporaryError('to be retried', delay=123) registry = OperatorRegistry() registry.register_activity_handler(fn=sample_fn, id='id', activity=activity, retries=3) with freezegun.freeze_time() as frozen: async def sleep_or_wait_substitute(*_, **__): frozen.tick(123) sleep_or_wait = mocker.patch('kopf.engines.sleeping.sleep_or_wait', wraps=sleep_or_wait_substitute) with pytest.raises(ActivityError) as e: await run_activity( registry=registry, activity=activity, lifecycle=all_at_once, ) assert sleep_or_wait.call_count == 3 # 3 retries, 1 sleep each
def test_on_delete_with_most_kwargs(mocker, cause_factory, optional): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, reason=Reason.DELETE) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.delete('group', 'version', 'plural', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, optional=optional, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass handlers = registry.resource_changing_handlers.get_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.DELETE assert handlers[0].id == 'id' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when assert handlers[0].field is None assert handlers[0].value is None assert handlers[0].old is None assert handlers[0].new is None
def test_on_create_with_all_kwargs(mocker): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=Reason.CREATE) mocker.patch('kopf.reactor.registries.match', return_value=True) @kopf.on.create('group', 'version', 'plural', id='id', timeout=123, registry=registry, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}) def fn(**_): pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.CREATE assert handlers[0].field is None assert handlers[0].id == 'id' assert handlers[0].timeout == 123 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'}
def test_on_delete_with_all_kwargs(mocker, optional): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=Reason.DELETE) mocker.patch('kopf.reactor.registries.match', return_value=True) @kopf.on.delete('group', 'version', 'plural', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, optional=optional, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}) def fn(**_): pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.DELETE assert handlers[0].field is None assert handlers[0].id == 'id' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].cooldown == 78 # deprecated alias assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'}
def test_resources(): registry = OperatorRegistry() with pytest.deprecated_call(match=r"use @kopf.on"): registry.register_resource_watching_handler('group1', 'version1', 'plural1', some_fn) with pytest.deprecated_call(match=r"use @kopf.on"): registry.register_resource_changing_handler('group2', 'version2', 'plural2', some_fn) with pytest.deprecated_call(match=r"use @kopf.on"): registry.register_resource_watching_handler('group2', 'version2', 'plural2', some_fn) with pytest.deprecated_call(match=r"use @kopf.on"): registry.register_resource_changing_handler('group1', 'version1', 'plural1', some_fn) resources = registry.resources assert isinstance(resources, collections.abc.Collection) assert len(resources) == 2 resource1 = Resource('group1', 'version1', 'plural1') resource2 = Resource('group2', 'version2', 'plural2') assert resource1 in resources assert resource2 in resources
def test_on_update_with_all_kwargs( mocker, cause_factory): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, reason=Reason.UPDATE) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.update('group', 'version', 'plural', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass with pytest.deprecated_call(match=r"use registry.resource_changing_handlers"): handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.UPDATE assert handlers[0].field is None assert handlers[0].id == 'id' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when
def test_on_resume_with_all_kwargs(mocker, reason): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=reason, initial=True, deleted=False) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.resume('group', 'version', 'plural', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, deleted=True, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass handlers = registry.resource_changing_handlers[resource].get_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field is None assert handlers[0].id == 'id' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].deleted == True assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when
async def test_single_credentials_provided_to_vault(): 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.activity_handlers.append( ActivityHandler( fn=login_fn, id='login_fn', activity=Activity.AUTHENTICATION, errors=None, timeout=None, retries=None, backoff=None, cooldown=None, )) await authenticate( registry=registry, 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_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!"
def test_on_field_with_all_kwargs(mocker): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') diff = [('op', ('field', 'subfield'), 'old', 'new')] cause = mocker.MagicMock(resource=resource, reason=Reason.UPDATE, diff=diff) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.field('group', 'version', 'plural', 'field.subfield', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass handlers = registry.resource_changing_handlers[resource].get_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field ==('field', 'subfield') assert handlers[0].id == 'id/field.subfield' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when
async def test_errors_are_raised_aggregated(activity): def sample_fn1(**_): raise PermanentError("boo!123") def sample_fn2(**_): raise PermanentError("boo!456") registry = OperatorRegistry() registry.register_activity_handler(fn=sample_fn1, id='id1', activity=activity) registry.register_activity_handler(fn=sample_fn2, id='id2', activity=activity) with pytest.raises(ActivityError) as e: await run_activity( registry=registry, activity=activity, lifecycle=all_at_once, ) 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_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
def test_requires_finalizer_no_deletion_handler(): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') @kopf.on.create('group', 'version', 'plural', registry=registry) def fn1(**_): pass requires_finalizer = registry.resource_changing_handlers[ resource].requires_finalizer(CAUSE) assert requires_finalizer is False
def test_requires_finalizer_no_deletion_handler(): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') @kopf.on.create('group', 'version', 'plural', registry=registry) def fn1(**_): pass requires_finalizer = registry.requires_finalizer(resource=resource, body=OBJECT_BODY) assert requires_finalizer is False
def test_requires_finalizer_no_deletion_handler(): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') @kopf.on.create('group', 'version', 'plural', registry=registry) def fn1(**_): pass with pytest.deprecated_call(match=r"use registry.resource_changing_handlers"): requires_finalizer = registry.requires_finalizer(resource=resource, cause=CAUSE) assert requires_finalizer is False
def test_requires_finalizer_deletion_handler(optional, expected): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') @kopf.on.delete('group', 'version', 'plural', registry=registry, optional=optional) def fn(**_): pass with pytest.deprecated_call(match=r"use registry.resource_changing_handlers"): requires_finalizer = registry.requires_finalizer(resource=resource, cause=CAUSE) assert requires_finalizer == expected
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"
def test_requires_finalizer_no_deletion_handler(cause_factory): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, body=OBJECT_BODY) @kopf.on.create('group', 'version', 'plural', registry=registry) def fn1(**_): pass with pytest.deprecated_call(match=r"cease using the internal registries"): requires_finalizer = registry.requires_finalizer(resource=resource, cause=cause) assert requires_finalizer is False
async def test_empty_registry_produces_no_credentials(): vault = Vault() registry = OperatorRegistry() await authenticate( registry=registry, vault=vault, ) assert not vault with pytest.raises(LoginError): async for _, _ in vault: pass
def test_registry_and_settings_are_propagated(mocker): operator_mock = mocker.patch('kopf.reactor.running.operator') registry = OperatorRegistry() settings = OperatorSettings() with KopfRunner(['run', '--standalone'], registry=registry, settings=settings) as runner: pass assert runner.exit_code == 0 assert runner.exception is None assert operator_mock.called assert operator_mock.call_args[1]['registry'] is registry assert operator_mock.call_args[1]['settings'] is settings
def test_requires_finalizer_deletion_handler(optional, expected): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') @kopf.on.delete('group', 'version', 'plural', registry=registry, optional=optional) def fn(**_): pass requires_finalizer = registry.resource_changing_handlers[ resource].requires_finalizer(CAUSE) assert requires_finalizer == expected
def test_requires_finalizer_deletion_handler(optional, expected): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') @kopf.on.delete('group', 'version', 'plural', registry=registry, optional=optional) def fn(**_): pass requires_finalizer = registry.requires_finalizer(resource=resource, body=OBJECT_BODY) assert requires_finalizer == expected