def test_custom( self, high_quality_image: io.BytesIO, monkeypatch: MonkeyPatch, ) -> None: """ It is possible to use set a custom amount of time that it takes for the Query API on the mock to process that a target has been deleted. """ # We choose a low time for a quick test. query_processes_deletion = 0.1 database = VuforiaDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + '/databases' requests.post(url=databases_url, json=database.to_dict()) monkeypatch.setenv( name='DELETION_PROCESSING_SECONDS', value=str(query_processes_deletion), ) time_taken = process_deletion_seconds( high_quality_image=high_quality_image, vuforia_database=database, ) expected = query_processes_deletion assert abs(expected - time_taken) < self.LEEWAY
def _enable_use_mock_vuforia( working_database: VuforiaDatabase, inactive_database: VuforiaDatabase, monkeypatch: MonkeyPatch, ) -> Generator: assert monkeypatch working_database = VuforiaDatabase( database_name=working_database.database_name, server_access_key=working_database.server_access_key, server_secret_key=working_database.server_secret_key, client_access_key=working_database.client_access_key, client_secret_key=working_database.client_secret_key, ) inactive_database = VuforiaDatabase( state=States.PROJECT_INACTIVE, database_name=inactive_database.database_name, server_access_key=inactive_database.server_access_key, server_secret_key=inactive_database.server_secret_key, client_access_key=inactive_database.client_access_key, client_secret_key=inactive_database.client_secret_key, ) with MockVWS(processing_time_seconds=0.2) as mock: mock.add_database(database=working_database) mock.add_database(database=inactive_database) yield
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_default(self) -> None: """ By default, the database has a random name. """ database_details = VuforiaDatabase() other_database_details = VuforiaDatabase() assert (database_details.database_name != other_database_details.database_name)
def _enable_use_docker_in_memory( working_database: VuforiaDatabase, inactive_database: VuforiaDatabase, monkeypatch: MonkeyPatch, ) -> Generator: # We set ``wsgi.input_terminated`` to ``True`` so that when going through # ``requests``, the Flask applications # have the given ``Content-Length`` headers and the given data in # ``request.headers`` and ``request.data``. # # We do not set these in the Flask application itself. # This is because when running the Flask application, if this is set, # reading ``request.data`` hangs. # # Therefore, when running the real Flask application, the behavior is not # the same as the real Vuforia. # This is documented as a difference in the documentation for this package. VWS_FLASK_APP.config['TERMINATE_WSGI_INPUT'] = True CLOUDRECO_FLASK_APP.config['TERMINATE_WSGI_INPUT'] = True target_manager_base_url = 'http://example.com' monkeypatch.setenv( name='TARGET_MANAGER_BASE_URL', value=target_manager_base_url, ) with requests_mock.Mocker(real_http=False) as mock: add_flask_app_to_mock( mock_obj=mock, flask_app=VWS_FLASK_APP, base_url='https://vws.vuforia.com', ) add_flask_app_to_mock( mock_obj=mock, flask_app=CLOUDRECO_FLASK_APP, base_url='https://cloudreco.vuforia.com', ) add_flask_app_to_mock( mock_obj=mock, flask_app=TARGET_MANAGER_FLASK_APP, base_url=target_manager_base_url, ) databases_url = target_manager_base_url + '/databases' databases = requests.get(url=databases_url).json() for database in databases: database_name = database['database_name'] requests.delete(url=databases_url + '/' + database_name) requests.post(url=databases_url, json=working_database.to_dict()) requests.post(url=databases_url, json=inactive_database.to_dict()) yield
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_to_dict_deleted(self, high_quality_image: io.BytesIO) -> None: """ It is possible to dump a deleted target 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, ) with MockVWS() as mock: mock.add_database(database=database) 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) vws_client.delete_target(target_id=target_id) (target, ) = database.targets target_dict = target.to_dict() # The dictionary is JSON dump-able assert json.dumps(target_dict) new_target = Target.from_dict(target_dict=target_dict) assert new_target.delete_date == target.delete_date
def test_project_inactive( high_quality_image: io.BytesIO, tmp_path: Path, ) -> None: """ An error is given if the project is inactive and the desired action cannot be taken because of this. """ new_file = tmp_path / uuid.uuid4().hex image_data = high_quality_image.getvalue() new_file.write_bytes(data=image_data) database = VuforiaDatabase(state=States.PROJECT_INACTIVE) with MockVWS() as mock: mock.add_database(database=database) runner = CliRunner(mix_stderr=False) commands = [ 'add-target', '--name', 'foo', '--width', '0.1', '--image', str(new_file), '--server-access-key', database.server_access_key, '--server-secret-key', database.server_secret_key, ] result = runner.invoke(vws_group, commands, catch_exceptions=False) assert result.exit_code == 1 expected_stderr = ( 'Error: The project associated with the given keys is inactive.\n') assert result.stderr == expected_stderr assert result.stdout == ''
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_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_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_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 fixture_mock_database() -> Iterator[VuforiaDatabase]: """ Yield a mock ``VuforiaDatabase``. """ with MockVWS() as mock: database = VuforiaDatabase() mock.add_database(database=database) yield database
def test_duplicate_keys(self) -> None: """ It is not possible to have multiple databases with matching keys. """ with MockVWS() as mock: mock.add_database(database=VuforiaDatabase(server_access_key='1')) with pytest.raises(ValueError) as exc: mock.add_database( database=VuforiaDatabase(server_access_key='1'), ) expected_message = ( 'All server access keys must be unique. ' 'There is already a database with the server access key "1".' ) assert str(exc.value) == expected_message with MockVWS() as mock: mock.add_database(database=VuforiaDatabase(server_secret_key='1')) with pytest.raises(ValueError) as exc: mock.add_database( database=VuforiaDatabase(server_secret_key='1'), ) expected_message = ( 'All server secret keys must be unique. ' 'There is already a database with the server secret key "1".' ) assert str(exc.value) == expected_message with MockVWS() as mock: mock.add_database(database=VuforiaDatabase(client_access_key='1')) with pytest.raises(ValueError) as exc: mock.add_database( database=VuforiaDatabase(client_access_key='1'), ) expected_message = ( 'All client access keys must be unique. ' 'There is already a database with the client access key "1".' ) assert str(exc.value) == expected_message with MockVWS() as mock: mock.add_database(database=VuforiaDatabase(client_secret_key='1')) with pytest.raises(ValueError) as exc: mock.add_database( database=VuforiaDatabase(client_secret_key='1'), ) expected_message = ( 'All client secret keys must be unique. ' 'There is already a database with the client secret key "1".' ) assert str(exc.value) == expected_message
def test_custom_timeout( self, high_quality_image: io.BytesIO, ) -> None: """ It is possible to set a maximum timeout. """ runner = CliRunner(mix_stderr=False) with MockVWS(processing_time_seconds=0.5) as mock: mock_database = VuforiaDatabase() mock.add_database(database=mock_database) vws_client = VWS( server_access_key=mock_database.server_access_key, server_secret_key=mock_database.server_secret_key, ) 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, '--timeout-seconds', '0.1', '--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.stderr == 'Timeout of 0.1 seconds reached.\n' commands = [ 'wait-for-target-processed', '--target-id', target_id, '--timeout-seconds', '0.5', '--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 report = vws_client.get_target_summary_report(target_id=target_id) assert report.status != TargetStatuses.PROCESSING
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() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + '/databases' requests.post(url=databases_url, json=database.to_dict()) time_taken = processing_time_seconds( vuforia_database=database, image=image_file_failed_state, ) expected = 0.5 assert abs(expected - time_taken) < self.LEEWAY
def test_custom_seconds_between_requests( self, high_quality_image: io.BytesIO, ) -> None: """ It is possible to customize the time waited between polling requests. """ runner = CliRunner() with MockVWS(processing_time_seconds=0.5) as mock: mock_database = VuforiaDatabase() mock.add_database(database=mock_database) vws_client = VWS( server_access_key=mock_database.server_access_key, server_secret_key=mock_database.server_secret_key, ) target_id = vws_client.add_target( name='x', width=1, image=high_quality_image, active_flag=True, application_metadata=None, ) commands = [ 'wait-for-target-processed', '--target-id', target_id, '--seconds-between-requests', '0.3', '--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_database_summary_report() expected_requests = ( # Add target request 1 + # Database summary request 1 + # Initial request 1 + # Request after 0.3 seconds - not processed # This assumes that there is less than 0.2 seconds taken # between the start of the target processing and the start of # waiting for the target to be processed. 1 + # Request after 0.6 seconds - processed 1) # At the time of writing there is a bug which prevents request # usage from being tracked so we cannot track this. expected_requests = 0 assert report.request_usage == expected_requests
def get_all_databases() -> Set[VuforiaDatabase]: """ Get all database objects from the task manager back-end. """ target_manager_base_url = os.environ['TARGET_MANAGER_BASE_URL'] response = requests.get(url=f'{target_manager_base_url}/databases') return { VuforiaDatabase.from_dict(database_dict=database_dict) for database_dict in response.json() }
def test_duplicate_keys(self) -> None: """ It is not possible to have multiple databases with matching keys. """ database = VuforiaDatabase( server_access_key='1', server_secret_key='2', client_access_key='3', client_secret_key='4', database_name='5', ) bad_server_access_key_db = VuforiaDatabase(server_access_key='1') bad_server_secret_key_db = VuforiaDatabase(server_secret_key='2') bad_client_access_key_db = VuforiaDatabase(client_access_key='3') bad_client_secret_key_db = VuforiaDatabase(client_secret_key='4') bad_database_name_db = VuforiaDatabase(database_name='5') server_access_key_conflict_error = ( 'All server access keys must be unique. ' 'There is already a database with the server access key "1".') server_secret_key_conflict_error = ( 'All server secret keys must be unique. ' 'There is already a database with the server secret key "2".') client_access_key_conflict_error = ( 'All client access keys must be unique. ' 'There is already a database with the client access key "3".') client_secret_key_conflict_error = ( 'All client secret keys must be unique. ' 'There is already a database with the client secret key "4".') database_name_conflict_error = ( 'All names must be unique. ' 'There is already a database with the name "5".') databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + '/databases' requests.post(url=databases_url, json=database.to_dict()) for bad_database, expected_message in ( (bad_server_access_key_db, server_access_key_conflict_error), (bad_server_secret_key_db, server_secret_key_conflict_error), (bad_client_access_key_db, client_access_key_conflict_error), (bad_client_secret_key_db, client_secret_key_conflict_error), (bad_database_name_db, database_name_conflict_error), ): response = requests.post( url=databases_url, json=bad_database.to_dict(), ) assert response.status_code == HTTPStatus.CONFLICT assert response.text == expected_message
def test_default( self, high_quality_image: io.BytesIO, ) -> None: """ By default it takes three seconds for the Query API on the mock to process that a target has been deleted. The real Query API takes between seven and thirty seconds. See ``test_query`` for more information. """ database = VuforiaDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + '/databases' requests.post(url=databases_url, json=database.to_dict()) time_taken = process_deletion_seconds( high_quality_image=high_quality_image, vuforia_database=database, ) expected = 3 assert abs(expected - time_taken) < self.LEEWAY
def vuforia_database() -> VuforiaDatabase: """ Return VWS credentials from environment variables. """ credentials: VuforiaDatabase = VuforiaDatabase( database_name=os.environ['VUFORIA_TARGET_MANAGER_DATABASE_NAME'], server_access_key=os.environ['VUFORIA_SERVER_ACCESS_KEY'], server_secret_key=os.environ['VUFORIA_SERVER_SECRET_KEY'], client_access_key=os.environ['VUFORIA_CLIENT_ACCESS_KEY'], client_secret_key=os.environ['VUFORIA_CLIENT_SECRET_KEY'], state=States.WORKING, ) return credentials
def test_custom_seconds_between_requests( self, high_quality_image: io.BytesIO, ) -> None: """ It is possible to customize the time waited between polling requests. """ with MockVWS(processing_time_seconds=0.5) 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, seconds_between_requests=0.3, ) report = vws_client.get_database_summary_report() expected_requests = ( # Add target request 1 + # Database summary request 1 + # Initial request 1 + # Request after 0.3 seconds - not processed # This assumes that there is less than 0.2 seconds taken # between the start of the target processing and the start of # waiting for the target to be processed. 1 + # Request after 0.6 seconds - processed 1 ) # At the time of writing there is a bug which prevents request # usage from being tracked so we cannot track this. expected_requests = 0 assert report.request_usage == expected_requests
def test_custom( self, image_file_failed_state: io.BytesIO, monkeypatch: MonkeyPatch, ) -> None: """ It is possible to set a custom processing time. """ monkeypatch.setenv( name='PROCESSING_TIME_SECONDS', value='0.1', ) database = VuforiaDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + '/databases' requests.post(url=databases_url, json=database.to_dict()) time_taken = processing_time_seconds( vuforia_database=database, image=image_file_failed_state, ) expected = 0.1 assert abs(expected - time_taken) < self.LEEWAY
def test_custom(self, image_file_failed_state: io.BytesIO) -> None: """ It is possible to set a custom processing time. """ database = VuforiaDatabase() with MockVWS(processing_time_seconds=0.1) as mock: mock.add_database(database=database) time_taken = processing_time_seconds( vuforia_database=database, image=image_file_failed_state, ) expected = 0.1 assert abs(expected - time_taken) < self.LEEWAY
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() with MockVWS() as mock: mock.add_database(database=database) time_taken = processing_time_seconds( vuforia_database=database, image=image_file_failed_state, ) expected = 0.5 assert abs(expected - time_taken) < self.LEEWAY
def inactive_database() -> VuforiaDatabase: """ Return VWS credentials for an inactive project from environment variables. """ credentials: VuforiaDatabase = VuforiaDatabase( database_name=os.environ[ 'INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME' ], server_access_key=os.environ['INACTIVE_VUFORIA_SERVER_ACCESS_KEY'], server_secret_key=os.environ['INACTIVE_VUFORIA_SERVER_SECRET_KEY'], client_access_key=os.environ['INACTIVE_VUFORIA_CLIENT_ACCESS_KEY'], client_secret_key=os.environ['INACTIVE_VUFORIA_CLIENT_SECRET_KEY'], state=States.PROJECT_INACTIVE, ) return credentials
def test_base_vwq_url(high_quality_image: io.BytesIO, tmp_path: Path) -> None: """ It is possible to use query a target to a database under a custom VWQ URL. """ runner = CliRunner(mix_stderr=False) base_vwq_url = 'http://example.com' new_file = tmp_path / uuid.uuid4().hex image_data = high_quality_image.getvalue() new_file.write_bytes(data=image_data) with MockVWS(base_vwq_url=base_vwq_url) as mock: mock_database = VuforiaDatabase() mock.add_database(database=mock_database) vws_client = VWS( server_access_key=mock_database.server_access_key, server_secret_key=mock_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) commands = [ str(new_file), '--client-access-key', mock_database.client_access_key, '--client-secret-key', mock_database.client_secret_key, '--base-vwq-url', base_vwq_url, ] result = runner.invoke( vuforia_cloud_reco, commands, catch_exceptions=False, ) assert result.exit_code == 0 [match] = yaml.load(result.stdout, Loader=yaml.FullLoader) assert match['target_id'] == target_id
def test_with_no_processing_time( self, high_quality_image: io.BytesIO, ) -> None: """ This exercises some otherwise untouched code. """ database = VuforiaDatabase() with MockVWS(query_processes_deletion_seconds=0) as mock: mock.add_database(database=database) time_taken = recognize_deletion_seconds( high_quality_image=high_quality_image, vuforia_database=database, ) expected = 0.2 assert abs(expected - time_taken) < self.LEEWAY
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