From f2f3e31792267cf7772b83976afe3b1dce511b1d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 6 Oct 2026 02:41:43 +0100 Subject: [PATCH 1/2] Use module-level test functions for Karva preparation --- tests/test_query.py | 1401 ++++++++++++++-------------- tests/test_vumark.py | 375 ++++---- tests/test_vws_commands.py | 1783 ++++++++++++++++++------------------ 3 files changed, 1774 insertions(+), 1785 deletions(-) diff --git a/tests/test_query.py b/tests/test_query.py index f1b67a6a..d507e185 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -17,304 +17,244 @@ from vws_cli.query import vuforia_cloud_reco +# Tests for making image queries. -class TestQuery: - """Tests for making image queries.""" - - @staticmethod - def test_no_matches( - *, - mock_database: CloudDatabase, - tmp_path: Path, - high_quality_image: io.BytesIO, - ) -> None: - """An empty list is returned if there are no matches.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - assert result_data == [] - - @staticmethod - def test_matches( - *, - tmp_path: Path, - high_quality_image: io.BytesIO, - vws_client: VWS, - mock_database: CloudDatabase, - ) -> None: - """Details of matching targets are shown.""" - name = uuid.uuid4().hex - target_id = vws_client.add_target( - name=name, - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - vws_client.wait_for_target_processed(target_id=target_id) - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - [matching_target] = result_data - target_timestamp: object = matching_target["target_data"][ - "target_timestamp" - ] - expected_result_data = { - "target_data": { - "application_metadata": None, - "name": name, - "target_timestamp": target_timestamp, - }, - "target_id": target_id, - } - assert matching_target == expected_result_data - - @staticmethod - def test_image_file_is_dir( - *, - tmp_path: Path, - mock_database: CloudDatabase, - ) -> None: - """ - An appropriate error is given if the given image file path - points to a - directory. - """ - runner = CliRunner() - commands = [ - str(object=tmp_path), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ +def test_no_matches( + *, + mock_database: CloudDatabase, + tmp_path: Path, + high_quality_image: io.BytesIO, +) -> None: + """An empty list is returned if there are no matches.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + assert result_data == [] + + +def test_matches( + *, + tmp_path: Path, + high_quality_image: io.BytesIO, + vws_client: VWS, + mock_database: CloudDatabase, +) -> None: + """Details of matching targets are shown.""" + name = uuid.uuid4().hex + target_id = vws_client.add_target( + name=name, + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + [matching_target] = result_data + target_timestamp: object = matching_target["target_data"][ + "target_timestamp" + ] + expected_result_data = { + "target_data": { + "application_metadata": None, + "name": name, + "target_timestamp": target_timestamp, + }, + "target_id": target_id, + } + assert matching_target == expected_result_data + + +def test_image_file_is_dir( + *, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """ + An appropriate error is given if the given image file path + points to a + directory. + """ + runner = CliRunner() + commands = [ + str(object=tmp_path), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ Usage: vuforia-cloud-reco [OPTIONS] IMAGE Try 'vuforia-cloud-reco --help' for help. Error: Invalid value for 'IMAGE': File '{tmp_path}' is a directory. """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - @staticmethod - def test_relative_path( - *, - tmp_path: Path, - mock_database: CloudDatabase, - high_quality_image: io.BytesIO, - ) -> None: - """Image file paths are resolved.""" - runner = CliRunner() - new_filename = uuid.uuid4().hex - original_image_file = tmp_path / "foo" - image_data = high_quality_image.getvalue() - _ = original_image_file.write_bytes(data=image_data) - commands = [ - str(object=new_filename), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - with runner.isolated_filesystem(): - new_file = Path(new_filename) - new_file.symlink_to(target=original_image_file) - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - assert result_data == [] - - @staticmethod - def test_image_file_does_not_exist( - *, - mock_database: CloudDatabase, - tmp_path: Path, - ) -> None: - """ - An appropriate error is given if the given image file does not - exist. - """ - runner = CliRunner() - does_not_exist_file = tmp_path / uuid.uuid4().hex - commands = [ - str(object=does_not_exist_file), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] + +def test_relative_path( + *, + tmp_path: Path, + mock_database: CloudDatabase, + high_quality_image: io.BytesIO, +) -> None: + """Image file paths are resolved.""" + runner = CliRunner() + new_filename = uuid.uuid4().hex + original_image_file = tmp_path / "foo" + image_data = high_quality_image.getvalue() + _ = original_image_file.write_bytes(data=image_data) + commands = [ + str(object=new_filename), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + with runner.isolated_filesystem(): + new_file = Path(new_filename) + new_file.symlink_to(target=original_image_file) result = runner.invoke( cli=vuforia_cloud_reco, args=commands, catch_exceptions=False, color=True, ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ + + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + assert result_data == [] + + +def test_image_file_does_not_exist( + *, + mock_database: CloudDatabase, + tmp_path: Path, +) -> None: + """ + An appropriate error is given if the given image file does not + exist. + """ + runner = CliRunner() + does_not_exist_file = tmp_path / uuid.uuid4().hex + commands = [ + str(object=does_not_exist_file), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ Usage: vuforia-cloud-reco [OPTIONS] IMAGE Try 'vuforia-cloud-reco --help' for help. Error: Invalid value for 'IMAGE': File '{does_not_exist_file}' does not exist. """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr -class TestDefaultRequestTimeout: - """Tests for the default request timeout.""" +# Tests for the default request timeout. - @staticmethod - @pytest.mark.parametrize( - argnames=("response_delay_seconds", "expect_timeout"), - argvalues=[(29, False), (31, True)], - ) - def test_default_timeout( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - response_delay_seconds: int, - expect_timeout: bool, - ) -> None: - """At 29 seconds there is no error; at 31 seconds there is a - timeout. - """ - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=response_delay_seconds, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - str(object=new_file), - "--client-access-key", - database.client_access_key, - "--client-secret-key", - database.client_secret_key, - ] - - if expect_timeout: - with pytest.raises( - expected_exception=requests.exceptions.Timeout, - ): - _ = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - else: - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - - -class TestCustomRequestTimeout: - """Tests for custom request timeout options.""" - - @staticmethod - def test_custom_timeout( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - ) -> None: - """Custom connection and read timeouts are respected.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=5, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - str(object=new_file), - "--client-access-key", - database.client_access_key, - "--client-secret-key", - database.client_secret_key, - "--read-timeout-seconds", - "1", - ] +@pytest.mark.parametrize( + argnames=("response_delay_seconds", "expect_timeout"), + argvalues=[(29, False), (31, True)], +) +def test_default_timeout( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, + response_delay_seconds: int, + expect_timeout: bool, +) -> None: + """At 29 seconds there is no error; at 31 seconds there is a + timeout. + """ + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=response_delay_seconds, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + commands = [ + str(object=new_file), + "--client-access-key", + database.client_access_key, + "--client-secret-key", + database.client_secret_key, + ] + + if expect_timeout: with pytest.raises( expected_exception=requests.exceptions.Timeout, ): @@ -324,42 +264,7 @@ def test_custom_timeout( catch_exceptions=False, color=True, ) - - @staticmethod - def test_custom_timeout_no_error( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - ) -> None: - """A sufficiently large timeout does not cause an error.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=5, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - str(object=new_file), - "--client-access-key", - database.client_access_key, - "--client-secret-key", - database.client_secret_key, - "--read-timeout-seconds", - "60", - ] - + else: result = runner.invoke( cli=vuforia_cloud_reco, args=commands, @@ -369,118 +274,88 @@ def test_custom_timeout_no_error( assert result.exit_code == 0 -def test_version() -> None: - """``vuforia-cloud-reco --version`` shows the version.""" - runner = CliRunner() - commands = ["--version"] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert result.stdout.startswith("vuforia-cloud-reco, version ") - - -class TestMaxNumResults: - """Tests for the ``--max-num-results`` option.""" +# Tests for custom request timeout options. - @staticmethod - def test_default( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, - ) -> None: - """By default the maximum number of results is 1.""" - runner = CliRunner() - 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) - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) +def test_custom_timeout( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """Custom connection and read timeouts are respected.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=5, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) commands = [ str(object=new_file), "--client-access-key", - mock_database.client_access_key, + database.client_access_key, "--client-secret-key", - mock_database.client_secret_key, + database.client_secret_key, + "--read-timeout-seconds", + "1", ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - assert len(result_data) == 1 - - @staticmethod - def test_custom( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, - ) -> None: - """It is possible to set a custom ``--max-num-results``.""" - runner = CliRunner() - 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) - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - max_num_results = 2 + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + _ = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + + +def test_custom_timeout_no_error( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """A sufficiently large timeout does not cause an error.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=5, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) commands = [ str(object=new_file), - "--max-num-results", - str(object=max_num_results), "--client-access-key", - mock_database.client_access_key, + database.client_access_key, "--client-secret-key", - mock_database.client_secret_key, + database.client_secret_key, + "--read-timeout-seconds", + "60", ] + result = runner.invoke( cli=vuforia_cloud_reco, args=commands, @@ -488,301 +363,419 @@ def test_custom( color=True, ) assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - assert len(result_data) == max_num_results - - @staticmethod - def test_out_of_range( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, - ) -> None: - """``--max-num-results`` must be between 1 and 50.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=0), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - expected_stderr_substring = ( - "Error: Invalid value for '--max-num-results': 0 is not in the " - "range 1<=x<=50." - ) - assert expected_stderr_substring in result.stderr - - -class TestIncludeTargetData: - """Tests for the ``--include-target-data`` option.""" - - @staticmethod - def test_default( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, - ) -> None: - """By default, target data is only returned in the top match.""" - runner = CliRunner() - 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) - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=2), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - matches = yaml.safe_load(stream=result.stdout) - top_match, second_match = matches - assert top_match["target_data"] is not None - assert second_match["target_data"] is None - - @staticmethod - def test_top( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, - ) -> None: - """ - When 'top' is given, target data is only returned in the top - match. - """ - runner = CliRunner() - 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) - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=2), - "--include-target-data", - "top", - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - matches = yaml.safe_load(stream=result.stdout) - top_match, second_match = matches - assert top_match["target_data"] is not None - assert second_match["target_data"] is None - - @staticmethod - def test_none( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, - ) -> None: - """ - When 'none' is given, target data is not returned in any - match. - """ - runner = CliRunner() - 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) - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=2), - "--include-target-data", - "none", - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - matches = yaml.safe_load(stream=result.stdout) - top_match, second_match = matches - assert top_match["target_data"] is None - assert second_match["target_data"] is None - - @staticmethod - def test_all( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, - ) -> None: - """When 'all' is given, target data is returned in all matches.""" - runner = CliRunner() - 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) - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=2), - "--include-target-data", - "all", - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - matches = yaml.safe_load(stream=result.stdout) - top_match, second_match = matches - assert top_match["target_data"] is not None - assert second_match["target_data"] is not None - - @staticmethod - def test_other( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, - ) -> None: - """ - When a string other than 'top', 'all', or 'none' is given, an - error is - shown. - """ - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=2), - "--include-target-data", - "other", - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - expected_stderr = ( - "'--include-target-data': 'other' is not one of 'top', 'none', " - "'all'." - ) - assert expected_stderr in result.stderr + +def test_version() -> None: + """``vuforia-cloud-reco --version`` shows the version.""" + runner = CliRunner() + commands = ["--version"] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert result.stdout.startswith("vuforia-cloud-reco, version ") + + +# Tests for the ``--max-num-results`` option. + + +def test_max_num_results_default( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """By default the maximum number of results is 1.""" + runner = CliRunner() + 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) + + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + assert len(result_data) == 1 + + +def test_custom( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """It is possible to set a custom ``--max-num-results``.""" + runner = CliRunner() + 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) + + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + max_num_results = 2 + commands = [ + str(object=new_file), + "--max-num-results", + str(object=max_num_results), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + assert len(result_data) == max_num_results + + +def test_out_of_range( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """``--max-num-results`` must be between 1 and 50.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--max-num-results", + str(object=0), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + expected_stderr_substring = ( + "Error: Invalid value for '--max-num-results': 0 is not in the " + "range 1<=x<=50." + ) + assert expected_stderr_substring in result.stderr + + +# Tests for the ``--include-target-data`` option. + + +def test_include_target_data_default( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """By default, target data is only returned in the top match.""" + runner = CliRunner() + 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) + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--max-num-results", + str(object=2), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + matches = yaml.safe_load(stream=result.stdout) + top_match, second_match = matches + assert top_match["target_data"] is not None + assert second_match["target_data"] is None + + +def test_top( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """ + When 'top' is given, target data is only returned in the top + match. + """ + runner = CliRunner() + 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) + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--max-num-results", + str(object=2), + "--include-target-data", + "top", + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + matches = yaml.safe_load(stream=result.stdout) + top_match, second_match = matches + assert top_match["target_data"] is not None + assert second_match["target_data"] is None + + +def test_none( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """ + When 'none' is given, target data is not returned in any + match. + """ + runner = CliRunner() + 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) + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--max-num-results", + str(object=2), + "--include-target-data", + "none", + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + matches = yaml.safe_load(stream=result.stdout) + top_match, second_match = matches + assert top_match["target_data"] is None + assert second_match["target_data"] is None + + +def test_all( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """When 'all' is given, target data is returned in all matches.""" + runner = CliRunner() + 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) + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + + commands = [ + str(object=new_file), + "--max-num-results", + str(object=2), + "--include-target-data", + "all", + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + matches = yaml.safe_load(stream=result.stdout) + top_match, second_match = matches + assert top_match["target_data"] is not None + assert second_match["target_data"] is not None + + +def test_other( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """ + When a string other than 'top', 'all', or 'none' is given, an + error is + shown. + """ + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--max-num-results", + str(object=2), + "--include-target-data", + "other", + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + expected_stderr = ( + "'--include-target-data': 'other' is not one of 'top', 'none', 'all'." + ) + assert expected_stderr in result.stderr def test_base_vwq_url( diff --git a/tests/test_vumark.py b/tests/test_vumark.py index 91136fa5..9cf16a30 100644 --- a/tests/test_vumark.py +++ b/tests/test_vumark.py @@ -15,208 +15,173 @@ generate_vumark = vumark_module.generate_vumark -class TestGenerateVuMark: - """Tests for ``vumark``.""" +# Tests for ``vumark``. - @staticmethod - @pytest.mark.parametrize( - argnames=("format_name", "expected_prefix"), - argvalues=[ - pytest.param("png", b"\x89PNG\r\n\x1a\n", id="png"), - pytest.param("svg", b"<", id="svg"), - pytest.param("pdf", b"%PDF", id="pdf"), - ], + +@pytest.mark.parametrize( + argnames=("format_name", "expected_prefix"), + argvalues=[ + pytest.param("png", b"\x89PNG\r\n\x1a\n", id="png"), + pytest.param("svg", b"<", id="svg"), + pytest.param("pdf", b"%PDF", id="pdf"), + ], +) +def test_generate_vumark_format( + vumark_database: VuMarkDatabase, + vumark_target: VuMarkTarget, + tmp_path: Path, + format_name: str, + expected_prefix: bytes, +) -> None: + """The returned file matches the requested format.""" + runner = CliRunner() + output_file = tmp_path / f"output.{format_name}" + commands = [ + "--target-id", + vumark_target.target_id, + "--instance-id", + "12345", + "--format", + format_name, + "--output", + str(object=output_file), + "--server-access-key", + vumark_database.server_access_key, + "--server-secret-key", + vumark_database.server_secret_key, + ] + result = runner.invoke( + cli=generate_vumark, + args=commands, + catch_exceptions=False, + color=True, ) - def test_generate_vumark_format( - vumark_database: VuMarkDatabase, - vumark_target: VuMarkTarget, - tmp_path: Path, - format_name: str, - expected_prefix: bytes, - ) -> None: - """The returned file matches the requested format.""" - runner = CliRunner() - output_file = tmp_path / f"output.{format_name}" - commands = [ - "--target-id", - vumark_target.target_id, - "--instance-id", - "12345", - "--format", - format_name, - "--output", - str(object=output_file), - "--server-access-key", - vumark_database.server_access_key, - "--server-secret-key", - vumark_database.server_secret_key, - ] - result = runner.invoke( - cli=generate_vumark, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert output_file.read_bytes().startswith(expected_prefix) + assert result.exit_code == 0 + assert output_file.read_bytes().startswith(expected_prefix) - @staticmethod - def test_default_format_is_png( - vumark_database: VuMarkDatabase, - vumark_target: VuMarkTarget, - tmp_path: Path, - ) -> None: - """The default output format is png.""" - runner = CliRunner() - output_file = tmp_path / "output.png" - commands = [ - "--target-id", - vumark_target.target_id, - "--instance-id", - "12345", - "--output", - str(object=output_file), - "--server-access-key", - vumark_database.server_access_key, - "--server-secret-key", - vumark_database.server_secret_key, - ] - result = runner.invoke( - cli=generate_vumark, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert output_file.exists() - assert output_file.read_bytes().startswith(b"\x89PNG\r\n\x1a\n") - @staticmethod - def test_unknown_target( - vumark_database: VuMarkDatabase, - tmp_path: Path, - ) -> None: - """An error is shown when the target ID does not exist.""" - nonexistent_target_id = uuid.uuid4().hex - runner = CliRunner() - output_file = tmp_path / "output.png" - commands = [ - "--target-id", - nonexistent_target_id, - "--instance-id", - "12345", - "--output", - str(object=output_file), - "--server-access-key", - vumark_database.server_access_key, - "--server-secret-key", - vumark_database.server_secret_key, - ] - result = runner.invoke( - cli=generate_vumark, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 1 - expected_stderr = ( - f'Error: Target "{nonexistent_target_id}" does not exist.\n' - ) - assert result.stderr == expected_stderr +def test_default_format_is_png( + vumark_database: VuMarkDatabase, + vumark_target: VuMarkTarget, + tmp_path: Path, +) -> None: + """The default output format is png.""" + runner = CliRunner() + output_file = tmp_path / "output.png" + commands = [ + "--target-id", + vumark_target.target_id, + "--instance-id", + "12345", + "--output", + str(object=output_file), + "--server-access-key", + vumark_database.server_access_key, + "--server-secret-key", + vumark_database.server_secret_key, + ] + result = runner.invoke( + cli=generate_vumark, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert output_file.exists() + assert output_file.read_bytes().startswith(b"\x89PNG\r\n\x1a\n") - @staticmethod - def test_invalid_instance_id( - vumark_database: VuMarkDatabase, - vumark_target: VuMarkTarget, - tmp_path: Path, - ) -> None: - """An error is shown when the instance ID is invalid.""" - runner = CliRunner() - output_file = tmp_path / "output.png" - commands = [ - "--target-id", - vumark_target.target_id, - "--instance-id", - "", - "--output", - str(object=output_file), - "--server-access-key", - vumark_database.server_access_key, - "--server-secret-key", - vumark_database.server_secret_key, - ] - result = runner.invoke( - cli=generate_vumark, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 1 - expected_stderr = "Error: The given instance ID is invalid.\n" - assert result.stderr == expected_stderr - @staticmethod - def test_target_not_in_success_state( - tmp_path: Path, - ) -> None: - """An error is shown when the target is not in the success - state. - """ - processing_target = VuMarkTarget( - name="processing-target", - processing_time_seconds=9999, - ) - database = VuMarkDatabase(vumark_targets={processing_target}) - with MockVWS() as mock: - mock.add_vumark_database(vumark_database=database) - target_id = processing_target.target_id - runner = CliRunner() - output_file = tmp_path / "output.png" - commands = [ - "--target-id", - target_id, - "--instance-id", - "12345", - "--output", - str(object=output_file), - "--server-access-key", - database.server_access_key, - "--server-secret-key", - database.server_secret_key, - ] - result = runner.invoke( - cli=generate_vumark, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 1 - expected_stderr = ( - f'Error: The target "{target_id}" is not in the success ' - "state and cannot be used to generate a VuMark instance.\n" - ) - assert result.stderr == expected_stderr +def test_unknown_target( + vumark_database: VuMarkDatabase, + tmp_path: Path, +) -> None: + """An error is shown when the target ID does not exist.""" + nonexistent_target_id = uuid.uuid4().hex + runner = CliRunner() + output_file = tmp_path / "output.png" + commands = [ + "--target-id", + nonexistent_target_id, + "--instance-id", + "12345", + "--output", + str(object=output_file), + "--server-access-key", + vumark_database.server_access_key, + "--server-secret-key", + vumark_database.server_secret_key, + ] + result = runner.invoke( + cli=generate_vumark, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 1 + expected_stderr = ( + f'Error: Target "{nonexistent_target_id}" does not exist.\n' + ) + assert result.stderr == expected_stderr + + +def test_invalid_instance_id( + vumark_database: VuMarkDatabase, + vumark_target: VuMarkTarget, + tmp_path: Path, +) -> None: + """An error is shown when the instance ID is invalid.""" + runner = CliRunner() + output_file = tmp_path / "output.png" + commands = [ + "--target-id", + vumark_target.target_id, + "--instance-id", + "", + "--output", + str(object=output_file), + "--server-access-key", + vumark_database.server_access_key, + "--server-secret-key", + vumark_database.server_secret_key, + ] + result = runner.invoke( + cli=generate_vumark, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 1 + expected_stderr = "Error: The given instance ID is invalid.\n" + assert result.stderr == expected_stderr + - @staticmethod - def test_authentication_failure( - vumark_database: VuMarkDatabase, - vumark_target: VuMarkTarget, - tmp_path: Path, - ) -> None: - """An error is shown on authentication failure.""" +def test_target_not_in_success_state( + tmp_path: Path, +) -> None: + """An error is shown when the target is not in the success + state. + """ + processing_target = VuMarkTarget( + name="processing-target", + processing_time_seconds=9999, + ) + database = VuMarkDatabase(vumark_targets={processing_target}) + with MockVWS() as mock: + mock.add_vumark_database(vumark_database=database) + target_id = processing_target.target_id runner = CliRunner() output_file = tmp_path / "output.png" commands = [ "--target-id", - vumark_target.target_id, + target_id, "--instance-id", "12345", "--output", str(object=output_file), "--server-access-key", - vumark_database.server_access_key, + database.server_access_key, "--server-secret-key", - "wrong-secret-key", + database.server_secret_key, ] result = runner.invoke( cli=generate_vumark, @@ -224,9 +189,43 @@ def test_authentication_failure( catch_exceptions=False, color=True, ) - assert result.exit_code == 1 - expected_stderr = "The given secret key was incorrect.\n" - assert result.stderr == expected_stderr + assert result.exit_code == 1 + expected_stderr = ( + f'Error: The target "{target_id}" is not in the success ' + "state and cannot be used to generate a VuMark instance.\n" + ) + assert result.stderr == expected_stderr + + +def test_authentication_failure( + vumark_database: VuMarkDatabase, + vumark_target: VuMarkTarget, + tmp_path: Path, +) -> None: + """An error is shown on authentication failure.""" + runner = CliRunner() + output_file = tmp_path / "output.png" + commands = [ + "--target-id", + vumark_target.target_id, + "--instance-id", + "12345", + "--output", + str(object=output_file), + "--server-access-key", + vumark_database.server_access_key, + "--server-secret-key", + "wrong-secret-key", + ] + result = runner.invoke( + cli=generate_vumark, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 1 + expected_stderr = "The given secret key was incorrect.\n" + assert result.stderr == expected_stderr @pytest.mark.parametrize(argnames="invalid_format", argvalues=["bmp", "gif"]) diff --git a/tests/test_vws_commands.py b/tests/test_vws_commands.py index b6d61ca2..c4be30f6 100644 --- a/tests/test_vws_commands.py +++ b/tests/test_vws_commands.py @@ -288,120 +288,56 @@ def test_get_duplicate_targets( assert result_data == expected_result_data -class TestDefaultRequestTimeout: - """Tests for the default request timeout.""" +# Tests for the default request timeout. - @staticmethod - @pytest.mark.parametrize( - argnames=("response_delay_seconds", "expect_timeout"), - argvalues=[(29, False), (31, True)], - ) - def test_default_timeout( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - response_delay_seconds: int, - expect_timeout: bool, - ) -> None: - """At 29 seconds there is no error; at 31 seconds there is a - timeout. - """ - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=response_delay_seconds, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - "add-target", - "--name", - uuid.uuid4().hex, - "--width", - "1", - "--image", - str(object=new_file), - "--server-access-key", - database.server_access_key, - "--server-secret-key", - database.server_secret_key, - ] - - if expect_timeout: - with pytest.raises( - expected_exception=requests.exceptions.Timeout, - ): - _ = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - else: - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - - -class TestCustomRequestTimeout: - """Tests for custom request timeout options.""" - - @staticmethod - def test_custom_timeout( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - ) -> None: - """Custom connection and read timeouts are respected.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=5, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - "add-target", - "--name", - uuid.uuid4().hex, - "--width", - "1", - "--image", - str(object=new_file), - "--server-access-key", - database.server_access_key, - "--server-secret-key", - database.server_secret_key, - "--read-timeout-seconds", - "1", - ] +@pytest.mark.parametrize( + argnames=("response_delay_seconds", "expect_timeout"), + argvalues=[(29, False), (31, True)], +) +def test_default_timeout( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, + response_delay_seconds: int, + expect_timeout: bool, +) -> None: + """At 29 seconds there is no error; at 31 seconds there is a + timeout. + """ + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=response_delay_seconds, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + commands = [ + "add-target", + "--name", + uuid.uuid4().hex, + "--width", + "1", + "--image", + str(object=new_file), + "--server-access-key", + database.server_access_key, + "--server-secret-key", + database.server_secret_key, + ] + + if expect_timeout: with pytest.raises( expected_exception=requests.exceptions.Timeout, ): @@ -411,48 +347,7 @@ def test_custom_timeout( catch_exceptions=False, color=True, ) - - @staticmethod - def test_custom_timeout_no_error( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - ) -> None: - """A sufficiently large timeout does not cause an error.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=5, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - "add-target", - "--name", - uuid.uuid4().hex, - "--width", - "1", - "--image", - str(object=new_file), - "--server-access-key", - database.server_access_key, - "--server-secret-key", - database.server_secret_key, - "--read-timeout-seconds", - "60", - ] - + else: result = runner.invoke( cli=vws_group, args=commands, @@ -462,646 +357,463 @@ def test_custom_timeout_no_error( assert result.exit_code == 0 -class TestAddTarget: - """Tests for ``vws add-target``.""" - - @staticmethod - def test_add_target( - *, - mock_database: CloudDatabase, - 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 = secrets.choice(seq=range(1, 5000)) / 100 +# Tests for custom request timeout options. + + +def test_custom_request_timeout_custom_timeout( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """Custom connection and read timeouts are respected.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=5, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) commands = [ "add-target", "--name", - name, + uuid.uuid4().hex, "--width", - str(object=width), + "1", "--image", str(object=new_file), "--server-access-key", - mock_database.server_access_key, + database.server_access_key, "--server-secret-key", - mock_database.server_secret_key, + database.server_secret_key, + "--read-timeout-seconds", + "1", ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0, result.output - - 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 - - @staticmethod - def test_image_file_does_not_exist( - *, - mock_database: CloudDatabase, - tmp_path: Path, - ) -> None: - """ - An appropriate error is given if the given image file does not - exist. - """ - runner = CliRunner() - does_not_exist_file = tmp_path / uuid.uuid4().hex + + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + _ = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + + +def test_custom_timeout_no_error( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """A sufficiently large timeout does not cause an error.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=5, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) commands = [ "add-target", "--name", - "foo", + uuid.uuid4().hex, "--width", "1", "--image", - str(object=does_not_exist_file), + str(object=new_file), "--server-access-key", - mock_database.server_access_key, + database.server_access_key, "--server-secret-key", - mock_database.server_secret_key, + database.server_secret_key, + "--read-timeout-seconds", + "60", ] + result = runner.invoke( cli=vws_group, args=commands, catch_exceptions=False, color=True, ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ + assert result.exit_code == 0 + + +# Tests for ``vws add-target``. + + +def test_add_target( + *, + mock_database: CloudDatabase, + 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 = secrets.choice(seq=range(1, 5000)) / 100 + commands = [ + "add-target", + "--name", + name, + "--width", + str(object=width), + "--image", + str(object=new_file), + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0, result.output + + 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_add_target_image_file_does_not_exist( + *, + mock_database: CloudDatabase, + tmp_path: Path, +) -> None: + """ + An appropriate error is given if the given image file does not + exist. + """ + runner = CliRunner() + does_not_exist_file = tmp_path / uuid.uuid4().hex + commands = [ + "add-target", + "--name", + "foo", + "--width", + "1", + "--image", + str(object=does_not_exist_file), + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ Usage: vws add-target [OPTIONS] Try 'vws add-target --help' for help. Error: Invalid value for '--image': File '{does_not_exist_file}' does not exist. """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - @staticmethod - def test_image_file_is_dir( - *, - mock_database: CloudDatabase, - tmp_path: Path, - ) -> None: - """ - An appropriate error is given if the given image file path - points to a - directory. - """ - runner = CliRunner() - commands = [ - "add-target", - "--name", - "foo", - "--width", - "1", - "--image", - str(object=tmp_path), - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr + + +def test_add_target_image_file_is_dir( + *, + mock_database: CloudDatabase, + tmp_path: Path, +) -> None: + """ + An appropriate error is given if the given image file path + points to a + directory. + """ + runner = CliRunner() + commands = [ + "add-target", + "--name", + "foo", + "--width", + "1", + "--image", + str(object=tmp_path), + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ Usage: vws add-target [OPTIONS] Try 'vws add-target --help' for help. Error: Invalid value for '--image': File '{tmp_path}' is a directory. """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - @staticmethod - def test_relative_path( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - ) -> None: - """Image file paths are resolved.""" - runner = CliRunner() - new_filename = uuid.uuid4().hex - original_image_file = tmp_path / "foo" - image_data = high_quality_image.getvalue() - _ = original_image_file.write_bytes(data=image_data) - name = uuid.uuid4().hex - commands = [ - "add-target", - "--name", - name, - "--width", - "1", - "--image", - new_filename, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - with runner.isolated_filesystem(): - new_file = Path(new_filename) - new_file.symlink_to(target=original_image_file) - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - target_id = result.stdout.strip() - target_record = vws_client.get_target_record(target_id=target_id) - assert target_record.target_record.name == name - - @staticmethod - def test_custom_metadata( - *, - mock_database: CloudDatabase, - 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(encoding="ascii") - base64_encoded_metadata_bytes = base64.b64encode(s=metadata_bytes) - base64_encoded_metadata = base64_encoded_metadata_bytes.decode( - encoding="ascii", - ) - commands = [ - "add-target", - "--name", - name, - "--width", - "0.1", - "--image", - str(object=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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - 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 - - @staticmethod - @pytest.mark.parametrize( - argnames=("active_flag_given", "active_flag_expected"), - argvalues=[ - ("true", True), - ("false", False), - ], - ) - def test_custom_active_flag( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - active_flag_given: str, - active_flag_expected: bool, - ) -> None: - """The Active Flag of the new target can be chosen.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - "add-target", - "--name", - "foo", - "--width", - "0.1", - "--image", - str(object=new_file), - "--active-flag", - active_flag_given, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr - target_id = result.stdout.strip() - target_details = vws_client.get_target_record(target_id=target_id) - active_flag = target_details.target_record.active_flag - assert active_flag is active_flag_expected - - -class TestWaitForTargetProcessed: - """Tests for ``vws wait-for-target-processed``.""" - - @staticmethod - def test_wait_for_target_processed( - *, - mock_database: CloudDatabase, - 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, - ] + +def test_add_target_relative_path( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """Image file paths are resolved.""" + runner = CliRunner() + new_filename = uuid.uuid4().hex + original_image_file = tmp_path / "foo" + image_data = high_quality_image.getvalue() + _ = original_image_file.write_bytes(data=image_data) + name = uuid.uuid4().hex + commands = [ + "add-target", + "--name", + name, + "--width", + "1", + "--image", + new_filename, + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + with runner.isolated_filesystem(): + new_file = Path(new_filename) + new_file.symlink_to(target=original_image_file) result = runner.invoke( cli=vws_group, args=commands, catch_exceptions=False, color=True, ) - assert result.exit_code == 0 - assert not bool(result.stdout) - report = vws_client.get_target_summary_report(target_id=target_id) - assert report.status != TargetStatuses.PROCESSING - - @staticmethod - def test_default_seconds_between_requests( - high_quality_image: io.BytesIO, - ) -> None: - """By default, 0.2 seconds are waited between polling requests.""" - runner = CliRunner() - with MockVWS(processing_time_seconds=0.5) as mock: - mock_database = CloudDatabase() - mock.add_cloud_database(cloud_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, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert not bool(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.2 seconds - not processed - 1 - + - # Request after 0.4 seconds - not processed - # This assumes that there is less than 0.1 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 - - @staticmethod - def test_custom_seconds_between_requests( - 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 = CloudDatabase() - mock.add_cloud_database(cloud_database=mock_database) - vws_client = VWS( - server_access_key=mock_database.server_access_key, - server_secret_key=mock_database.server_secret_key, - ) + assert result.exit_code == 0 + target_id = result.stdout.strip() + target_record = vws_client.get_target_record(target_id=target_id) + assert target_record.target_record.name == name - 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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert not bool(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 - - @staticmethod - def test_custom_seconds_too_small(mock_database: CloudDatabase) -> None: - """ - The minimum valid value for ``--seconds-between-requests`` is - 0.05 - seconds. - """ - runner = CliRunner() - commands = [ - "wait-for-target-processed", - "--target-id", - "x", - "--seconds-between-requests", - "0.01", - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code != 0 - assert not bool(result.stdout) - expected_substring = "0.01 is not in the range x>=0.05." - assert expected_substring in result.stderr - - @staticmethod - def test_custom_timeout(high_quality_image: io.BytesIO) -> None: - """It is possible to set a maximum timeout.""" - runner = CliRunner() - with MockVWS(processing_time_seconds=0.5) as mock: - mock_database = CloudDatabase() - mock.add_cloud_database(cloud_database=mock_database) - vws_client = VWS( - server_access_key=mock_database.server_access_key, - server_secret_key=mock_database.server_secret_key, - ) +def test_custom_metadata( + *, + mock_database: CloudDatabase, + 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(encoding="ascii") + base64_encoded_metadata_bytes = base64.b64encode(s=metadata_bytes) + base64_encoded_metadata = base64_encoded_metadata_bytes.decode( + encoding="ascii", + ) + commands = [ + "add-target", + "--name", + name, + "--width", + "0.1", + "--image", + str(object=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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + 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 + + +@pytest.mark.parametrize( + argnames=("active_flag_given", "active_flag_expected"), + argvalues=[ + ("true", True), + ("false", False), + ], +) +def test_custom_active_flag( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + active_flag_given: str, + active_flag_expected: bool, +) -> None: + """The Active Flag of the new target can be chosen.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + "add-target", + "--name", + "foo", + "--width", + "0.1", + "--image", + str(object=new_file), + "--active-flag", + active_flag_given, + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 - target_id = vws_client.add_target( - name="x", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) + target_id = result.stdout.strip() + target_details = vws_client.get_target_record(target_id=target_id) + active_flag = target_details.target_record.active_flag + assert active_flag is active_flag_expected - 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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code != 0 - assert result.stderr == ( - "Error: The target processing time has exceeded the allowed " - "limit.\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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - report = vws_client.get_target_summary_report(target_id=target_id) - assert report.status != TargetStatuses.PROCESSING - - @staticmethod - def test_custom_timeout_too_small(mock_database: CloudDatabase) -> None: - """ - The minimum valid value for ``--timeout-seconds`` is 0.05 - seconds. - """ - runner = CliRunner() - commands = [ - "wait-for-target-processed", - "--target-id", - "x", - "--timeout-seconds", - "0.01", - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, +# Tests for ``vws wait-for-target-processed``. + + +def test_wait_for_target_processed( + *, + mock_database: CloudDatabase, + 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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(result.stdout) + report = vws_client.get_target_summary_report(target_id=target_id) + assert report.status != TargetStatuses.PROCESSING + + +def test_default_seconds_between_requests( + high_quality_image: io.BytesIO, +) -> None: + """By default, 0.2 seconds are waited between polling requests.""" + runner = CliRunner() + with MockVWS(processing_time_seconds=0.5) as mock: + mock_database = CloudDatabase() + mock.add_cloud_database(cloud_database=mock_database) + vws_client = VWS( + server_access_key=mock_database.server_access_key, + server_secret_key=mock_database.server_secret_key, ) - assert result.exit_code != 0 - expected_substring = "0.01 is not in the range x>=0.05." - assert expected_substring in result.stderr - - -class TestUpdateTarget: - """Tests for ``vws update-target``.""" - - @staticmethod - def test_update_target( - *, - mock_database: CloudDatabase, - 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() - old_name = uuid.uuid4().hex - old_width = secrets.choice(seq=range(1, 5000)) / 100 + target_id = vws_client.add_target( - name=old_name, - width=old_width, + name="x", + width=1, 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(s=b"a").decode( - encoding="ascii", - ) - new_name = uuid.uuid4().hex - new_width = secrets.choice(seq=range(1, 5000)) / 100 - 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", + "wait-for-target-processed", "--target-id", target_id, - "--name", - new_name, - "--width", - str(object=new_width), - "--image", - str(object=new_image_file), - "--active-flag", - "true", - "--application-metadata", - new_application_metadata, "--server-access-key", mock_database.server_access_key, "--server-secret-key", @@ -1115,67 +827,65 @@ def test_update_target( ) assert result.exit_code == 0 assert not bool(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.2 seconds - not processed + 1 + + + # Request after 0.4 seconds - not processed + # This assumes that there is less than 0.1 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 - 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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, +def test_custom_seconds_between_requests( + 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 = CloudDatabase() + mock.add_cloud_database(cloud_database=mock_database) + vws_client = VWS( + server_access_key=mock_database.server_access_key, + server_secret_key=mock_database.server_secret_key, ) - assert result.exit_code == 0 - assert not bool(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 - - @staticmethod - def test_no_fields_given( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - ) -> None: - """It is possible to give no update fields.""" - runner = CliRunner() - old_name = uuid.uuid4().hex - old_width = secrets.choice(seq=range(1, 5000)) / 100 + target_id = vws_client.add_target( - name=old_name, - width=old_width, + 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 = [ - "update-target", + "wait-for-target-processed", "--target-id", target_id, + "--seconds-between-requests", + "0.3", "--server-access-key", mock_database.server_access_key, "--server-secret-key", @@ -1189,19 +899,75 @@ def test_no_fields_given( ) assert result.exit_code == 0 assert not bool(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 test_custom_seconds_too_small(mock_database: CloudDatabase) -> None: + """ + The minimum valid value for ``--seconds-between-requests`` is + 0.05 + seconds. + """ + runner = CliRunner() + commands = [ + "wait-for-target-processed", + "--target-id", + "x", + "--seconds-between-requests", + "0.01", + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code != 0 + assert not bool(result.stdout) + expected_substring = "0.01 is not in the range x>=0.05." + assert expected_substring in result.stderr + + +def test_wait_for_target_processed_custom_timeout( + high_quality_image: io.BytesIO, +) -> None: + """It is possible to set a maximum timeout.""" + runner = CliRunner() + with MockVWS(processing_time_seconds=0.5) as mock: + mock_database = CloudDatabase() + mock.add_cloud_database(cloud_database=mock_database) + vws_client = VWS( + server_access_key=mock_database.server_access_key, + server_secret_key=mock_database.server_secret_key, + ) - @staticmethod - def test_image_file_does_not_exist( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - ) -> None: - """ - An appropriate error is given if the given image file does not - exist. - """ target_id = vws_client.add_target( name="x", width=1, @@ -1209,15 +975,16 @@ def test_image_file_does_not_exist( active_flag=True, application_metadata=None, ) - vws_client.wait_for_target_processed(target_id=target_id) - runner = CliRunner() - does_not_exist_file = tmp_path / uuid.uuid4().hex + + report = vws_client.get_target_summary_report(target_id=target_id) + assert report.status == TargetStatuses.PROCESSING + commands = [ - "update-target", + "wait-for-target-processed", "--target-id", target_id, - "--image", - str(object=does_not_exist_file), + "--timeout-seconds", + "0.1", "--server-access-key", mock_database.server_access_key, "--server-secret-key", @@ -1229,47 +996,18 @@ def test_image_file_does_not_exist( catch_exceptions=False, color=True, ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ - Usage: vws update-target [OPTIONS] - Try 'vws update-target --help' for help. - - Error: Invalid value for '--image': File '{does_not_exist_file}' does not exist. - """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - @staticmethod - def test_image_file_is_dir( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - ) -> None: - """ - An appropriate error is given if the given image file path - points to a - directory. - """ - target_id = vws_client.add_target( - name="x", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, + assert result.exit_code != 0 + assert result.stderr == ( + "Error: The target processing time has exceeded the allowed " + "limit.\n" ) - vws_client.wait_for_target_processed(target_id=target_id) - runner = CliRunner() + commands = [ - "update-target", + "wait-for-target-processed", "--target-id", target_id, - "--image", - str(object=tmp_path), + "--timeout-seconds", + "0.5", "--server-access-key", mock_database.server_access_key, "--server-secret-key", @@ -1281,64 +1019,323 @@ def test_image_file_is_dir( catch_exceptions=False, color=True, ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ + assert result.exit_code == 0 + report = vws_client.get_target_summary_report(target_id=target_id) + assert report.status != TargetStatuses.PROCESSING + + +def test_custom_timeout_too_small(mock_database: CloudDatabase) -> None: + """ + The minimum valid value for ``--timeout-seconds`` is 0.05 + seconds. + """ + runner = CliRunner() + commands = [ + "wait-for-target-processed", + "--target-id", + "x", + "--timeout-seconds", + "0.01", + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code != 0 + expected_substring = "0.01 is not in the range x>=0.05." + assert expected_substring in result.stderr + + +# Tests for ``vws update-target``. + + +def test_update_target( + *, + mock_database: CloudDatabase, + 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() + old_name = uuid.uuid4().hex + old_width = secrets.choice(seq=range(1, 5000)) / 100 + 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(s=b"a").decode( + encoding="ascii", + ) + new_name = uuid.uuid4().hex + new_width = secrets.choice(seq=range(1, 5000)) / 100 + 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(object=new_width), + "--image", + str(object=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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(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 + + +def test_no_fields_given( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, +) -> None: + """It is possible to give no update fields.""" + runner = CliRunner() + old_name = uuid.uuid4().hex + old_width = secrets.choice(seq=range(1, 5000)) / 100 + 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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(result.stdout) + + +def test_update_target_image_file_does_not_exist( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """ + An appropriate error is given if the given image file does not + exist. + """ + 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) + runner = CliRunner() + does_not_exist_file = tmp_path / uuid.uuid4().hex + commands = [ + "update-target", + "--target-id", + target_id, + "--image", + str(object=does_not_exist_file), + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ + Usage: vws update-target [OPTIONS] + Try 'vws update-target --help' for help. + + Error: Invalid value for '--image': File '{does_not_exist_file}' does not exist. + """, + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr + + +def test_update_target_image_file_is_dir( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """ + An appropriate error is given if the given image file path + points to a + directory. + """ + 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) + runner = CliRunner() + commands = [ + "update-target", + "--target-id", + target_id, + "--image", + str(object=tmp_path), + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ Usage: vws update-target [OPTIONS] Try 'vws update-target --help' for help. Error: Invalid value for '--image': File '{tmp_path}' is a directory. """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - @staticmethod - def test_relative_path( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - ) -> None: - """Image file paths are resolved.""" - runner = CliRunner() - 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) - new_filename = uuid.uuid4().hex - original_image_file = tmp_path / "foo" - image_data = high_quality_image.getvalue() - _ = original_image_file.write_bytes(data=image_data) - commands = [ - "update-target", - "--target-id", - target_id, - "--image", - new_filename, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr - with runner.isolated_filesystem(): - new_file = Path(new_filename) - new_file.symlink_to(target=original_image_file) - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 +def test_update_target_relative_path( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """Image file paths are resolved.""" + runner = CliRunner() + 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) + new_filename = uuid.uuid4().hex + original_image_file = tmp_path / "foo" + image_data = high_quality_image.getvalue() + _ = original_image_file.write_bytes(data=image_data) + commands = [ + "update-target", + "--target-id", + target_id, + "--image", + new_filename, + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + + with runner.isolated_filesystem(): + new_file = Path(new_filename) + new_file.symlink_to(target=original_image_file) + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + + assert result.exit_code == 0 def test_custom_base_url() -> None: From 82d27f7b9d8a690ae3c1d325e02736174414b676 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 6 Oct 2026 08:47:03 +0100 Subject: [PATCH 2/2] Organize test functions into files by behavior --- pyproject.toml | 2 +- tests/query/__init__.py | 1 + tests/query/test_options.py | 405 +++++++++ tests/query/test_requests.py | 298 +++++++ tests/query/test_timeouts.py | 163 ++++ tests/test_query.py | 857 ------------------- tests/test_vumark.py | 3 - tests/test_vws_commands.py | 1368 ------------------------------- tests/vws/__init__.py | 1 + tests/vws/test_add_target.py | 297 +++++++ tests/vws/test_configuration.py | 211 +++++ tests/vws/test_processing.py | 313 +++++++ tests/vws/test_reports.py | 109 +++ tests/vws/test_targets.py | 179 ++++ tests/vws/test_update_target.py | 297 +++++++ 15 files changed, 2275 insertions(+), 2229 deletions(-) create mode 100644 tests/query/__init__.py create mode 100644 tests/query/test_options.py create mode 100644 tests/query/test_requests.py create mode 100644 tests/query/test_timeouts.py delete mode 100644 tests/test_query.py delete mode 100644 tests/test_vws_commands.py create mode 100644 tests/vws/__init__.py create mode 100644 tests/vws/test_add_target.py create mode 100644 tests/vws/test_configuration.py create mode 100644 tests/vws/test_processing.py create mode 100644 tests/vws/test_reports.py create mode 100644 tests/vws/test_targets.py create mode 100644 tests/vws/test_update_target.py diff --git a/pyproject.toml b/pyproject.toml index c2fc8489..e802ada0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,7 +163,7 @@ lint.per-file-ignores."doccmd_*.py" = [ # Allow asserts in docs. "S101", ] -lint.per-file-ignores."tests/test_*.py" = [ +lint.per-file-ignores."tests/**/test_*.py" = [ # Do not require tests to have a one-line summary. "D205", ] diff --git a/tests/query/__init__.py b/tests/query/__init__.py new file mode 100644 index 00000000..75a879ba --- /dev/null +++ b/tests/query/__init__.py @@ -0,0 +1 @@ +"""Query tests.""" diff --git a/tests/query/test_options.py b/tests/query/test_options.py new file mode 100644 index 00000000..bbec427e --- /dev/null +++ b/tests/query/test_options.py @@ -0,0 +1,405 @@ +"""Tests for `query` options commands.""" + +import io +import uuid +from pathlib import Path + +import yaml +from click.testing import CliRunner +from mock_vws.database import CloudDatabase +from vws import VWS + +from vws_cli.query import vuforia_cloud_reco + + +def test_max_num_results_default( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """By default the maximum number of results is 1.""" + runner = CliRunner() + 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) + + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + assert len(result_data) == 1 + + +def test_custom( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """It is possible to set a custom ``--max-num-results``.""" + runner = CliRunner() + 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) + + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + max_num_results = 2 + commands = [ + str(object=new_file), + "--max-num-results", + str(object=max_num_results), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + assert len(result_data) == max_num_results + + +def test_out_of_range( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """``--max-num-results`` must be between 1 and 50.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--max-num-results", + str(object=0), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + expected_stderr_substring = ( + "Error: Invalid value for '--max-num-results': 0 is not in the " + "range 1<=x<=50." + ) + assert expected_stderr_substring in result.stderr + + +def test_include_target_data_default( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """By default, target data is only returned in the top match.""" + runner = CliRunner() + 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) + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--max-num-results", + str(object=2), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + matches = yaml.safe_load(stream=result.stdout) + top_match, second_match = matches + assert top_match["target_data"] is not None + assert second_match["target_data"] is None + + +def test_top( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """ + When 'top' is given, target data is only returned in the top + match. + """ + runner = CliRunner() + 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) + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--max-num-results", + str(object=2), + "--include-target-data", + "top", + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + matches = yaml.safe_load(stream=result.stdout) + top_match, second_match = matches + assert top_match["target_data"] is not None + assert second_match["target_data"] is None + + +def test_none( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """ + When 'none' is given, target data is not returned in any + match. + """ + runner = CliRunner() + 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) + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--max-num-results", + str(object=2), + "--include-target-data", + "none", + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + matches = yaml.safe_load(stream=result.stdout) + top_match, second_match = matches + assert top_match["target_data"] is None + assert second_match["target_data"] is None + + +def test_all( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """When 'all' is given, target data is returned in all matches.""" + runner = CliRunner() + 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) + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + + commands = [ + str(object=new_file), + "--max-num-results", + str(object=2), + "--include-target-data", + "all", + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + matches = yaml.safe_load(stream=result.stdout) + top_match, second_match = matches + assert top_match["target_data"] is not None + assert second_match["target_data"] is not None + + +def test_other( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """ + When a string other than 'top', 'all', or 'none' is given, an + error is + shown. + """ + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--max-num-results", + str(object=2), + "--include-target-data", + "other", + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + expected_stderr = ( + "'--include-target-data': 'other' is not one of 'top', 'none', 'all'." + ) + assert expected_stderr in result.stderr diff --git a/tests/query/test_requests.py b/tests/query/test_requests.py new file mode 100644 index 00000000..0b7198f3 --- /dev/null +++ b/tests/query/test_requests.py @@ -0,0 +1,298 @@ +"""Tests for `query` requests commands.""" + +import io +import uuid +from pathlib import Path +from textwrap import dedent + +import yaml +from click.testing import CliRunner +from mock_vws import MockVWS +from mock_vws.database import CloudDatabase +from vws import VWS + +from vws_cli.query import vuforia_cloud_reco + + +def test_no_matches( + *, + mock_database: CloudDatabase, + tmp_path: Path, + high_quality_image: io.BytesIO, +) -> None: + """An empty list is returned if there are no matches.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + assert result_data == [] + + +def test_matches( + *, + tmp_path: Path, + high_quality_image: io.BytesIO, + vws_client: VWS, + mock_database: CloudDatabase, +) -> None: + """Details of matching targets are shown.""" + name = uuid.uuid4().hex + target_id = vws_client.add_target( + name=name, + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + str(object=new_file), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + [matching_target] = result_data + target_timestamp: object = matching_target["target_data"][ + "target_timestamp" + ] + expected_result_data = { + "target_data": { + "application_metadata": None, + "name": name, + "target_timestamp": target_timestamp, + }, + "target_id": target_id, + } + assert matching_target == expected_result_data + + +def test_image_file_is_dir( + *, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """ + An appropriate error is given if the given image file path + points to a + directory. + """ + runner = CliRunner() + commands = [ + str(object=tmp_path), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ + Usage: vuforia-cloud-reco [OPTIONS] IMAGE + Try 'vuforia-cloud-reco --help' for help. + + Error: Invalid value for 'IMAGE': File '{tmp_path}' is a directory. + """, + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr + + +def test_relative_path( + *, + tmp_path: Path, + mock_database: CloudDatabase, + high_quality_image: io.BytesIO, +) -> None: + """Image file paths are resolved.""" + runner = CliRunner() + new_filename = uuid.uuid4().hex + original_image_file = tmp_path / "foo" + image_data = high_quality_image.getvalue() + _ = original_image_file.write_bytes(data=image_data) + commands = [ + str(object=new_filename), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + with runner.isolated_filesystem(): + new_file = Path(new_filename) + new_file.symlink_to(target=original_image_file) + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + assert result_data == [] + + +def test_image_file_does_not_exist( + *, + mock_database: CloudDatabase, + tmp_path: Path, +) -> None: + """ + An appropriate error is given if the given image file does not + exist. + """ + runner = CliRunner() + does_not_exist_file = tmp_path / uuid.uuid4().hex + commands = [ + str(object=does_not_exist_file), + "--client-access-key", + mock_database.client_access_key, + "--client-secret-key", + mock_database.client_secret_key, + ] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ + Usage: vuforia-cloud-reco [OPTIONS] IMAGE + Try 'vuforia-cloud-reco --help' for help. + + Error: Invalid value for 'IMAGE': File '{does_not_exist_file}' does not exist. + """, + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr + + +def test_version() -> None: + """``vuforia-cloud-reco --version`` shows the version.""" + runner = CliRunner() + commands = ["--version"] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert result.stdout.startswith("vuforia-cloud-reco, version ") + + +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() + 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 = CloudDatabase() + mock.add_cloud_database(cloud_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(object=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( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + + [match] = yaml.safe_load(stream=result.stdout) + assert match["target_id"] == target_id + + +def test_env_var_credentials( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, + mock_database: CloudDatabase, +) -> None: + """ + It is possible to use environment variables to set the + credentials. + """ + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [str(object=new_file)] + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + env={ + "VUFORIA_CLIENT_ACCESS_KEY": mock_database.client_access_key, + "VUFORIA_CLIENT_SECRET_KEY": mock_database.client_secret_key, + }, + ) + assert result.exit_code == 0 diff --git a/tests/query/test_timeouts.py b/tests/query/test_timeouts.py new file mode 100644 index 00000000..482bde9e --- /dev/null +++ b/tests/query/test_timeouts.py @@ -0,0 +1,163 @@ +"""Tests for `query` timeouts commands.""" + +import datetime +import io +import uuid +from pathlib import Path + +import pytest +import requests +from click.testing import CliRunner +from freezegun import freeze_time +from mock_vws import MockVWS +from mock_vws.database import CloudDatabase + +from vws_cli.query import vuforia_cloud_reco + + +@pytest.mark.parametrize( + argnames=("response_delay_seconds", "expect_timeout"), + argvalues=[(29, False), (31, True)], +) +def test_default_timeout( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, + response_delay_seconds: int, + expect_timeout: bool, +) -> None: + """At 29 seconds there is no error; at 31 seconds there is a + timeout. + """ + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=response_delay_seconds, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + commands = [ + str(object=new_file), + "--client-access-key", + database.client_access_key, + "--client-secret-key", + database.client_secret_key, + ] + + if expect_timeout: + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + _ = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + else: + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + + +def test_custom_timeout( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """Custom connection and read timeouts are respected.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=5, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + commands = [ + str(object=new_file), + "--client-access-key", + database.client_access_key, + "--client-secret-key", + database.client_secret_key, + "--read-timeout-seconds", + "1", + ] + + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + _ = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + + +def test_custom_timeout_no_error( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """A sufficiently large timeout does not cause an error.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=5, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + commands = [ + str(object=new_file), + "--client-access-key", + database.client_access_key, + "--client-secret-key", + database.client_secret_key, + "--read-timeout-seconds", + "60", + ] + + result = runner.invoke( + cli=vuforia_cloud_reco, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 diff --git a/tests/test_query.py b/tests/test_query.py deleted file mode 100644 index d507e185..00000000 --- a/tests/test_query.py +++ /dev/null @@ -1,857 +0,0 @@ -"""Test for the Cloud Reco Service commands.""" - -import datetime -import io -import uuid -from pathlib import Path -from textwrap import dedent - -import pytest -import requests -import yaml -from click.testing import CliRunner -from freezegun import freeze_time -from mock_vws import MockVWS -from mock_vws.database import CloudDatabase -from vws import VWS - -from vws_cli.query import vuforia_cloud_reco - -# Tests for making image queries. - - -def test_no_matches( - *, - mock_database: CloudDatabase, - tmp_path: Path, - high_quality_image: io.BytesIO, -) -> None: - """An empty list is returned if there are no matches.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - assert result_data == [] - - -def test_matches( - *, - tmp_path: Path, - high_quality_image: io.BytesIO, - vws_client: VWS, - mock_database: CloudDatabase, -) -> None: - """Details of matching targets are shown.""" - name = uuid.uuid4().hex - target_id = vws_client.add_target( - name=name, - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - vws_client.wait_for_target_processed(target_id=target_id) - - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - [matching_target] = result_data - target_timestamp: object = matching_target["target_data"][ - "target_timestamp" - ] - expected_result_data = { - "target_data": { - "application_metadata": None, - "name": name, - "target_timestamp": target_timestamp, - }, - "target_id": target_id, - } - assert matching_target == expected_result_data - - -def test_image_file_is_dir( - *, - tmp_path: Path, - mock_database: CloudDatabase, -) -> None: - """ - An appropriate error is given if the given image file path - points to a - directory. - """ - runner = CliRunner() - commands = [ - str(object=tmp_path), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ - Usage: vuforia-cloud-reco [OPTIONS] IMAGE - Try 'vuforia-cloud-reco --help' for help. - - Error: Invalid value for 'IMAGE': File '{tmp_path}' is a directory. - """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - -def test_relative_path( - *, - tmp_path: Path, - mock_database: CloudDatabase, - high_quality_image: io.BytesIO, -) -> None: - """Image file paths are resolved.""" - runner = CliRunner() - new_filename = uuid.uuid4().hex - original_image_file = tmp_path / "foo" - image_data = high_quality_image.getvalue() - _ = original_image_file.write_bytes(data=image_data) - commands = [ - str(object=new_filename), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - with runner.isolated_filesystem(): - new_file = Path(new_filename) - new_file.symlink_to(target=original_image_file) - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - assert result_data == [] - - -def test_image_file_does_not_exist( - *, - mock_database: CloudDatabase, - tmp_path: Path, -) -> None: - """ - An appropriate error is given if the given image file does not - exist. - """ - runner = CliRunner() - does_not_exist_file = tmp_path / uuid.uuid4().hex - commands = [ - str(object=does_not_exist_file), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ - Usage: vuforia-cloud-reco [OPTIONS] IMAGE - Try 'vuforia-cloud-reco --help' for help. - - Error: Invalid value for 'IMAGE': File '{does_not_exist_file}' does not exist. - """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - -# Tests for the default request timeout. - - -@pytest.mark.parametrize( - argnames=("response_delay_seconds", "expect_timeout"), - argvalues=[(29, False), (31, True)], -) -def test_default_timeout( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - response_delay_seconds: int, - expect_timeout: bool, -) -> None: - """At 29 seconds there is no error; at 31 seconds there is a - timeout. - """ - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=response_delay_seconds, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - str(object=new_file), - "--client-access-key", - database.client_access_key, - "--client-secret-key", - database.client_secret_key, - ] - - if expect_timeout: - with pytest.raises( - expected_exception=requests.exceptions.Timeout, - ): - _ = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - else: - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - - -# Tests for custom request timeout options. - - -def test_custom_timeout( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, -) -> None: - """Custom connection and read timeouts are respected.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=5, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - str(object=new_file), - "--client-access-key", - database.client_access_key, - "--client-secret-key", - database.client_secret_key, - "--read-timeout-seconds", - "1", - ] - - with pytest.raises( - expected_exception=requests.exceptions.Timeout, - ): - _ = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - - -def test_custom_timeout_no_error( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, -) -> None: - """A sufficiently large timeout does not cause an error.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=5, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - str(object=new_file), - "--client-access-key", - database.client_access_key, - "--client-secret-key", - database.client_secret_key, - "--read-timeout-seconds", - "60", - ] - - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - - -def test_version() -> None: - """``vuforia-cloud-reco --version`` shows the version.""" - runner = CliRunner() - commands = ["--version"] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert result.stdout.startswith("vuforia-cloud-reco, version ") - - -# Tests for the ``--max-num-results`` option. - - -def test_max_num_results_default( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, -) -> None: - """By default the maximum number of results is 1.""" - runner = CliRunner() - 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) - - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - assert len(result_data) == 1 - - -def test_custom( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, -) -> None: - """It is possible to set a custom ``--max-num-results``.""" - runner = CliRunner() - 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) - - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - max_num_results = 2 - commands = [ - str(object=new_file), - "--max-num-results", - str(object=max_num_results), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - assert len(result_data) == max_num_results - - -def test_out_of_range( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, -) -> None: - """``--max-num-results`` must be between 1 and 50.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=0), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - expected_stderr_substring = ( - "Error: Invalid value for '--max-num-results': 0 is not in the " - "range 1<=x<=50." - ) - assert expected_stderr_substring in result.stderr - - -# Tests for the ``--include-target-data`` option. - - -def test_include_target_data_default( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, -) -> None: - """By default, target data is only returned in the top match.""" - runner = CliRunner() - 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) - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=2), - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - matches = yaml.safe_load(stream=result.stdout) - top_match, second_match = matches - assert top_match["target_data"] is not None - assert second_match["target_data"] is None - - -def test_top( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, -) -> None: - """ - When 'top' is given, target data is only returned in the top - match. - """ - runner = CliRunner() - 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) - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=2), - "--include-target-data", - "top", - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - matches = yaml.safe_load(stream=result.stdout) - top_match, second_match = matches - assert top_match["target_data"] is not None - assert second_match["target_data"] is None - - -def test_none( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, -) -> None: - """ - When 'none' is given, target data is not returned in any - match. - """ - runner = CliRunner() - 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) - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=2), - "--include-target-data", - "none", - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - matches = yaml.safe_load(stream=result.stdout) - top_match, second_match = matches - assert top_match["target_data"] is None - assert second_match["target_data"] is None - - -def test_all( - *, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, -) -> None: - """When 'all' is given, target data is returned in all matches.""" - runner = CliRunner() - 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) - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - - commands = [ - str(object=new_file), - "--max-num-results", - str(object=2), - "--include-target-data", - "all", - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - matches = yaml.safe_load(stream=result.stdout) - top_match, second_match = matches - assert top_match["target_data"] is not None - assert second_match["target_data"] is not None - - -def test_other( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, -) -> None: - """ - When a string other than 'top', 'all', or 'none' is given, an - error is - shown. - """ - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - str(object=new_file), - "--max-num-results", - str(object=2), - "--include-target-data", - "other", - "--client-access-key", - mock_database.client_access_key, - "--client-secret-key", - mock_database.client_secret_key, - ] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - expected_stderr = ( - "'--include-target-data': 'other' is not one of 'top', 'none', 'all'." - ) - assert expected_stderr in result.stderr - - -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() - 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 = CloudDatabase() - mock.add_cloud_database(cloud_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(object=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( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - - [match] = yaml.safe_load(stream=result.stdout) - assert match["target_id"] == target_id - - -def test_env_var_credentials( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - mock_database: CloudDatabase, -) -> None: - """ - It is possible to use environment variables to set the - credentials. - """ - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [str(object=new_file)] - result = runner.invoke( - cli=vuforia_cloud_reco, - args=commands, - catch_exceptions=False, - color=True, - env={ - "VUFORIA_CLIENT_ACCESS_KEY": mock_database.client_access_key, - "VUFORIA_CLIENT_SECRET_KEY": mock_database.client_secret_key, - }, - ) - assert result.exit_code == 0 diff --git a/tests/test_vumark.py b/tests/test_vumark.py index 9cf16a30..c9f1fc87 100644 --- a/tests/test_vumark.py +++ b/tests/test_vumark.py @@ -15,9 +15,6 @@ generate_vumark = vumark_module.generate_vumark -# Tests for ``vumark``. - - @pytest.mark.parametrize( argnames=("format_name", "expected_prefix"), argvalues=[ diff --git a/tests/test_vws_commands.py b/tests/test_vws_commands.py deleted file mode 100644 index c4be30f6..00000000 --- a/tests/test_vws_commands.py +++ /dev/null @@ -1,1368 +0,0 @@ -"""Tests for VWS CLI commands.""" - -import base64 -import datetime -import io -import secrets -import uuid -from pathlib import Path -from textwrap import dedent - -import pytest -import requests -import yaml -from click.testing import CliRunner -from freezegun import freeze_time -from mock_vws import MockVWS -from mock_vws.database import CloudDatabase -from vws import VWS, CloudRecoService -from vws.reports import TargetStatuses - -from vws_cli import vws_group - - -def test_get_database_summary_report( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, -) -> None: - """It is possible to get a database summary report.""" - runner = CliRunner() - for name in ("a", "b"): - _ = vws_client.add_target( - name=name, - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - - commands = [ - "get-database-summary-report", - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - expected_result_data = { - "active_images": 0, - "current_month_recos": 0, - "failed_images": 0, - "inactive_images": 0, - "name": mock_database.database_name, - "previous_month_recos": 0, - "processing_images": 2, - "reco_threshold": 1000, - "request_quota": 100000, - "request_usage": 0, - "target_quota": 1000, - "total_recos": 0, - } - assert result_data == expected_result_data - - -def test_list_targets( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, -) -> None: - """It is possible to get a list of targets in the database.""" - runner = CliRunner() - target_id_1 = vws_client.add_target( - name="x1", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - target_id_2 = vws_client.add_target( - name="x2", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - commands = [ - "list-targets", - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - expected_result_data = [target_id_1, target_id_2] - # We do not expect a particular order. - assert sorted(result_data) == sorted(expected_result_data) - - -def test_get_target_record( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, -) -> None: - """It is possible to get a target record.""" - runner = CliRunner() - target_id = vws_client.add_target( - name="x", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - commands = [ - "get-target-record", - "--target-id", - target_id, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - expected_result_data = { - "active_flag": True, - "name": "x", - "reco_rating": "", - "target_id": target_id, - "tracking_rating": -1, - "width": 1, - } - assert result_data == expected_result_data - - -def test_get_target_summary_report( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, -) -> None: - """It is possible to get a target summary report.""" - runner = CliRunner() - upload_date = "2015-04-29" - with freeze_time(time_to_freeze=upload_date): - target_id = vws_client.add_target( - name="x", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - - commands = [ - "get-target-summary-report", - "--target-id", - target_id, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - expected_result_data = { - "active_flag": True, - "current_month_recos": 0, - "database_name": mock_database.database_name, - "previous_month_recos": 0, - "status": "success", - "target_name": "x", - "total_recos": 0, - "tracking_rating": result_data["tracking_rating"], - "upload_date": upload_date, - } - assert result_data == expected_result_data - - -def test_delete_target( - *, - mock_database: CloudDatabase, - 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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert not bool(result.stdout) - assert vws_client.list_targets() == [] - - -def test_get_duplicate_targets( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, -) -> None: - """It is possible to get a list of duplicate targets.""" - runner = CliRunner() - target_id = vws_client.add_target( - name="x", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - target_id_2 = vws_client.add_target( - name="x2", - 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) - - commands = [ - "get-duplicate-targets", - "--target-id", - target_id, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - result_data = yaml.safe_load(stream=result.stdout) - expected_result_data = [target_id_2] - assert result_data == expected_result_data - - -# Tests for the default request timeout. - - -@pytest.mark.parametrize( - argnames=("response_delay_seconds", "expect_timeout"), - argvalues=[(29, False), (31, True)], -) -def test_default_timeout( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, - response_delay_seconds: int, - expect_timeout: bool, -) -> None: - """At 29 seconds there is no error; at 31 seconds there is a - timeout. - """ - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=response_delay_seconds, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - "add-target", - "--name", - uuid.uuid4().hex, - "--width", - "1", - "--image", - str(object=new_file), - "--server-access-key", - database.server_access_key, - "--server-secret-key", - database.server_secret_key, - ] - - if expect_timeout: - with pytest.raises( - expected_exception=requests.exceptions.Timeout, - ): - _ = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - else: - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - - -# Tests for custom request timeout options. - - -def test_custom_request_timeout_custom_timeout( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, -) -> None: - """Custom connection and read timeouts are respected.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=5, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - "add-target", - "--name", - uuid.uuid4().hex, - "--width", - "1", - "--image", - str(object=new_file), - "--server-access-key", - database.server_access_key, - "--server-secret-key", - database.server_secret_key, - "--read-timeout-seconds", - "1", - ] - - with pytest.raises( - expected_exception=requests.exceptions.Timeout, - ): - _ = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - - -def test_custom_timeout_no_error( - *, - high_quality_image: io.BytesIO, - tmp_path: Path, -) -> None: - """A sufficiently large timeout does not cause an error.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - with ( - freeze_time() as frozen_datetime, - MockVWS( - response_delay_seconds=5, - sleep_fn=lambda seconds: ( - frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), - None, - )[1], - ) as mock, - ): - database = CloudDatabase() - mock.add_cloud_database(cloud_database=database) - commands = [ - "add-target", - "--name", - uuid.uuid4().hex, - "--width", - "1", - "--image", - str(object=new_file), - "--server-access-key", - database.server_access_key, - "--server-secret-key", - database.server_secret_key, - "--read-timeout-seconds", - "60", - ] - - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - - -# Tests for ``vws add-target``. - - -def test_add_target( - *, - mock_database: CloudDatabase, - 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 = secrets.choice(seq=range(1, 5000)) / 100 - commands = [ - "add-target", - "--name", - name, - "--width", - str(object=width), - "--image", - str(object=new_file), - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0, result.output - - 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_add_target_image_file_does_not_exist( - *, - mock_database: CloudDatabase, - tmp_path: Path, -) -> None: - """ - An appropriate error is given if the given image file does not - exist. - """ - runner = CliRunner() - does_not_exist_file = tmp_path / uuid.uuid4().hex - commands = [ - "add-target", - "--name", - "foo", - "--width", - "1", - "--image", - str(object=does_not_exist_file), - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ - Usage: vws add-target [OPTIONS] - Try 'vws add-target --help' for help. - - Error: Invalid value for '--image': File '{does_not_exist_file}' does not exist. - """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - -def test_add_target_image_file_is_dir( - *, - mock_database: CloudDatabase, - tmp_path: Path, -) -> None: - """ - An appropriate error is given if the given image file path - points to a - directory. - """ - runner = CliRunner() - commands = [ - "add-target", - "--name", - "foo", - "--width", - "1", - "--image", - str(object=tmp_path), - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ - Usage: vws add-target [OPTIONS] - Try 'vws add-target --help' for help. - - Error: Invalid value for '--image': File '{tmp_path}' is a directory. - """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - -def test_add_target_relative_path( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, -) -> None: - """Image file paths are resolved.""" - runner = CliRunner() - new_filename = uuid.uuid4().hex - original_image_file = tmp_path / "foo" - image_data = high_quality_image.getvalue() - _ = original_image_file.write_bytes(data=image_data) - name = uuid.uuid4().hex - commands = [ - "add-target", - "--name", - name, - "--width", - "1", - "--image", - new_filename, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - with runner.isolated_filesystem(): - new_file = Path(new_filename) - new_file.symlink_to(target=original_image_file) - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - target_id = result.stdout.strip() - target_record = vws_client.get_target_record(target_id=target_id) - assert target_record.target_record.name == name - - -def test_custom_metadata( - *, - mock_database: CloudDatabase, - 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(encoding="ascii") - base64_encoded_metadata_bytes = base64.b64encode(s=metadata_bytes) - base64_encoded_metadata = base64_encoded_metadata_bytes.decode( - encoding="ascii", - ) - commands = [ - "add-target", - "--name", - name, - "--width", - "0.1", - "--image", - str(object=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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - 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 - - -@pytest.mark.parametrize( - argnames=("active_flag_given", "active_flag_expected"), - argvalues=[ - ("true", True), - ("false", False), - ], -) -def test_custom_active_flag( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, - active_flag_given: str, - active_flag_expected: bool, -) -> None: - """The Active Flag of the new target can be chosen.""" - runner = CliRunner() - new_file = tmp_path / uuid.uuid4().hex - image_data = high_quality_image.getvalue() - _ = new_file.write_bytes(data=image_data) - commands = [ - "add-target", - "--name", - "foo", - "--width", - "0.1", - "--image", - str(object=new_file), - "--active-flag", - active_flag_given, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - - target_id = result.stdout.strip() - target_details = vws_client.get_target_record(target_id=target_id) - active_flag = target_details.target_record.active_flag - assert active_flag is active_flag_expected - - -# Tests for ``vws wait-for-target-processed``. - - -def test_wait_for_target_processed( - *, - mock_database: CloudDatabase, - 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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert not bool(result.stdout) - report = vws_client.get_target_summary_report(target_id=target_id) - assert report.status != TargetStatuses.PROCESSING - - -def test_default_seconds_between_requests( - high_quality_image: io.BytesIO, -) -> None: - """By default, 0.2 seconds are waited between polling requests.""" - runner = CliRunner() - with MockVWS(processing_time_seconds=0.5) as mock: - mock_database = CloudDatabase() - mock.add_cloud_database(cloud_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, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert not bool(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.2 seconds - not processed - 1 - + - # Request after 0.4 seconds - not processed - # This assumes that there is less than 0.1 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_seconds_between_requests( - 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 = CloudDatabase() - mock.add_cloud_database(cloud_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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert not bool(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 test_custom_seconds_too_small(mock_database: CloudDatabase) -> None: - """ - The minimum valid value for ``--seconds-between-requests`` is - 0.05 - seconds. - """ - runner = CliRunner() - commands = [ - "wait-for-target-processed", - "--target-id", - "x", - "--seconds-between-requests", - "0.01", - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code != 0 - assert not bool(result.stdout) - expected_substring = "0.01 is not in the range x>=0.05." - assert expected_substring in result.stderr - - -def test_wait_for_target_processed_custom_timeout( - high_quality_image: io.BytesIO, -) -> None: - """It is possible to set a maximum timeout.""" - runner = CliRunner() - with MockVWS(processing_time_seconds=0.5) as mock: - mock_database = CloudDatabase() - mock.add_cloud_database(cloud_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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code != 0 - assert result.stderr == ( - "Error: The target processing time has exceeded the allowed " - "limit.\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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - report = vws_client.get_target_summary_report(target_id=target_id) - assert report.status != TargetStatuses.PROCESSING - - -def test_custom_timeout_too_small(mock_database: CloudDatabase) -> None: - """ - The minimum valid value for ``--timeout-seconds`` is 0.05 - seconds. - """ - runner = CliRunner() - commands = [ - "wait-for-target-processed", - "--target-id", - "x", - "--timeout-seconds", - "0.01", - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code != 0 - expected_substring = "0.01 is not in the range x>=0.05." - assert expected_substring in result.stderr - - -# Tests for ``vws update-target``. - - -def test_update_target( - *, - mock_database: CloudDatabase, - 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() - old_name = uuid.uuid4().hex - old_width = secrets.choice(seq=range(1, 5000)) / 100 - 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(s=b"a").decode( - encoding="ascii", - ) - new_name = uuid.uuid4().hex - new_width = secrets.choice(seq=range(1, 5000)) / 100 - 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(object=new_width), - "--image", - str(object=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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert not bool(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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert not bool(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 - - -def test_no_fields_given( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, -) -> None: - """It is possible to give no update fields.""" - runner = CliRunner() - old_name = uuid.uuid4().hex - old_width = secrets.choice(seq=range(1, 5000)) / 100 - 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( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - assert result.exit_code == 0 - assert not bool(result.stdout) - - -def test_update_target_image_file_does_not_exist( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, -) -> None: - """ - An appropriate error is given if the given image file does not - exist. - """ - 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) - runner = CliRunner() - does_not_exist_file = tmp_path / uuid.uuid4().hex - commands = [ - "update-target", - "--target-id", - target_id, - "--image", - str(object=does_not_exist_file), - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ - Usage: vws update-target [OPTIONS] - Try 'vws update-target --help' for help. - - Error: Invalid value for '--image': File '{does_not_exist_file}' does not exist. - """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - -def test_update_target_image_file_is_dir( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, -) -> None: - """ - An appropriate error is given if the given image file path - points to a - directory. - """ - 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) - runner = CliRunner() - commands = [ - "update-target", - "--target-id", - target_id, - "--image", - str(object=tmp_path), - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - expected_result_code = 2 - assert result.exit_code == expected_result_code - assert not bool(result.stdout) - expected_stderr = dedent( - text=f"""\ - Usage: vws update-target [OPTIONS] - Try 'vws update-target --help' for help. - - Error: Invalid value for '--image': File '{tmp_path}' is a directory. - """, - ).replace("\\", "\\\\") - assert result.stderr == expected_stderr - - -def test_update_target_relative_path( - *, - mock_database: CloudDatabase, - vws_client: VWS, - high_quality_image: io.BytesIO, - tmp_path: Path, -) -> None: - """Image file paths are resolved.""" - runner = CliRunner() - 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) - new_filename = uuid.uuid4().hex - original_image_file = tmp_path / "foo" - image_data = high_quality_image.getvalue() - _ = original_image_file.write_bytes(data=image_data) - commands = [ - "update-target", - "--target-id", - target_id, - "--image", - new_filename, - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - ] - - with runner.isolated_filesystem(): - new_file = Path(new_filename) - new_file.symlink_to(target=original_image_file) - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - - assert result.exit_code == 0 - - -def test_custom_base_url() -> None: - """ - It is possible to use the API to connect to a database under a - custom VWS - URL. - """ - runner = CliRunner() - base_vws_url = "http://example.com" - mock_database = CloudDatabase() - commands = [ - "list-targets", - "--server-access-key", - mock_database.server_access_key, - "--server-secret-key", - mock_database.server_secret_key, - "--base-vws-url", - base_vws_url, - ] - with MockVWS(base_vws_url=base_vws_url) as mock: - mock.add_cloud_database(cloud_database=mock_database) - result = runner.invoke( - cli=vws_group, - args=commands, - catch_exceptions=False, - color=True, - ) - - assert result.exit_code == 0 diff --git a/tests/vws/__init__.py b/tests/vws/__init__.py new file mode 100644 index 00000000..d56bfb0c --- /dev/null +++ b/tests/vws/__init__.py @@ -0,0 +1 @@ +"""Vws tests.""" diff --git a/tests/vws/test_add_target.py b/tests/vws/test_add_target.py new file mode 100644 index 00000000..4b098e6a --- /dev/null +++ b/tests/vws/test_add_target.py @@ -0,0 +1,297 @@ +"""Tests for `vws` add target commands.""" + +import base64 +import io +import secrets +import uuid +from pathlib import Path +from textwrap import dedent + +import pytest +from click.testing import CliRunner +from mock_vws.database import CloudDatabase +from vws import VWS, CloudRecoService + +from vws_cli import vws_group + + +def test_add_target( + *, + mock_database: CloudDatabase, + 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 = secrets.choice(seq=range(1, 5000)) / 100 + commands = [ + "add-target", + "--name", + name, + "--width", + str(object=width), + "--image", + str(object=new_file), + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0, result.output + + 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_image_file_does_not_exist( + *, + mock_database: CloudDatabase, + tmp_path: Path, +) -> None: + """ + An appropriate error is given if the given image file does not + exist. + """ + runner = CliRunner() + does_not_exist_file = tmp_path / uuid.uuid4().hex + commands = [ + "add-target", + "--name", + "foo", + "--width", + "1", + "--image", + str(object=does_not_exist_file), + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ + Usage: vws add-target [OPTIONS] + Try 'vws add-target --help' for help. + + Error: Invalid value for '--image': File '{does_not_exist_file}' does not exist. + """, + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr + + +def test_image_file_is_dir( + *, + mock_database: CloudDatabase, + tmp_path: Path, +) -> None: + """ + An appropriate error is given if the given image file path + points to a + directory. + """ + runner = CliRunner() + commands = [ + "add-target", + "--name", + "foo", + "--width", + "1", + "--image", + str(object=tmp_path), + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ + Usage: vws add-target [OPTIONS] + Try 'vws add-target --help' for help. + + Error: Invalid value for '--image': File '{tmp_path}' is a directory. + """, + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr + + +def test_relative_path( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """Image file paths are resolved.""" + runner = CliRunner() + new_filename = uuid.uuid4().hex + original_image_file = tmp_path / "foo" + image_data = high_quality_image.getvalue() + _ = original_image_file.write_bytes(data=image_data) + name = uuid.uuid4().hex + commands = [ + "add-target", + "--name", + name, + "--width", + "1", + "--image", + new_filename, + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + with runner.isolated_filesystem(): + new_file = Path(new_filename) + new_file.symlink_to(target=original_image_file) + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + target_id = result.stdout.strip() + target_record = vws_client.get_target_record(target_id=target_id) + assert target_record.target_record.name == name + + +def test_custom_metadata( + *, + mock_database: CloudDatabase, + 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(encoding="ascii") + base64_encoded_metadata_bytes = base64.b64encode(s=metadata_bytes) + base64_encoded_metadata = base64_encoded_metadata_bytes.decode( + encoding="ascii", + ) + commands = [ + "add-target", + "--name", + name, + "--width", + "0.1", + "--image", + str(object=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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + 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 + + +@pytest.mark.parametrize( + argnames=("active_flag_given", "active_flag_expected"), + argvalues=[ + ("true", True), + ("false", False), + ], +) +def test_custom_active_flag( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, + active_flag_given: str, + active_flag_expected: bool, +) -> None: + """The Active Flag of the new target can be chosen.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + commands = [ + "add-target", + "--name", + "foo", + "--width", + "0.1", + "--image", + str(object=new_file), + "--active-flag", + active_flag_given, + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + + target_id = result.stdout.strip() + target_details = vws_client.get_target_record(target_id=target_id) + active_flag = target_details.target_record.active_flag + assert active_flag is active_flag_expected diff --git a/tests/vws/test_configuration.py b/tests/vws/test_configuration.py new file mode 100644 index 00000000..f0ec10c5 --- /dev/null +++ b/tests/vws/test_configuration.py @@ -0,0 +1,211 @@ +"""Tests for `vws` configuration commands.""" + +import datetime +import io +import uuid +from pathlib import Path + +import pytest +import requests +from click.testing import CliRunner +from freezegun import freeze_time +from mock_vws import MockVWS +from mock_vws.database import CloudDatabase + +from vws_cli import vws_group + + +@pytest.mark.parametrize( + argnames=("response_delay_seconds", "expect_timeout"), + argvalues=[(29, False), (31, True)], +) +def test_default_timeout( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, + response_delay_seconds: int, + expect_timeout: bool, +) -> None: + """At 29 seconds there is no error; at 31 seconds there is a + timeout. + """ + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=response_delay_seconds, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + commands = [ + "add-target", + "--name", + uuid.uuid4().hex, + "--width", + "1", + "--image", + str(object=new_file), + "--server-access-key", + database.server_access_key, + "--server-secret-key", + database.server_secret_key, + ] + + if expect_timeout: + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + _ = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + else: + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + + +def test_custom_timeout( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """Custom connection and read timeouts are respected.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=5, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + commands = [ + "add-target", + "--name", + uuid.uuid4().hex, + "--width", + "1", + "--image", + str(object=new_file), + "--server-access-key", + database.server_access_key, + "--server-secret-key", + database.server_secret_key, + "--read-timeout-seconds", + "1", + ] + + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + _ = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + + +def test_custom_timeout_no_error( + *, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """A sufficiently large timeout does not cause an error.""" + runner = CliRunner() + new_file = tmp_path / uuid.uuid4().hex + image_data = high_quality_image.getvalue() + _ = new_file.write_bytes(data=image_data) + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=5, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + commands = [ + "add-target", + "--name", + uuid.uuid4().hex, + "--width", + "1", + "--image", + str(object=new_file), + "--server-access-key", + database.server_access_key, + "--server-secret-key", + database.server_secret_key, + "--read-timeout-seconds", + "60", + ] + + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + + +def test_custom_base_url() -> None: + """ + It is possible to use the API to connect to a database under a + custom VWS + URL. + """ + runner = CliRunner() + base_vws_url = "http://example.com" + mock_database = CloudDatabase() + commands = [ + "list-targets", + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + "--base-vws-url", + base_vws_url, + ] + with MockVWS(base_vws_url=base_vws_url) as mock: + mock.add_cloud_database(cloud_database=mock_database) + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + + assert result.exit_code == 0 diff --git a/tests/vws/test_processing.py b/tests/vws/test_processing.py new file mode 100644 index 00000000..c84ea53d --- /dev/null +++ b/tests/vws/test_processing.py @@ -0,0 +1,313 @@ +"""Tests for `vws` processing commands.""" + +import io + +from click.testing import CliRunner +from mock_vws import MockVWS +from mock_vws.database import CloudDatabase +from vws import VWS +from vws.reports import TargetStatuses + +from vws_cli import vws_group + + +def test_wait_for_target_processed( + *, + mock_database: CloudDatabase, + 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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(result.stdout) + report = vws_client.get_target_summary_report(target_id=target_id) + assert report.status != TargetStatuses.PROCESSING + + +def test_default_seconds_between_requests( + high_quality_image: io.BytesIO, +) -> None: + """By default, 0.2 seconds are waited between polling requests.""" + runner = CliRunner() + with MockVWS(processing_time_seconds=0.5) as mock: + mock_database = CloudDatabase() + mock.add_cloud_database(cloud_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, + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(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.2 seconds - not processed + 1 + + + # Request after 0.4 seconds - not processed + # This assumes that there is less than 0.1 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_seconds_between_requests( + 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 = CloudDatabase() + mock.add_cloud_database(cloud_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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(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 test_custom_seconds_too_small(mock_database: CloudDatabase) -> None: + """ + The minimum valid value for ``--seconds-between-requests`` is + 0.05 + seconds. + """ + runner = CliRunner() + commands = [ + "wait-for-target-processed", + "--target-id", + "x", + "--seconds-between-requests", + "0.01", + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code != 0 + assert not bool(result.stdout) + expected_substring = "0.01 is not in the range x>=0.05." + assert expected_substring in result.stderr + + +def test_custom_timeout(high_quality_image: io.BytesIO) -> None: + """It is possible to set a maximum timeout.""" + runner = CliRunner() + with MockVWS(processing_time_seconds=0.5) as mock: + mock_database = CloudDatabase() + mock.add_cloud_database(cloud_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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code != 0 + assert result.stderr == ( + "Error: The target processing time has exceeded the allowed " + "limit.\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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + report = vws_client.get_target_summary_report(target_id=target_id) + assert report.status != TargetStatuses.PROCESSING + + +def test_custom_timeout_too_small(mock_database: CloudDatabase) -> None: + """ + The minimum valid value for ``--timeout-seconds`` is 0.05 + seconds. + """ + runner = CliRunner() + commands = [ + "wait-for-target-processed", + "--target-id", + "x", + "--timeout-seconds", + "0.01", + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code != 0 + expected_substring = "0.01 is not in the range x>=0.05." + assert expected_substring in result.stderr diff --git a/tests/vws/test_reports.py b/tests/vws/test_reports.py new file mode 100644 index 00000000..b646ebcd --- /dev/null +++ b/tests/vws/test_reports.py @@ -0,0 +1,109 @@ +"""Tests for `vws` reports commands.""" + +import io + +import yaml +from click.testing import CliRunner +from freezegun import freeze_time +from mock_vws.database import CloudDatabase +from vws import VWS + +from vws_cli import vws_group + + +def test_get_database_summary_report( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, +) -> None: + """It is possible to get a database summary report.""" + runner = CliRunner() + for name in ("a", "b"): + _ = vws_client.add_target( + name=name, + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + + commands = [ + "get-database-summary-report", + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + expected_result_data = { + "active_images": 0, + "current_month_recos": 0, + "failed_images": 0, + "inactive_images": 0, + "name": mock_database.database_name, + "previous_month_recos": 0, + "processing_images": 2, + "reco_threshold": 1000, + "request_quota": 100000, + "request_usage": 0, + "target_quota": 1000, + "total_recos": 0, + } + assert result_data == expected_result_data + + +def test_get_target_summary_report( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, +) -> None: + """It is possible to get a target summary report.""" + runner = CliRunner() + upload_date = "2015-04-29" + with freeze_time(time_to_freeze=upload_date): + target_id = vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + + commands = [ + "get-target-summary-report", + "--target-id", + target_id, + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + expected_result_data = { + "active_flag": True, + "current_month_recos": 0, + "database_name": mock_database.database_name, + "previous_month_recos": 0, + "status": "success", + "target_name": "x", + "total_recos": 0, + "tracking_rating": result_data["tracking_rating"], + "upload_date": upload_date, + } + assert result_data == expected_result_data diff --git a/tests/vws/test_targets.py b/tests/vws/test_targets.py new file mode 100644 index 00000000..4b1e7571 --- /dev/null +++ b/tests/vws/test_targets.py @@ -0,0 +1,179 @@ +"""Tests for `vws` targets commands.""" + +import io + +import yaml +from click.testing import CliRunner +from mock_vws.database import CloudDatabase +from vws import VWS + +from vws_cli import vws_group + + +def test_list_targets( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, +) -> None: + """It is possible to get a list of targets in the database.""" + runner = CliRunner() + target_id_1 = vws_client.add_target( + name="x1", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + target_id_2 = vws_client.add_target( + name="x2", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + commands = [ + "list-targets", + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + expected_result_data = [target_id_1, target_id_2] + # We do not expect a particular order. + assert sorted(result_data) == sorted(expected_result_data) + + +def test_get_target_record( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, +) -> None: + """It is possible to get a target record.""" + runner = CliRunner() + target_id = vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + commands = [ + "get-target-record", + "--target-id", + target_id, + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + expected_result_data = { + "active_flag": True, + "name": "x", + "reco_rating": "", + "target_id": target_id, + "tracking_rating": -1, + "width": 1, + } + assert result_data == expected_result_data + + +def test_delete_target( + *, + mock_database: CloudDatabase, + 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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(result.stdout) + assert vws_client.list_targets() == [] + + +def test_get_duplicate_targets( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, +) -> None: + """It is possible to get a list of duplicate targets.""" + runner = CliRunner() + target_id = vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + target_id_2 = vws_client.add_target( + name="x2", + 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) + + commands = [ + "get-duplicate-targets", + "--target-id", + target_id, + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + result_data = yaml.safe_load(stream=result.stdout) + expected_result_data = [target_id_2] + assert result_data == expected_result_data diff --git a/tests/vws/test_update_target.py b/tests/vws/test_update_target.py new file mode 100644 index 00000000..307d6736 --- /dev/null +++ b/tests/vws/test_update_target.py @@ -0,0 +1,297 @@ +"""Tests for `vws` update target commands.""" + +import base64 +import io +import secrets +import uuid +from pathlib import Path +from textwrap import dedent + +from click.testing import CliRunner +from mock_vws.database import CloudDatabase +from vws import VWS, CloudRecoService + +from vws_cli import vws_group + + +def test_update_target( + *, + mock_database: CloudDatabase, + 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() + old_name = uuid.uuid4().hex + old_width = secrets.choice(seq=range(1, 5000)) / 100 + 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(s=b"a").decode( + encoding="ascii", + ) + new_name = uuid.uuid4().hex + new_width = secrets.choice(seq=range(1, 5000)) / 100 + 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(object=new_width), + "--image", + str(object=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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(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 + + +def test_no_fields_given( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, +) -> None: + """It is possible to give no update fields.""" + runner = CliRunner() + old_name = uuid.uuid4().hex + old_width = secrets.choice(seq=range(1, 5000)) / 100 + 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( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + assert result.exit_code == 0 + assert not bool(result.stdout) + + +def test_image_file_does_not_exist( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """ + An appropriate error is given if the given image file does not + exist. + """ + 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) + runner = CliRunner() + does_not_exist_file = tmp_path / uuid.uuid4().hex + commands = [ + "update-target", + "--target-id", + target_id, + "--image", + str(object=does_not_exist_file), + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ + Usage: vws update-target [OPTIONS] + Try 'vws update-target --help' for help. + + Error: Invalid value for '--image': File '{does_not_exist_file}' does not exist. + """, + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr + + +def test_image_file_is_dir( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """ + An appropriate error is given if the given image file path + points to a + directory. + """ + 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) + runner = CliRunner() + commands = [ + "update-target", + "--target-id", + target_id, + "--image", + str(object=tmp_path), + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + expected_result_code = 2 + assert result.exit_code == expected_result_code + assert not bool(result.stdout) + expected_stderr = dedent( + text=f"""\ + Usage: vws update-target [OPTIONS] + Try 'vws update-target --help' for help. + + Error: Invalid value for '--image': File '{tmp_path}' is a directory. + """, + ).replace("\\", "\\\\") + assert result.stderr == expected_stderr + + +def test_relative_path( + *, + mock_database: CloudDatabase, + vws_client: VWS, + high_quality_image: io.BytesIO, + tmp_path: Path, +) -> None: + """Image file paths are resolved.""" + runner = CliRunner() + 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) + new_filename = uuid.uuid4().hex + original_image_file = tmp_path / "foo" + image_data = high_quality_image.getvalue() + _ = original_image_file.write_bytes(data=image_data) + commands = [ + "update-target", + "--target-id", + target_id, + "--image", + new_filename, + "--server-access-key", + mock_database.server_access_key, + "--server-secret-key", + mock_database.server_secret_key, + ] + + with runner.isolated_filesystem(): + new_file = Path(new_filename) + new_file.symlink_to(target=original_image_file) + result = runner.invoke( + cli=vws_group, + args=commands, + catch_exceptions=False, + color=True, + ) + + assert result.exit_code == 0