def test_processing_images( self, image_file_success_state_low_rating: io.BytesIO, ) -> None: """ The number of images in the processing state is returned. """ database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, ) with MockVWS() as mock: mock.add_database(database=database) vws_client.add_target( name=uuid.uuid4().hex, width=1, image=image_file_success_state_low_rating, active_flag=True, application_metadata=None, ) _wait_for_image_numbers( vws_client=vws_client, active_images=0, inactive_images=0, failed_images=0, processing_images=1, )
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_to_dict(self, high_quality_image: io.BytesIO) -> None: """ It is possible to dump a database to a dictionary and load it back. """ database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, ) # We test a database with a target added. with MockVWS() as mock: mock.add_database(database=database) vws_client.add_target( name='example', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) database_dict = database.to_dict() # The dictionary is JSON dump-able assert json.dumps(database_dict) new_database = VuforiaDatabase.from_dict(database_dict=database_dict) assert new_database == database
def test_not_image( self, vuforia_database: VuforiaDatabase, target_id: str, vws_client: VWS, ) -> None: """ If the given image is not an image file then a `BadImage` result is returned. """ not_image_data = b'not_image_data' image_data_encoded = base64.b64encode(not_image_data).decode('ascii') vws_client.wait_for_target_processed(target_id=target_id) response = update_target( vuforia_database=vuforia_database, data={'image': image_data_encoded}, target_id=target_id, ) assert_vws_failure( response=response, status_code=HTTPStatus.UNPROCESSABLE_ENTITY, result_code=ResultCodes.BAD_IMAGE, )
def test_default(self, image_file_failed_state: io.BytesIO) -> None: """ By default, targets in the mock take 0.5 seconds to be processed. """ database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, ) with MockVWS() as mock: mock.add_database(database=database) target_id = vws_client.add_target( name='example', width=1, image=image_file_failed_state, active_flag=True, application_metadata=None, ) start_time = datetime.now() while True: target_details = vws_client.get_target_record( target_id=target_id, ) status = target_details.status if status != TargetStatuses.PROCESSING: elapsed_time = datetime.now() - start_time # There is a race condition in this test - if it starts to # fail, maybe extend the acceptable range. assert elapsed_time < timedelta(seconds=0.55) assert elapsed_time > timedelta(seconds=0.49) return
def test_same_name_given( self, image_file_success_state_low_rating: io.BytesIO, vuforia_database: VuforiaDatabase, vws_client: VWS, ) -> None: """ Updating a target with its own name does not give an error. """ name = 'example' target_id = vws_client.add_target( name=name, width=1, image=image_file_success_state_low_rating, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) response = update_target( vuforia_database=vuforia_database, data={'name': name}, target_id=target_id, ) assert_vws_failure( response=response, status_code=HTTPStatus.OK, result_code=ResultCodes.SUCCESS, ) target_details = vws_client.get_target_record(target_id=target_id) assert target_details.target_record.name == name
def test_not_base64_encoded_processable( self, vuforia_database: VuforiaDatabase, target_id: str, not_base64_encoded_processable: str, vws_client: VWS, ) -> None: """ Some strings which are not valid base64 encoded strings are allowed as an image without getting a "Fail" response. This is because Vuforia treats them as valid base64, but then not a valid image. """ vws_client.wait_for_target_processed(target_id=target_id) response = update_target( vuforia_database=vuforia_database, data={'image': not_base64_encoded_processable}, target_id=target_id, ) assert_vws_failure( response=response, status_code=HTTPStatus.UNPROCESSABLE_ENTITY, result_code=ResultCodes.BAD_IMAGE, )
def test_no_fields_given( self, mock_database: VuforiaDatabase, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: """ It is possible to give no update fields. """ 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) commands = [ 'update-target', '--target-id', target_id, '--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 == ''
def wait_for_target_processed( server_access_key: str, server_secret_key: str, target_id: str, seconds_between_requests: float, base_vws_url: str, timeout_seconds: float, ) -> None: """ Wait for a target to be "processed". This is done by polling the VWS API. """ vws_client = VWS( server_access_key=server_access_key, server_secret_key=server_secret_key, base_vws_url=base_vws_url, ) try: vws_client.wait_for_target_processed( target_id=target_id, seconds_between_requests=seconds_between_requests, timeout_seconds=timeout_seconds, ) except TargetProcessingTimeout: click.echo(f'Timeout of {timeout_seconds} seconds reached.', err=True) sys.exit(1)
def test_delete_target( mock_database: VuforiaDatabase, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: """ It is possible to delete a target. """ runner = CliRunner() target_id = vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) assert vws_client.list_targets() == [target_id] commands = [ 'delete-target', '--target-id', target_id, '--server-access-key', mock_database.server_access_key, '--server-secret-key', mock_database.server_secret_key, ] vws_client.wait_for_target_processed(target_id=target_id) result = runner.invoke(vws_group, commands, catch_exceptions=False) assert result.exit_code == 0 assert result.stdout == '' assert vws_client.list_targets() == []
def test_wait_for_target_processed( self, mock_database: VuforiaDatabase, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: """ It is possible to use a command to wait for a target to be processed. """ runner = CliRunner() target_id = vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) report = vws_client.get_target_summary_report(target_id=target_id) assert report.status == TargetStatuses.PROCESSING commands = [ 'wait-for-target-processed', '--target-id', target_id, '--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 == '' report = vws_client.get_target_summary_report(target_id=target_id) assert report.status != TargetStatuses.PROCESSING
def test_not_real_id( self, vws_client: VWS, endpoint: Endpoint, target_id: str, ) -> None: """ A `NOT_FOUND` error is returned when an endpoint is given a target ID of a target which does not exist. """ if not endpoint.prepared_request.path_url.endswith(target_id): return vws_client.wait_for_target_processed(target_id=target_id) vws_client.delete_target(target_id=target_id) session = requests.Session() response = session.send( # type: ignore request=endpoint.prepared_request, ) assert_vws_failure( response=response, status_code=HTTPStatus.NOT_FOUND, result_code=ResultCodes.UNKNOWN_TARGET, )
def test_bad_target_request( self, high_quality_image: io.BytesIO, vws_client: VWS, ) -> None: """ The ``request_usage`` count does not increase with each request to the target API, even if it is a bad request. """ report = vws_client.get_database_summary_report() original_request_usage = report.request_usage with pytest.raises(Fail) as exc: vws_client.add_target( name='example', width=-1, image=high_quality_image, active_flag=True, application_metadata=None, ) assert exc.value.response.status_code == HTTPStatus.BAD_REQUEST report = vws_client.get_database_summary_report() new_request_usage = report.request_usage assert new_request_usage == original_request_usage
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_metadata_too_large( self, vuforia_database: VuforiaDatabase, vws_client: VWS, target_id: str, ) -> None: """ A base64 encoded string of greater than 1024 * 1024 bytes is too large for application metadata. """ metadata = b'a' * (self._MAX_METADATA_BYTES + 1) metadata_encoded = base64.b64encode(metadata).decode('ascii') vws_client.wait_for_target_processed(target_id=target_id) response = update_target( vuforia_database=vuforia_database, data={'application_metadata': metadata_encoded}, target_id=target_id, ) assert_vws_failure( response=response, status_code=HTTPStatus.UNPROCESSABLE_ENTITY, result_code=ResultCodes.METADATA_TOO_LARGE, )
def test_get_target_summary_report( self, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: """ Details of a target are returned by ``get_target_summary_report``. """ date = '2018-04-25' target_name = uuid.uuid4().hex with freeze_time(date): target_id = vws_client.add_target( name=target_name, width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) result = vws_client.get_target_summary_report(target_id=target_id) expected_report = TargetSummaryReport( status=TargetStatuses.SUCCESS, database_name=result.database_name, target_name=target_name, upload_date=datetime.date(2018, 4, 25), active_flag=True, tracking_rating=result.tracking_rating, total_recos=0, current_month_recos=0, previous_month_recos=0, ) assert result == expected_report
def test_name_invalid( self, name: str, target_id: str, vuforia_database: VuforiaDatabase, vws_client: VWS, status_code: int, result_code: ResultCodes, ) -> None: """ A target's name must be a string of length 0 < N < 65. """ vws_client.wait_for_target_processed(target_id=target_id) response = update_target( vuforia_database=vuforia_database, data={'name': name}, target_id=target_id, ) assert_vws_failure( response=response, status_code=status_code, result_code=result_code, )
def test_get_target_record( self, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: """ Details of a target are returned by ``get_target_record``. """ target_id = vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) result = vws_client.get_target_record(target_id=target_id) expected_target_record = TargetRecord( target_id=target_id, active_flag=True, name='x', width=1, tracking_rating=-1, reco_rating='', ) assert result.target_record == expected_target_record assert result.status == TargetStatuses.PROCESSING
def test_image_valid( self, vuforia_database: VuforiaDatabase, image_files_failed_state: io.BytesIO, target_id: str, vws_client: VWS, ) -> None: """ JPEG and PNG files in the RGB and greyscale color spaces are allowed. """ image_file = image_files_failed_state image_data = image_file.read() image_data_encoded = base64.b64encode(image_data).decode('ascii') vws_client.wait_for_target_processed(target_id=target_id) response = update_target( vuforia_database=vuforia_database, data={'image': image_data_encoded}, target_id=target_id, ) assert_vws_response( response=response, status_code=HTTPStatus.OK, result_code=ResultCodes.SUCCESS, )
def test_target_name_exist( vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: """ A ``TargetNameExist`` exception is raised after adding two targets with the same name. """ vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) with pytest.raises(TargetNameExist) as exc: vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) assert exc.value.response.status_code == HTTPStatus.FORBIDDEN assert exc.value.target_name == 'x'
def test_not_base64_encoded_not_processable( self, vuforia_database: VuforiaDatabase, vws_client: VWS, target_id: str, not_base64_encoded_not_processable: str, ) -> None: """ Some strings which are not valid base64 encoded strings are not processable by Vuforia, and then when given as an image Vuforia returns a "Fail" response. """ vws_client.wait_for_target_processed(target_id=target_id) response = update_target( vuforia_database=vuforia_database, data={'image': not_base64_encoded_not_processable}, target_id=target_id, ) assert_vws_failure( response=response, status_code=HTTPStatus.UNPROCESSABLE_ENTITY, result_code=ResultCodes.FAIL, )
def test_request_time_too_skewed( vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: """ A ``RequestTimeTooSkewed`` exception is raised when the request time is more than five minutes different from the server time. """ target_id = vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) vws_max_time_skew = 60 * 5 leeway = 10 time_difference_from_now = vws_max_time_skew + leeway # We use a custom tick because we expect the following: # # * At least one time check when creating the request # * At least one time check when processing the request # # >= 1 ticks are acceptable. with freeze_time(auto_tick_seconds=time_difference_from_now): with pytest.raises(RequestTimeTooSkewed) as exc: vws_client.get_target_record(target_id=target_id) assert exc.value.response.status_code == HTTPStatus.FORBIDDEN
def test_custom(self, image_file_failed_state: io.BytesIO) -> None: """ It is possible to set a custom processing time. """ database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, ) with MockVWS(processing_time_seconds=0.1) as mock: mock.add_database(database=database) target_id = vws_client.add_target( name='example', width=1, image=image_file_failed_state, active_flag=True, application_metadata=None, ) start_time = datetime.now() while True: target_details = vws_client.get_target_record( target_id=target_id, ) status = target_details.status if status != TargetStatuses.PROCESSING: elapsed_time = datetime.now() - start_time assert elapsed_time < timedelta(seconds=0.15) assert elapsed_time > timedelta(seconds=0.09) return
def test_authentication_failure(high_quality_image: io.BytesIO) -> None: """ An ``AuthenticationFailure`` exception is raised when the server access key exists but the server secret key is incorrect, or when a client key is incorrect. """ database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key='a', ) with MockVWS() as mock: mock.add_database(database=database) with pytest.raises(AuthenticationFailure) as exc: vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED
def test_deleted_existing_target_name( self, image_file_failed_state: io.BytesIO, vuforia_database: VuforiaDatabase, vws_client: VWS, ) -> None: """ A target can be added with the name of a deleted target. """ image_data = image_file_failed_state.read() image_data_encoded = base64.b64encode(image_data).decode('ascii') data = { 'name': 'example_name', 'width': 1, 'image': image_data_encoded, } response = add_target_to_vws( vuforia_database=vuforia_database, data=data, ) target_id = response.json()['target_id'] vws_client.wait_for_target_processed(target_id=target_id) vws_client.delete_target(target_id=target_id) response = add_target_to_vws( vuforia_database=vuforia_database, data=data, ) assert_success(response=response)
def processing_time_seconds( vuforia_database: VuforiaDatabase, image: io.BytesIO, ) -> float: """ Return the time taken to process a target in the database. """ vws_client = VWS( server_access_key=vuforia_database.server_access_key, server_secret_key=vuforia_database.server_secret_key, ) target_id = vws_client.add_target( name='example', width=1, image=image, active_flag=True, application_metadata=None, ) start_time = datetime.now() while (vws_client.get_target_record( target_id=target_id).status == TargetStatuses.PROCESSING): pass return (datetime.now() - start_time).total_seconds()
def test_success_status( self, image_file_success_state_low_rating: io.BytesIO, vws_client: VWS, ) -> None: """ When a random, large enough image is given, the status changes from 'processing' to 'success' after some time. The mock is much more lenient than the real implementation of VWS. The test image does not prove that what is counted as a success in the mock will be counted as a success in the real implementation. """ target_id = vws_client.add_target( name='example', width=1, image=image_file_success_state_low_rating, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) target_details = vws_client.get_target_record(target_id=target_id) assert target_details.status == TargetStatuses.SUCCESS # Tracking rating is between 0 and 5 when status is 'success' tracking_rating = target_details.target_record.tracking_rating assert tracking_rating in range(6) # The tracking rating stays stable across requests target_details = vws_client.get_target_record(target_id=target_id) new_tracking_rating = target_details.target_record.tracking_rating assert new_tracking_rating == tracking_rating
def test_width_invalid( self, vuforia_database: VuforiaDatabase, vws_client: VWS, width: Any, target_id: str, ) -> None: """ The width must be a number greater than zero. """ vws_client.wait_for_target_processed(target_id=target_id) target_details = vws_client.get_target_record(target_id=target_id) original_width = target_details.target_record.width response = update_target( vuforia_database=vuforia_database, data={'width': width}, target_id=target_id, ) assert_vws_failure( response=response, status_code=HTTPStatus.BAD_REQUEST, result_code=ResultCodes.FAIL, ) target_details = vws_client.get_target_record(target_id=target_id) assert target_details.target_record.width == original_width
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_deleted( self, image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: """ Deleted targets are not shown in the summary. """ target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, image=image_file_failed_state, active_flag=True, application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) vws_client.delete_target(target_id=target_id) _wait_for_image_numbers( vws_client=vws_client, active_images=0, inactive_images=0, failed_images=0, processing_images=0, )