def test_signature_mismatch_missing(mocker: ptm.MockFixture) -> None: async def foo( limit: t.Optional[int], page: t.Optional[float], include_extra: t.Optional[bool], ) -> pipeline.Response: ... with pytest.raises(handler.InvalidHandlerError) as err: handler._resolve( handler=foo, operation=operation, request_processor=mocker.Mock(), response_processor=mocker.Mock(), ) assert err.value.operation_id == 'TestAnalysisParameters' assert len(err.value) == 1 assert 'id' in err.value assert err.value['id'] == 'missing'
def test_ok(self, fake_columns, fake_row_model, mocker: MockFixture): fake_columns_keys = [col.key for col in fake_columns] fake_values = {key: mocker.Mock() for key in fake_columns_keys} fake_row_model.__table__.columns = fake_columns for key in fake_columns_keys: setattr(fake_row_model, key, fake_values[key]) for key, value in fake_row_model: assert key in fake_columns_keys assert value == fake_values[key]
async def test_acquire_ok(self, model_manager: BaseModelManager, mocker: MockFixture): fake_sql = mocker.Mock() fake_fetch = mocker.Mock() mocked_run_query_with_connection = mocker.patch.object( model_manager, 'run_query_with_connection', CoroutineMock() ) mocked_connection = CoroutineMock() mocker.patch.object( BaseModelManager, 'engine', mocker.PropertyMock( return_value=mocker.Mock( acquire=async_context_mock(return_value=mocked_connection) ) ) ) await model_manager.run_query(fake_sql, fake_fetch) mocked_run_query_with_connection.assert_called_once_with(mocked_connection, fake_sql, fake_fetch)
def get_cached_data(key, data_fn, mocker: MockFixture, name='data', force=False): data_fn_mock = mocker.Mock(wraps=data_fn, name='data_fn') result = cache_data(key, PREFIX + '_' + name, data_fn_mock, force_regenerate=force) return (result, data_fn_mock.call_count)
async def test_ok_none_result(self, fake_row_model, mocker: MockFixture): fake_pk_key = 'foo' fake_kwargs = {'bar': 'baz'} fake_row_model.__table__.primary_key = [mocker.Mock(key=fake_pk_key)] mocked_check = mocker.patch.object(fake_row_model, 'check') mocked_set_values = mocker.patch.object(fake_row_model, '_set_values') mocked_model_manager = mocker.patch.object( fake_row_model, 'model_manager', update=CoroutineMock(return_value=None) ) setattr(fake_row_model, fake_pk_key, mocker.Mock()) await fake_row_model.update(**fake_kwargs) mocked_check.assert_called_once_with() mocked_model_manager.update.assert_called_once_with( where_list=[(fake_row_model.pk_column == fake_row_model._pk_value)], fetch=False, **fake_kwargs ) mocked_set_values.assert_not_called()
def test_restart_deployments(self, mocker: MockFixture) -> None: mocked_aps_v1_api = mocker.Mock(spec=AppsV1Api) mocker.patch("opta.core.kubernetes.AppsV1Api", return_value=mocked_aps_v1_api) mocker.patch("opta.core.kubernetes.load_kube_config") mocked_deploy = mocker.Mock(spec=V1Deployment) mocked_deploy.metadata = mocker.Mock() mocked_deploy.metadata.name = "deploy-name" mocked_deployments = mocker.Mock(spec=V1DeploymentList) mocked_deployments.items = [mocked_deploy] mocked_aps_v1_api.list_namespaced_deployment.return_value = mocked_deployments namespace = "ns-name" restart_deployments(namespace) mocked_aps_v1_api.list_namespaced_deployment.assert_called_once_with( namespace=namespace) # check that the deployment to restart was patched mocked_aps_v1_api.patch_namespaced_deployment.assert_called_once_with( "deploy-name", namespace, mocker.ANY)
async def test_ok_no_results(self, model_manager: BaseModelManager, mocker: MockFixture): fake_where_list = mocker.Mock() mocked_get_item = mocker.patch.object(model_manager, 'get_item', CoroutineMock(return_value=None)) mocked_row_class = mocker.patch.object(model_manager, 'row_class') compared_result = await model_manager.get_instance(fake_where_list) expected_result = None mocked_get_item.assert_called_once_with(fake_where_list) mocked_row_class.assert_not_called() assert compared_result == expected_result
def test_get_all_remote_configs_buckets_not_present( self, mocker: MockFixture) -> None: mock_s3_client_instance = mocker.Mock(spec=S3Client) mocker.patch("opta.core.aws.boto3.client", return_value=mock_s3_client_instance) mocker.patch("opta.core.aws.AWS._get_opta_buckets", return_value=[]) mock_s3_client_instance.list_objects.return_value = {} mock_download_remote_blob = mocker.patch( "opta.core.aws.AWS._download_remote_blob") AWS().get_all_remote_configs() mock_s3_client_instance.list_objects.assert_not_called() mock_download_remote_blob.assert_not_called()
async def test_ok(self, mocker: MockFixture, fake_data_provider: BaseDataProvider): data_length = 10 fake_data = [mocker.Mock() for _ in range(0, data_length)] fake_include_settings = mocker.Mock() fake_include_params = mocker.Mock() fake_item_include_data = mocker.Mock() mocked_get_item_include_data = mocker.patch.object( fake_data_provider, 'get_item_include_data', CoroutineMock(return_value=fake_item_include_data)) result = await fake_data_provider.get_list_include_data( fake_data, fake_include_settings, fake_include_params) mocked_get_item_include_data.assert_has_calls([ mocker.call(fake_data[i], fake_include_settings, fake_include_params) for i in range(0, data_length) ]) assert result == [ fake_item_include_data for _ in range(0, data_length) ]
def test_deploy_basic(mocker: MockFixture) -> None: mocker.patch("opta.commands.deploy.opta_acquire_lock") mocked_os_path_exists = mocker.patch("opta.utils.os.path.exists") mocked_os_path_exists.return_value = True mock_tf_download_state = mocker.patch( "opta.commands.deploy.Terraform.download_state", return_value=True) mocker.patch( "opta.commands.deploy.Terraform.tf_lock_details", return_value=(False, ""), ) mock_push = mocker.patch("opta.commands.deploy.push_image", return_value=("local_digest", "local_tag")) mock_apply = mocker.patch("opta.commands.deploy._apply") mocker.patch("opta.commands.deploy.__check_layer_and_image", return_value=True) mocked_layer_class = mocker.patch("opta.commands.deploy.Layer") mocked_layer = mocker.Mock(spec=Layer) mocked_layer_class.load_from_yaml.return_value = mocked_layer mocked_layer.org_name = "dummy_org_name" mocked_layer.name = "dummy_name" mock_terraform_outputs = mocker.patch( "opta.commands.deploy.Terraform.get_outputs", return_value={"docker_repo_url": "blah"}, ) mocker.patch("opta.commands.deploy.opta_release_lock") runner = CliRunner() result = runner.invoke(cli, ["deploy", "-i", "local_image:local_tag"]) assert result.exit_code == 0 mocked_layer.validate_required_path_dependencies.assert_called_once() mock_tf_download_state.assert_called_once_with(mocked_layer) mock_push.assert_called_once_with( image="local_image:local_tag", config="opta.yaml", env=None, tag=None, input_variables={}, ) mock_terraform_outputs.assert_called_once_with(mocked_layer) mock_apply.assert_called_once_with( config="opta.yaml", env=None, refresh=False, image_tag=None, test=False, auto_approve=False, image_digest="local_digest", detailed_plan=False, local=False, input_variables={}, ) mock_terraform_outputs.assert_called_once_with(mocker.ANY)
async def test_ok(self, test_method, test_constant, model_manager: BaseModelManager, mocker: MockFixture): fake_sql = mocker.Mock() mocked_const = mocker.patch.object(model_manager, test_constant) mocked_run_query = mocker.patch.object(model_manager, 'run_query', CoroutineMock()) compared_result = await getattr(model_manager, test_method)(fake_sql) expected_result = mocked_run_query.return_value assert compared_result == expected_result mocked_run_query.assert_called_once_with(sql=fake_sql, fetch=mocked_const)
def test_missing_return_annotation(mocker: ptm.MockFixture) -> None: async def test(): # type: ignore ... with pytest.raises(handler.InvalidHandlerError) as err: handler._resolve( handler=test, operation=_make_operation({ '204': { 'description': 'It will not work anyway', }, }), request_processor=mocker.Mock(), response_processor=mocker.Mock(), ) assert err assert err.value assert 1 == len(err.value) assert 'return' in err.value assert 'missing' == err.value['return']
def test_check_layer_and_image_xyz_image_image_flag( mocker: MockFixture) -> None: mock_k8s_service_module = mocker.Mock(spec=Module) mock_k8s_service_module.name = "test-app" mock_k8s_service_module.type = "k8s-service" mock_k8s_service_module.data = {"image": "xyz"} mock_layer = mocker.Mock(spec=Layer) mock_layer.variables = {} mock_layer.name = "test" mock_layer.org_name = "test_org" mock_layer.cloud = "aws" mock_layer.modules = [mock_k8s_service_module] mock_layer.get_module_by_type.return_value = [mock_k8s_service_module] mocker.patch("opta.commands.deploy.opta_acquire_lock") mocker.patch("opta.commands.deploy.pre_check") mocker.patch("opta.utils.os.path.exists", return_value=True) mock_layer_class = mocker.patch("opta.commands.deploy.Layer") mock_layer_class.load_from_yaml.return_value = mock_layer mock_check_layer_and_image = mocker.patch( "opta.commands.deploy.__check_layer_and_image", side_effect=UserErrors( "Do not pass any image. Image xyz already present in configuration." ), ) mocker.patch("opta.commands.deploy.opta_release_lock") runner = CliRunner() result = runner.invoke(cli, ["deploy", "-i", "app:main"]) assert result.exit_code == 1 mock_check_layer_and_image.assert_called_once_with(mock_layer, "app:main") assert (type( result.exception ) == UserErrors and result.exception.args == UserErrors( "Do not pass any image. Image xyz already present in configuration."). args)
async def test_transaction_cm(self, async_context_manager, mocker: MockFixture): exc_type, exc_val, exc_tb = None, None, None fake_transaction_cm = async_context_manager(mocker.Mock()) fake_connection = mocker.Mock(begin=mocker.Mock(return_value=fake_transaction_cm)) fake_conn_cm = async_context_manager(fake_connection) fake_model_mgr = mocker.Mock( transaction_connection=None, engine=mocker.Mock(acquire=mocker.Mock(return_value=fake_conn_cm)) ) mocker.spy(fake_conn_cm, '__aenter__') mocker.spy(fake_conn_cm, '__aexit__') mocker.spy(fake_transaction_cm, '__aenter__') mocker.spy(fake_transaction_cm, '__aexit__') transaction = _TransactionContextManager(fake_model_mgr) async with transaction as model_objects: assert model_objects == fake_model_mgr fake_model_mgr.engine.acquire.assert_called_once_with() fake_conn_cm.__aenter__.assert_called_once_with() fake_transaction_cm.__aenter__.assert_called_once_with() fake_connection.begin.assert_called_once_with() assert fake_model_mgr.transaction_connection == fake_connection fake_transaction_cm.__aexit__.assert_called_once_with(exc_type, exc_val, exc_tb) fake_conn_cm.__aexit__.assert_called_once_with(exc_type, exc_val, exc_tb) assert fake_model_mgr.transaction_connection is None
def test_list_persistent_volume_claims(self, mocker: MockFixture) -> None: mocked_core_v1_api = mocker.Mock(spec=CoreV1Api) mocker.patch("opta.core.kubernetes.CoreV1Api", return_value=mocked_core_v1_api) mocker.patch("opta.core.kubernetes.load_kube_config") mocked_claim_opta = mocker.Mock(spec=V1PersistentVolumeClaim) mocked_claim_opta.metadata = mocker.Mock() mocked_claim_opta.metadata.name = "opta-claim-0" mocked_claim_non_opta = mocker.Mock(spec=V1PersistentVolumeClaim) mocked_claim_non_opta.metadata = mocker.Mock() mocked_claim_non_opta.metadata.name = "my-org-claim-0" mocked_claim_list = mocker.Mock(spec=V1PersistentVolumeClaimList) mocked_claim_list.items = [mocked_claim_opta, mocked_claim_non_opta] mocked_core_v1_api.list_persistent_volume_claim_for_all_namespaces.return_value = ( mocked_claim_list) mocked_core_v1_api.list_namespaced_persistent_volume_claim.return_value = ( mocked_claim_list) # call with no parameter, expect all_namespaces method called results = list_persistent_volume_claims() mocked_core_v1_api.list_persistent_volume_claim_for_all_namespaces.assert_called_once_with( ) assert len(results) == 2 # call with namespace, expect namespaced method called results = list_persistent_volume_claims(namespace="hello") mocked_core_v1_api.list_namespaced_persistent_volume_claim.assert_called_once_with( "hello") # check opta_managed filtering works results = list_persistent_volume_claims(opta_managed=True) assert len(results) == 1
def test_incorrect_cookies_type( cookies_type: t.Type[t.Any], mocker: ptm.MockFixture, ) -> None: test_returns = te.TypedDict( # type: ignore 'RV_Bad_Cookies', { 'http_code': int, 'cookies': cookies_type }, # type: ignore ) async def test() -> test_returns: ... with pytest.raises(handler.InvalidHandlerError) as err: handler._resolve( handler=test, operation=_make_operation({ '204': { 'description': 'Incorrect return type', }, }), request_processor=mocker.Mock(), response_processor=mocker.Mock(), ) assert err assert err.value assert 1 == len(err.value) assert 'return.cookies' in err.value assert (f'expected [' f'typing.Mapping[str, typing.Any],' f'typing.Dict[str, typing.Any],' f'typing_extensions.TypedDict], ' f'but got ' f'{get_type_repr.get_repr(cookies_type)}' ) == err.value['return.cookies']
def test_omitted_return_code_single_oas_resp( response_code: t.Union[str, te.Literal['default']], mocker: ptm.MockFixture, ) -> None: test_returns = te.TypedDict( # type: ignore 'MissingHttpCode', {'body': t.Dict[str, int]}, ) async def test() -> test_returns: ... handler._resolve( handler=test, operation=_make_operation({ f'{response_code}': { 'description': 'I am alone', }, }), request_processor=mocker.Mock(), response_processor=mocker.Mock(), )
def test_init(self, mocker: MockFixture) -> None: mocker.patch("opta.core.terraform.nice_run") # Calling terraform apply should also call terraform init tf_init = mocker.patch("opta.core.terraform.Terraform.init") fake_layer = mocker.Mock(spec=Layer) fake_layer.cloud = "blah" Terraform.apply(layer=fake_layer) assert tf_init.call_count == 1 # Calling terraform plan should also call terraform init Terraform.plan(layer=fake_layer) assert tf_init.call_count == 2
async def test_ok_with_extra_params(self, patched_producer, mocker: MockFixture): mocker.patch.object(patched_producer, 'is_connected', True) fake_payload = mocker.Mock() fake_mandatory = mocker.Mock() fake_immediate = mocker.Mock() await patched_producer.publish_message(payload=fake_payload, mandatory=fake_mandatory, immediate=fake_immediate) patched_producer._init_connection.assert_not_called() patched_producer.serialize_data.assert_called_once_with(fake_payload) patched_producer._channel.basic_publish.assert_called_once_with( payload=patched_producer.serialize_data.return_value, exchange_name=patched_producer._exchange, routing_key=patched_producer._routing_key, properties=BaseProducer.DEFAULT_PROPERTIES, mandatory=fake_mandatory, immediate=fake_immediate, )
def test_signature_set_oas_cookies( the_type: t.Type[t.Any], caplog: logging.LogCaptureFixture, mocker: ptm.MockFixture, ) -> None: async def foo(name: str, cookies: the_type) -> pipeline.Response: # type: ignore ... hdrl = handler._resolve( foo, next(filter(lambda op: op.id == 'cookies_op', operations)), request_processor=mocker.Mock(), response_processor=mocker.Mock(), ) assert id(hdrl.user_handler) == id(foo) assert hdrl.cookie_params assert ('csrftoken', 'csrftoken') in hdrl.cookie_params.items() assert ('debug', 'debug') in hdrl.cookie_params.items() assert '"cookies" found both in signature and operation' in caplog.messages
def test_invalid_cookies_type( op_id: str, the_type: t.Type[t.Any], mocker: ptm.MockFixture, ) -> None: async def foo(name: str, cookies: the_type) -> pipeline.Response: # type: ignore ... with pytest.raises(handler.InvalidHandlerError) as err: handler._resolve( foo, next(filter(lambda op: op.id == op_id, operations)), request_processor=mocker.Mock(), response_processor=mocker.Mock(), ) assert len(err.value) == 1 assert 'cookies' in err.value assert err.value['cookies'] == ( f'expected [typing.Mapping[str, typing.Any],' f'typing.Dict[str, typing.Any],typing_extensions.TypedDict]' f', but got {get_type_repr.get_repr(the_type)}' )
async def test_ok(self, amqp: BaseAmqp, mocker: MockFixture): fake_transport = mocker.Mock() fake_protocol = CoroutineMock() mocked_from_url = mocker.patch( 'aioamqp.from_url', CoroutineMock(return_value=(fake_transport, fake_protocol))) await amqp.connect() mocked_from_url.assert_called_once_with(amqp._url, loop=amqp._loop) fake_protocol.channel.assert_called_once_with() assert amqp._transport == fake_transport assert amqp._protocol == fake_protocol
def mocked_layer(mocker: MockFixture) -> Any: mocked_layer_class = mocker.patch("opta.commands.apply.Layer") mocked_layer = mocker.Mock(spec=Layer) mocked_layer.variables = {} mocked_layer.name = "blah" mocked_layer.org_name = "blahorg" mocked_layer.cloud = "aws" mocked_layer.get_event_properties = mocker.Mock(return_value={}) mocked_layer.gen_providers = lambda x: { "provider": { "aws": { "region": "us-east-1" } } } mocked_layer_class.load_from_yaml.return_value = mocked_layer mocked_layer.parent = None mocked_layer.original_spec = "" mocked_module = mocker.Mock() mocked_module.aliased_type = "dummy" mocked_layer.modules = [mocked_module] return mocked_layer
def test_get_modules(self, mocker: MockFixture) -> None: mocker.patch("opta.core.terraform.Terraform.download_state", return_value=True) mocker.patch( "opta.core.terraform.Terraform.get_state", return_value={ "resources": [ { "module": "module.redis", "mode": "managed", "type": "aws_elasticache_replication_group", "name": "redis_cluster", }, { "module": "module.redis", "mode": "data", "type": "aws_eks_cluster_auth", "name": "k8s", }, { "module": "module.redis", "mode": "managed", "type": "aws_elasticache_replication_group", "name": "redis_cluster", }, { "module": "module.doc_db", "mode": "data", "type": "aws_security_group", "name": "security_group", }, { "mode": "data", "type": "aws_caller_identity", "name": "provider" }, { "mode": "data", "type": "aws_eks_cluster_auth", "name": "k8s" }, ] }, ) mocked_layer = mocker.Mock(spec=Layer) mocked_layer.name = "blah" mocked_layer.cloud = "blah" assert {"redis", "doc_db"} == Terraform.get_existing_modules(mocked_layer)
async def test_ok(self, mocker: MockFixture, fake_model_data_provider: ModelDataProvider): fake_filter_name = mocker.Mock() mocked_sub = mocker.patch('re.sub') compared_result = fake_model_data_provider.remove_comparison_suffix( fake_filter_name) expected_result = mocked_sub.return_value assert compared_result == expected_result mocked_sub.assert_called_once_with( '__({})$'.format(fake_model_data_provider.COMPARISON_SIGNS), '', fake_filter_name)
def test_shell_with_invalid_shell(mocker: MockFixture) -> None: mocked_os_path_exists = mocker.patch("opta.utils.os.path.exists") mocked_os_path_exists.return_value = True mocked_layer_class = mocker.patch("opta.commands.shell.Layer") mocked_layer = mocker.Mock(spec=Layer) mocked_layer.name = "layer_name" mocked_layer_class.load_from_yaml.return_value = mocked_layer mocked_core_v1_api_class = mocker.patch("opta.commands.shell.CoreV1Api") mocked_core_v1_api = mocker.Mock(spec=CoreV1Api) mocked_core_v1_api_class.return_value = mocked_core_v1_api mocked_v1_pod = mocker.Mock(spec=V1Pod) mocked_v1_pod.metadata.name = "pod_name" mocked_v1_pod_list = mocker.Mock(spec=V1PodList) mocked_v1_pod_list.items = [mocked_v1_pod] mocked_core_v1_api.list_namespaced_pod.return_value = mocked_v1_pod_list runner = CliRunner() result = runner.invoke(cli, ["shell", "-t", "shh"]) assert result.exit_code == 2
def test_get_all_remote_configs_configuration_not_present( self, mocker: MockFixture) -> None: mock_s3_client_instance = mocker.Mock(spec=S3Client) mocker.patch("opta.core.aws.boto3.client", return_value=mock_s3_client_instance) mocker.patch("opta.core.aws.AWS._get_opta_buckets", return_value=["test"]) mock_s3_client_instance.list_objects.return_value = {} mock_download_remote_blob = mocker.patch( "opta.core.aws.AWS._download_remote_blob") AWS().get_all_remote_configs() mock_s3_client_instance.list_objects.assert_called_once_with( Bucket="test", Prefix="opta_config/", Delimiter="/") mock_download_remote_blob.assert_not_called()
def test_signature_all_bad_type(mocker: ptm.MockFixture) -> None: async def foo( id: float, limit: t.Optional[t.Union[float, int]], page: t.Optional[t.AbstractSet[bool]], include_extra: t.Union[int, str], ) -> pipeline.Response: ... with pytest.raises(handler.InvalidHandlerError) as err: handler._resolve( handler=foo, operation=operation, request_processor=mocker.Mock(), response_processor=mocker.Mock(), ) assert err.value.operation_id == 'TestAnalysisParameters' assert len(err.value) == 4 for mismatch in err.value: actual_msg = err.value[mismatch.param_name] expected_msg = None if mismatch.param_name == 'id': expected_msg = 'expected [str], but got float' elif mismatch.param_name == 'limit': expected_msg = ('expected [typing.Optional[int]], but got ' 'typing.Optional[typing.Union[float, int]]') elif mismatch.param_name == 'page': expected_msg = ('expected [typing.Optional[float]], but got ' 'typing.Optional[typing.AbstractSet[bool]]') elif mismatch.param_name == 'includeExtra': expected_msg = ('expected [typing.Optional[bool]], but got ' 'typing.Union[int, str]') assert expected_msg is not None assert actual_msg == expected_msg
def test_aws_download_state(self, mocker: MockFixture) -> None: layer = mocker.Mock(spec=Layer) layer.gen_providers.return_value = { "terraform": { "backend": { "s3": { "bucket": "opta-tf-state-test-dev1", "key": "dev1", "dynamodb_table": "opta-tf-state-test-dev1", "region": "us-east-1", } } } } layer.name = "blah" layer.cloud = "aws" mocker.patch("opta.core.terraform.Terraform._aws_verify_storage", return_value=True) patched_init = mocker.patch("opta.core.terraform.Terraform.init", return_value=True) mocked_s3_client = mocker.Mock() mocked_boto_client = mocker.patch("opta.core.terraform.boto3.client", return_value=mocked_s3_client) read_data = '{"a": 1}' mocked_file = mocker.mock_open(read_data=read_data) mocker.patch("opta.core.terraform.os.remove") mocked_open = mocker.patch("opta.core.terraform.open", mocked_file) assert Terraform.download_state(layer) layer.gen_providers.assert_called_once_with(0) mocked_s3_client.download_file.assert_called_once_with( Bucket="opta-tf-state-test-dev1", Key="dev1", Filename="./tmp.tfstate") mocked_open.assert_called_once_with("./tmp.tfstate", "r") patched_init.assert_not_called() mocked_boto_client.assert_called_once_with("s3", config=mocker.ANY)
def test_get_acr_auth_info(mocker: MockFixture) -> None: mocked_layer = mocker.Mock(spec=Layer) mocked_layer.root.return_value = mocked_layer mocked_get_terraform_output = mocker.patch( "opta.commands.push.get_terraform_outputs", return_value={"acr_name": "blah"} ) mocked_nice_run_output = mocker.Mock() mocked_nice_run = mocker.patch( "opta.commands.push.nice_run", return_value=mocked_nice_run_output ) mocked_nice_run_output.stdout = "dummy_token" assert get_acr_auth_info(mocked_layer) == ( "00000000-0000-0000-0000-000000000000", "dummy_token", ) mocked_get_terraform_output.assert_called_once_with(mocked_layer) mocked_nice_run.assert_has_calls( [ mocker.call( [ "az", "acr", "login", "--name", "blah", "--expose-token", "--output", "tsv", "--query", "accessToken", ], check=True, capture_output=True, ), ] )