def _wait_for_deletion_processed( image: io.BytesIO, vuforia_database: VuforiaDatabase, ) -> None: """ Wait until the query endpoint "recognizes" the deletion of all targets with an image matching the given image. That is, wait until querying the given image returns a result with no targets. """ _wait_for_deletion_recognized( image=image, vuforia_database=vuforia_database, ) cloud_reco_client = CloudRecoService( client_access_key=vuforia_database.client_access_key, client_secret_key=vuforia_database.client_secret_key, ) while True: try: cloud_reco_client.query(image=image) except MatchProcessing: continue return
def test_query_request( self, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, vws_client: VWS, ) -> None: """ The ``*_recos`` counts seem to be delayed by a significant amount of time. We therefore test that they exist, are integers and do not change between quick requests. """ target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) report_before = vws_client.get_database_summary_report() cloud_reco_client.query(image=high_quality_image) report_after = vws_client.get_database_summary_report() assert report_before.total_recos == report_after.total_recos assert (report_before.current_month_recos == report_after.current_month_recos) assert (report_before.previous_month_recos == report_after.previous_month_recos)
def test_update_target( self, vws_client: VWS, high_quality_image: io.BytesIO, different_high_quality_image: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: """ It is possible to update a target. """ old_name = uuid.uuid4().hex old_width = random.uniform(a=0.01, b=50) target_id = vws_client.add_target( name=old_name, width=old_width, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) [matching_target] = cloud_reco_client.query(image=high_quality_image) assert matching_target.target_id == target_id query_target_data = matching_target.target_data assert query_target_data is not None query_metadata = query_target_data.application_metadata assert query_metadata is None new_name = uuid.uuid4().hex new_width = random.uniform(a=0.01, b=50) new_application_metadata = base64.b64encode(b'a').decode('ascii') vws_client.update_target( target_id=target_id, name=new_name, width=new_width, active_flag=True, image=different_high_quality_image, application_metadata=new_application_metadata, ) vws_client.wait_for_target_processed(target_id=target_id) [ matching_target, ] = cloud_reco_client.query(image=different_high_quality_image) assert matching_target.target_id == target_id query_target_data = matching_target.target_data assert query_target_data is not None query_metadata = query_target_data.application_metadata assert query_metadata == new_application_metadata vws_client.update_target( target_id=target_id, active_flag=False, ) target_details = vws_client.get_target_record(target_id=target_id) assert target_details.target_record.name == new_name assert target_details.target_record.width == new_width assert not target_details.target_record.active_flag
def test_image_too_large( cloud_reco_client: CloudRecoService, png_too_large: io.BytesIO, ) -> None: """ A ``RequestEntityTooLarge`` exception is raised if an image which is too large is given. """ with pytest.raises(RequestEntityTooLarge): cloud_reco_client.query(image=png_too_large)
def test_image_too_large( cloud_reco_client: CloudRecoService, png_too_large: io.BytesIO, ) -> None: """ A ``ConnectionErrorPossiblyImageTooLarge`` exception is raised if an image which is too large is given. """ with pytest.raises(ConnectionErrorPossiblyImageTooLarge) as exc: cloud_reco_client.query(image=png_too_large) assert isinstance(exc.value, requests.ConnectionError)
def test_recognition( self, vws_client: VWS, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ The recognition counts stay at 0 even after recognitions. """ target_id = vws_client.add_target( name='example', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) results = cloud_reco_client.query(image=high_quality_image) [result] = results assert result.target_id == target_id report = vws_client.get_target_summary_report(target_id=target_id) assert report.status == TargetStatuses.SUCCESS assert report.total_recos == 0 assert report.current_month_recos == 0 assert report.previous_month_recos == 0
def test_default( self, vws_client: VWS, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ By default, target data is only returned in the top match. """ target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) target_id_2 = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) vws_client.wait_for_target_processed(target_id=target_id_2) top_match, second_match = cloud_reco_client.query( image=high_quality_image, max_num_results=2, ) assert top_match.target_data is not None assert second_match.target_data is None
def test_all( self, vws_client: VWS, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ When ``CloudRecoIncludeTargetData.ALL`` is given, target data is returned in all matches. """ target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) target_id_2 = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) vws_client.wait_for_target_processed(target_id=target_id_2) top_match, second_match = cloud_reco_client.query( image=high_quality_image, max_num_results=2, include_target_data=CloudRecoIncludeTargetData.ALL, ) assert top_match.target_data is not None assert second_match.target_data is not None
def test_custom_base_url(self, high_quality_image: io.BytesIO) -> None: """ It is possible to use query a target to a database under a custom VWQ URL. """ base_vwq_url = 'http://example.com' with MockVWS(base_vwq_url=base_vwq_url) as mock: database = VuforiaDatabase() mock.add_database(database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, ) target_id = vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, base_vwq_url=base_vwq_url, ) matches = cloud_reco_client.query(image=high_quality_image) assert len(matches) == 1 match = matches[0] assert match.target_id == target_id
def test_default( self, vws_client: VWS, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ By default the maximum number of results is 1. """ target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) target_id_2 = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) vws_client.wait_for_target_processed(target_id=target_id_2) matches = cloud_reco_client.query(image=high_quality_image) assert len(matches) == 1
def test_give_no_details(self, high_quality_image: io.BytesIO) -> None: """ It is possible to create a database without giving any data. """ databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + '/databases' response = requests.post(url=databases_url, json={}) assert response.status_code == HTTPStatus.CREATED data = response.json() assert data['targets'] == [] assert data['state_name'] == 'WORKING' assert 'database_name' in data.keys() vws_client = VWS( server_access_key=data['server_access_key'], server_secret_key=data['server_secret_key'], ) cloud_reco_client = CloudRecoService( client_access_key=data['client_access_key'], client_secret_key=data['client_secret_key'], ) assert not vws_client.list_targets() assert not cloud_reco_client.query(image=high_quality_image)
def test_query_request( self, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, vws_client: VWS, ) -> None: """ The ``request_usage`` count does not increase with each query. """ report = vws_client.get_database_summary_report() original_request_usage = report.request_usage cloud_reco_client.query(image=high_quality_image) report = vws_client.get_database_summary_report() new_request_usage = report.request_usage # The request usage goes up for the database summary request, not the # query. assert new_request_usage == original_request_usage
def test_authentication_failure(high_quality_image: io.BytesIO) -> None: """ An ``AuthenticationFailure`` exception is raised when the client access key exists but the client secret key is incorrect. """ database = VuforiaDatabase() cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key='a', ) with MockVWS() as mock: mock.add_database(database=database) with pytest.raises(AuthenticationFailure) as exc: cloud_reco_client.query(image=high_quality_image) assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED
def test_inactive_project(high_quality_image: io.BytesIO) -> None: """ An ``InactiveProject`` exception is raised when querying an inactive database. """ database = VuforiaDatabase(state=States.PROJECT_INACTIVE) with MockVWS() as mock: mock.add_database(database=database) cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, ) with pytest.raises(InactiveProject) as exc: cloud_reco_client.query(image=high_quality_image) assert exc.value.response.status_code == HTTPStatus.FORBIDDEN
def test_too_many_max_results( cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ A ``MaxNumResultsOutOfRange`` error is raised if the given ``max_num_results`` is out of range. """ with pytest.raises(MaxNumResultsOutOfRange) as exc: cloud_reco_client.query( image=high_quality_image, max_num_results=51, ) expected_value = ( "Integer out of range (51) in form data part 'max_result'. " 'Accepted range is from 1 to 50 (inclusive).') assert str(exc.value) == exc.value.response.text == expected_value
def test_no_matches( self, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ An empty list is returned if there are no matches. """ result = cloud_reco_client.query(image=high_quality_image) assert result == []
def test_match_processing( vws_client: VWS, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ A ``MatchProcessing`` exception is raised when a target in processing is matched. """ vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) with pytest.raises(MatchProcessing) as exc: cloud_reco_client.query(image=high_quality_image) assert exc.value.response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
def test_base_exception( vws_client: VWS, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ ``CloudRecoException``s has a response property. """ vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) with pytest.raises(CloudRecoException) as exc: cloud_reco_client.query(image=high_quality_image) assert exc.value.response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
def test_active_matching_targets_delete_processing( vws_client: VWS, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ A ``ActiveMatchingTargetsDeleteProcessing`` exception is raised when a target which has recently been deleted is matched. """ target_id = vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) vws_client.delete_target(target_id=target_id) time.sleep(0.2) with pytest.raises(ActiveMatchingTargetsDeleteProcessing): cloud_reco_client.query(image=high_quality_image)
def test_bad_secret_key_query( self, vuforia_database: VuforiaDatabase, high_quality_image: io.BytesIO, ) -> None: """ If the client secret key given is incorrect, an ``UNAUTHORIZED`` response is returned. """ cloud_reco_client = CloudRecoService( client_access_key=vuforia_database.client_access_key, client_secret_key='example', ) with pytest.raises(cloud_reco_exceptions.AuthenticationFailure) as exc: cloud_reco_client.query(image=high_quality_image) response = exc.value.response assert_vwq_failure( response=response, status_code=HTTPStatus.UNAUTHORIZED, content_type='application/json', cache_control=None, www_authenticate='VWS', connection='keep-alive', ) assert response.json().keys() == {'transaction_id', 'result_code'} assert_valid_transaction_id(response=response) assert_valid_date_header(response=response) result_code = response.json()['result_code'] transaction_id = response.json()['transaction_id'] assert result_code == ResultCodes.AUTHENTICATION_FAILURE.value # The separators are inconsistent and we test this. expected_text = ('{"transaction_id":' f'"{transaction_id}",' f'"result_code":"{result_code}"' '}') assert response.text == expected_text
def test_add_target( self, mock_database: VuforiaDatabase, vws_client: VWS, high_quality_image: io.BytesIO, tmp_path: Path, cloud_reco_client: CloudRecoService, ) -> None: """ It is possible to add a target. """ runner = CliRunner() new_file = tmp_path / uuid.uuid4().hex name = uuid.uuid4().hex image_data = high_quality_image.getvalue() new_file.write_bytes(data=image_data) width = random.uniform(a=0.01, b=50) commands = [ 'add-target', '--name', name, '--width', str(width), '--image', str(new_file), '--server-access-key', mock_database.server_access_key, '--server-secret-key', mock_database.server_secret_key, ] result = runner.invoke(vws_group, commands, catch_exceptions=False) assert result.exit_code == 0 target_id = result.stdout.strip() target_details = vws_client.get_target_record(target_id=target_id) target_record = target_details.target_record assert target_record.name == name assert target_record.width == width assert target_record.active_flag is True vws_client.wait_for_target_processed(target_id=target_id) [query_result] = cloud_reco_client.query(image=high_quality_image) assert query_result.target_id == target_id target_data = query_result.target_data assert target_data is not None assert target_data.application_metadata is None
def test_custom_metadata( self, mock_database: VuforiaDatabase, cloud_reco_client: CloudRecoService, vws_client: VWS, tmp_path: Path, high_quality_image: io.BytesIO, ) -> None: """ Custom metadata can be given. """ runner = CliRunner() new_file = tmp_path / uuid.uuid4().hex name = uuid.uuid4().hex image_data = high_quality_image.getvalue() new_file.write_bytes(data=image_data) application_metadata = uuid.uuid4().hex metadata_bytes = application_metadata.encode('ascii') base64_encoded_metadata_bytes = base64.b64encode(metadata_bytes) base64_encoded_metadata = base64_encoded_metadata_bytes.decode('ascii') commands = [ 'add-target', '--name', name, '--width', '0.1', '--image', str(new_file), '--application-metadata', base64_encoded_metadata, '--server-access-key', mock_database.server_access_key, '--server-secret-key', mock_database.server_secret_key, ] result = runner.invoke(vws_group, commands, catch_exceptions=False) assert result.exit_code == 0 target_id = result.stdout.strip() vws_client.wait_for_target_processed(target_id=target_id) [query_result] = cloud_reco_client.query(image=high_quality_image) assert query_result.target_id == target_id target_data = query_result.target_data assert target_data is not None assert target_data.application_metadata == base64_encoded_metadata
def test_match( self, vws_client: VWS, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ Details of matching targets are returned. """ target_id = vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) [matching_target] = cloud_reco_client.query(image=high_quality_image) assert matching_target.target_id == target_id
def test_add_target( self, vws_client: VWS, high_quality_image: io.BytesIO, active_flag: bool, application_metadata: Optional[bytes], cloud_reco_client: CloudRecoService, ) -> None: """ No exception is raised when adding one target. """ name = 'x' width = 1 if application_metadata is None: encoded_metadata = None else: encoded_metadata_bytes = base64.b64encode(application_metadata) encoded_metadata = encoded_metadata_bytes.decode('utf-8') target_id = vws_client.add_target( name=name, width=width, image=high_quality_image, application_metadata=encoded_metadata, active_flag=active_flag, ) target_record = vws_client.get_target_record( target_id=target_id, ).target_record assert target_record.name == name assert target_record.width == width assert target_record.active_flag is active_flag vws_client.wait_for_target_processed(target_id=target_id) matching_targets = cloud_reco_client.query(image=high_quality_image) if active_flag: [matching_target] = matching_targets assert matching_target.target_id == target_id assert matching_target.target_data is not None query_metadata = matching_target.target_data.application_metadata assert query_metadata == encoded_metadata else: assert matching_targets == []
def test_custom( self, vws_client: VWS, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: """ It is possible to set a custom ``max_num_results``. """ target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) target_id_2 = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) target_id_3 = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) vws_client.wait_for_target_processed(target_id=target_id_2) vws_client.wait_for_target_processed(target_id=target_id_3) matches = cloud_reco_client.query( image=high_quality_image, max_num_results=2, ) assert len(matches) == 2
def vuforia_cloud_reco( image: Path, client_access_key: str, client_secret_key: str, max_num_results: int, include_target_data: CloudRecoIncludeTargetData, base_vwq_url: str, ) -> None: """ Make a request to the Vuforia Cloud Recognition Service API. """ client = CloudRecoService( client_access_key=client_access_key, client_secret_key=client_secret_key, base_vwq_url=base_vwq_url, ) query_result = client.query( image=io.BytesIO(image.read_bytes()), max_num_results=max_num_results, include_target_data=include_target_data, ) query_result_dict_list = [dataclasses.asdict(res) for res in query_result] yaml_list = yaml.dump(query_result_dict_list) click.echo(yaml_list)
def _wait_for_deletion_recognized( image: io.BytesIO, vuforia_database: VuforiaDatabase, ) -> None: """ Wait until the query endpoint "recognizes" the deletion of all targets with an image matching the given image. That is, wait until querying the given image does not return a result with targets. """ cloud_reco_client = CloudRecoService( client_access_key=vuforia_database.client_access_key, client_secret_key=vuforia_database.client_secret_key, ) while True: try: results = cloud_reco_client.query(image=image) except ActiveMatchingTargetsDeleteProcessing: return if not results: return
def test_build_and_run( high_quality_image: io.BytesIO, custom_bridge_network: Network, ) -> None: """ It is possible to build Docker images which combine to make a working mock application. """ repository_root = Path(__file__).parent.parent.parent client = docker.from_env() dockerfile_dir = repository_root / 'src/mock_vws/_flask_server/dockerfiles' target_manager_dockerfile = (dockerfile_dir / 'target_manager' / 'Dockerfile') vws_dockerfile = dockerfile_dir / 'vws' / 'Dockerfile' vwq_dockerfile = dockerfile_dir / 'vwq' / 'Dockerfile' random = uuid.uuid4().hex target_manager_tag = 'vws-mock-target-manager:latest-' + random vws_tag = 'vws-mock-vws:latest-' + random vwq_tag = 'vws-mock-vwq:latest-' + random try: target_manager_image, _ = client.images.build( path=str(repository_root), dockerfile=str(target_manager_dockerfile), tag=target_manager_tag, ) except docker.errors.BuildError as exc: full_log = '\n'.join( [item['stream'] for item in exc.build_log if 'stream' in item], ) # If this assertion fails, it may be useful to look at the other # properties of ``exc``. assert 'no matching manifest for windows/amd64' in exc.msg, full_log reason = 'We do not currently support using Windows containers.' pytest.skip(reason) vws_image, _ = client.images.build( path=str(repository_root), dockerfile=str(vws_dockerfile), tag=vws_tag, ) vwq_image, _ = client.images.build( path=str(repository_root), dockerfile=str(vwq_dockerfile), tag=vwq_tag, ) database = VuforiaDatabase() target_manager_container_name = 'vws-mock-target-manager-' + random target_manager_base_url = f'http://{target_manager_container_name}:5000' target_manager_container = client.containers.run( image=target_manager_image, detach=True, name=target_manager_container_name, publish_all_ports=True, network=custom_bridge_network.name, ) vws_container = client.containers.run( image=vws_image, detach=True, name='vws-mock-vws-' + random, publish_all_ports=True, network=custom_bridge_network.name, environment={'TARGET_MANAGER_BASE_URL': target_manager_base_url}, ) vwq_container = client.containers.run( image=vwq_image, detach=True, name='vws-mock-vwq-' + random, publish_all_ports=True, network=custom_bridge_network.name, environment={'TARGET_MANAGER_BASE_URL': target_manager_base_url}, ) target_manager_container.reload() target_manager_port_attrs = target_manager_container.attrs[ 'NetworkSettings']['Ports'] target_manager_host_ip = target_manager_port_attrs['5000/tcp'][0]['HostIp'] target_manager_host_port = target_manager_port_attrs['5000/tcp'][0][ 'HostPort'] vws_container.reload() vws_port_attrs = vws_container.attrs['NetworkSettings']['Ports'] vws_host_ip = vws_port_attrs['5000/tcp'][0]['HostIp'] vws_host_port = vws_port_attrs['5000/tcp'][0]['HostPort'] vwq_container.reload() vwq_port_attrs = vwq_container.attrs['NetworkSettings']['Ports'] vwq_host_ip = vwq_port_attrs['5000/tcp'][0]['HostIp'] vwq_host_port = vwq_port_attrs['5000/tcp'][0]['HostPort'] target_manager_host_url = ( f'http://{target_manager_host_ip}:{target_manager_host_port}') response = requests.post( url=f'{target_manager_host_url}/databases', json=database.to_dict(), ) assert response.status_code == HTTPStatus.CREATED vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, base_vws_url=f'http://{vws_host_ip}:{vws_host_port}', ) target_id = vws_client.add_target( name='example', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, base_vwq_url=f'http://{vwq_host_ip}:{vwq_host_port}', ) matching_targets = cloud_reco_client.query(image=high_quality_image) for container in (target_manager_container, vws_container, vwq_container): container.stop() container.remove() assert matching_targets[0].target_id == target_id
def test_update_target( self, mock_database: VuforiaDatabase, vws_client: VWS, high_quality_image: io.BytesIO, tmp_path: Path, cloud_reco_client: CloudRecoService, different_high_quality_image: io.BytesIO, ) -> None: """ It is possible to update a target. """ runner = CliRunner(mix_stderr=False) old_name = uuid.uuid4().hex old_width = random.uniform(a=0.01, b=50) target_id = vws_client.add_target( name=old_name, width=old_width, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) new_application_metadata = base64.b64encode(b'a').decode('ascii') new_name = uuid.uuid4().hex new_width = random.uniform(a=0.01, b=50) new_image_file = tmp_path / uuid.uuid4().hex new_image_data = different_high_quality_image.getvalue() new_image_file.write_bytes(data=new_image_data) commands = [ 'update-target', '--target-id', target_id, '--name', new_name, '--width', str(new_width), '--image', str(new_image_file), '--active-flag', 'true', '--application-metadata', new_application_metadata, '--server-access-key', mock_database.server_access_key, '--server-secret-key', mock_database.server_secret_key, ] result = runner.invoke(vws_group, commands, catch_exceptions=False) assert result.exit_code == 0 assert result.stdout == '' vws_client.wait_for_target_processed(target_id=target_id) [ matching_target, ] = cloud_reco_client.query(image=different_high_quality_image) assert matching_target.target_id == target_id query_target_data = matching_target.target_data assert query_target_data is not None query_metadata = query_target_data.application_metadata assert query_metadata == new_application_metadata commands = [ 'update-target', '--target-id', target_id, '--active-flag', 'false', '--server-access-key', mock_database.server_access_key, '--server-secret-key', mock_database.server_secret_key, ] result = runner.invoke(vws_group, commands, catch_exceptions=False) assert result.exit_code == 0 assert result.stdout == '' target_details = vws_client.get_target_record(target_id=target_id) target_record = target_details.target_record assert not target_record.active_flag assert target_record.name == new_name assert target_record.width == new_width assert not target_record.active_flag