From 11e4b06f0e5ff50be96c37ab94358927178082aa Mon Sep 17 00:00:00 2001 From: ashdriod Date: Sat, 9 Dec 2023 16:52:38 +0100 Subject: [PATCH 01/11] WIP:TST --- dtool_lookup_gui/views/main_window.py | 13 +++++++++++++ test/test_main_window_actions.py | 13 +++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/dtool_lookup_gui/views/main_window.py b/dtool_lookup_gui/views/main_window.py index 0484b744..9c186c8a 100644 --- a/dtool_lookup_gui/views/main_window.py +++ b/dtool_lookup_gui/views/main_window.py @@ -315,6 +315,7 @@ def refresh(self): async def _refresh(): # first, refresh base uri list and its selection await self._refresh_base_uri_list_box() + self.select_and_load_first_uri() _logger.debug(f"Done refreshing base URIs.") # on_base_uri_selected(self, list_box, row) called by selection @@ -616,6 +617,7 @@ def do_refresh_view(self, action, value): """Refresh view by reloading base uri list, """ self.refresh() + # signal handlers @Gtk.Template.Callback() def on_settings_clicked(self, widget): @@ -1009,6 +1011,17 @@ def enable_pagination_buttons(self): self.page_advancer_button.set_sensitive(True) self.next_page_advancer_button.set_sensitive(True) + def select_and_load_first_uri(self): + """ + This function is used exclusively for testing purposes. It automatically reloads the data + and selects the first URI. This function is designed to operate only when there is exactly + one URI in the list box, ensuring that tests can be automated effectively. + """ + if len(self.base_uri_list_box.get_children()) == 1: + first_row = self.base_uri_list_box.get_children()[0] + self.base_uri_list_box.select_row(first_row) + self.on_base_uri_selected(self.base_uri_list_box, first_row) + # TODO: this should be an action do_copy # if it is possible to hand to strings, e.g. source and destination to an action, then this action should # go to the main app. diff --git a/test/test_main_window_actions.py b/test/test_main_window_actions.py index 62b94d33..e77627d4 100644 --- a/test/test_main_window_actions.py +++ b/test/test_main_window_actions.py @@ -41,13 +41,22 @@ async def test_do_get_item_direct_call_fails_due_to_no_selected_item(app): with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'dataset'"): app.main_window.do_get_item(None, mock_variant) + @pytest.mark.asyncio -@pytest.mark.skip(reason="no way of currently testing this") async def test_populate_dataset_list(populated_app): + """ + Generates test data and selects the first URI. This test triggers the 'refresh-view' action + to simulate the data loading process, which is necessary for validating the functionality + of the application in a test environment. + """ + + # Call the activate_action method to trigger the 'refresh-view' action populated_app.main_window.activate_action('refresh-view') - await asyncio.sleep(3600) + # Wait for a reasonable amount of time to ensure data loading and UI updates are complete + await asyncio.sleep(500) + @pytest.mark.asyncio @pytest.mark.skip(reason="no way of currently testing this") From 0d6339b76bf0e7658c9cfc17ea062eae5b9fd7b0 Mon Sep 17 00:00:00 2001 From: ashdriod Date: Sun, 10 Dec 2023 23:06:17 +0100 Subject: [PATCH 02/11] TST:WIP --- test/test_main_window_actions.py | 60 +++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/test/test_main_window_actions.py b/test/test_main_window_actions.py index e77627d4..00e3411c 100644 --- a/test/test_main_window_actions.py +++ b/test/test_main_window_actions.py @@ -4,6 +4,7 @@ # 4. Separating tests for direct calls and action triggers aids in maintaining clear, organized test structures. # 5. This approach enhances test suite readability and makes it easier to understand and update. import asyncio +import time import pytest from unittest.mock import patch, MagicMock, AsyncMock @@ -120,23 +121,12 @@ async def test_do_get_item_action_trigger(app): # TODO: let's start with this unit est and transform it into a proper test on GUI behavior @pytest.mark.asyncio -@pytest.mark.skip(reason="no way of currently testing this") async def test_do_search_select_and_show_direct_call(populated_app, mock_dataset_list): - """Test the do_search_select_and_show method for processing a search directly.""" - - # Mock dependencies - #mock_base_uri_list_box = MagicMock() - #populated_app.main_window.base_uri_list_box = mock_base_uri_list_box - - # Mock _search_select_and_show to simulate search behavior - #populated_app.main_window._search_select_and_show = MagicMock() - - # Create a mock action and variant - #mock_action = MagicMock(spec=Gio.SimpleAction) - mock_variant = GLib.Variant.new_string("test_search_query") - - # Directly call the method with mock objects - populated_app.main_window.do_search_select_and_show(None, mock_variant) + """ + Test the do_search_select_and_show method for processing a search directly. It verifies + if the dataset list is correctly populated, the first dataset is selected, and its + content is displayed. + """ # TODO: assert that # * dataset list is populated with information returned by dtool_lookup_api.search @@ -147,6 +137,44 @@ async def test_do_search_select_and_show_direct_call(populated_app, mock_dataset # Assert that _search_select_and_show was called with the correct query # populated_app.main_window._search_select_and_show.assert_called_once_with("test_search_query") + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Short sleep to yield control and wait + return False # Timeout reached + + # Trigger the 'refresh-view' action + populated_app.main_window.activate_action('refresh-view') + + # Wait until datasets are loaded + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Create a GLib.Variant with the test search query + mock_variant = GLib.Variant.new_string("test_search_query") + + # Call the method with the test search query + populated_app.main_window.do_search_select_and_show(None, mock_variant) + + + # Assertions to check the state of the application after the search + assert len(populated_app.main_window.dataset_list_box.get_children()) > 0 + first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] + + # Access the dataset from the first row + dataset = first_dataset_row.dataset + + # Assert that the dataset's URI matches the first item in your mock dataset list + assert dataset.uri == mock_dataset_list[0]['uri'] + + #print("dataset.uri: ", dataset.uri) + + await asyncio.sleep(1) + + + @pytest.mark.asyncio async def test_do_search_select_and_show_action_trigger(app): From cd698eb134bb56882c38438a06fa792bbfc4d98f Mon Sep 17 00:00:00 2001 From: Johannes Laurin Hoermann Date: Mon, 11 Dec 2023 13:33:05 +0100 Subject: [PATCH 03/11] TST: more dtool_lookup_api mocked --- test/conftest.py | 125 +++++++++++++++++++++++--- test/data/mock_manifest_response.json | 24 +++++ test/data/mock_readme_response.json | 1 + test/test_main_window_actions.py | 6 +- 4 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 test/data/mock_manifest_response.json create mode 100644 test/data/mock_readme_response.json diff --git a/test/conftest.py b/test/conftest.py index 0428fea2..e7d862c2 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -24,7 +24,84 @@ _HERE = os.path.dirname(os.path.abspath(__file__)) -# _ROOT = os.path.join(_HERE, "..") + +# dtool_lookup_api Config + +DTOOL_LOOKUP_SERVER_URL = "https://localhost:5000/lookup" +DTOOL_LOOKUP_SERVER_TOKEN = r"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwMTY4OTU5MywianRpIjoiYTdkN2Y5ZWItZGI3MS00YjExLWFhMzktZGQ2YzgzOTJmOWE4IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3R1c2VyIiwibmJmIjoxNzAxNjg5NTkzLCJleHAiOjE3MDE2OTMxOTN9.t9SQ00ecZRc-pspz-Du7321xfWIzgTTFKobkNed1CuQYHvtNrc3vdHYbqCWaYCqZpEVF8RlltldT4Lookx6vNgnW4olpiS2KTZ-X2asMhn7SShDtUJuU54CGeViWzYX_V_Pzckoe_cgjFkOutRvnwy_072Whnmc0TwYojwNqUScAIJRu0pzym84JngloXfdI7r25GcRVNtzsGUl7DDfrIz4aSOeVDAVEXhPjgEatKsvNdVZl1DIJsTZpuI7Jh7ZW1WsyjonqHR0J0kIVQn9imQyLyS9_CtmURBQ3kabx6cxhpx5LADrzLutSu24eA4FyECOdzjJ3SPGb9nIVTEDxQg" +DTOOL_LOOKUP_SERVER_TOKEN_GENERATOR_URL = "https://localhost:5000/token" +DTOOL_LOOKUP_SERVER_USERNAME = "testuser" +DTOOL_LOOKUP_SERVER_PASSWORD = "test_password" +DTOOL_LOOKUP_SERVER_VERIFY_SSL = True + +AFFIRMATIVE_EXPRESSIONS = ['true', '1', 'y', 'yes', 'on'] +NEGATIVE_EXPRESSIONS = ['false', '0', 'n', 'no', 'off'] + +logger = logging.getLogger(__name__) + +class MockDtoolLookupAPIConfig(): + """Mock dtool configuration without touching the local config file.""" + + def __init__(self, *args, **kwargs): + self._lookup_server_url = DTOOL_LOOKUP_SERVER_URL + self._lookup_server_token = DTOOL_LOOKUP_SERVER_URL + self._lookup_server_token_generator_url = DTOOL_LOOKUP_SERVER_TOKEN_GENERATOR_URL + self._lookup_server_username = DTOOL_LOOKUP_SERVER_USERNAME + self._lookup_server_password = DTOOL_LOOKUP_SERVER_PASSWORD + self._lookup_server_verify_ssl = DTOOL_LOOKUP_SERVER_VERIFY_SSL + + + @property + def lookup_url(self): + return self._lookup_server_url + + @lookup_url.setter + def lookup_url(self, value): + self._lookup_server_url = value + + # optional + @property + def token(self): + return self._lookup_server_token + + @token.setter + def token(self, token): + self._lookup_server_token = token + + @property + def auth_url(self): + return self._lookup_server_token_generator_url + + @auth_url.setter + def auth_url(self, value): + self._lookup_server_token_generator_url = value + + @property + def username(self): + return self._lookup_server_username + + @username.setter + def username(self, value): + self._lookup_server_username = value + + @property + def password(self): + return self._lookup_server_password + + @password.setter + def password(self, value): + self._lookup_server_password = value + + @property + def verify_ssl(self): + return self._lookup_server_verify_ssl + + @verify_ssl.setter + def verify_ssl(self, value): + self._lookup_server_verify_ssl = value + +# Config = DtoolLookupAPIConfig() + # ========================================================================== # fixtures from https://github.com/beeware/gbulb/blob/main/tests/conftest.py @@ -41,7 +118,10 @@ def setup_test_loop(loop): def check_loop_failures(loop): # pragma: no cover if loop.test_failure is not None: - pytest.fail("{message}: {exception}".format(**loop.test_failure)) + if hasattr(loop.test_failure, 'message') and hasattr(loop.test_failure, 'exception'): + pytest.fail("{message}: {exception}".format(**loop.test_failure)) + else: + pytest.fail("{message}".format(**loop.test_failure)) @pytest.fixture(scope="function") @@ -64,7 +144,9 @@ def glib_loop(glib_policy): loop = glib_policy.new_event_loop() setup_test_loop(loop) logger.debug("Create GLibEventLoopPolicy event loop") + yield loop + check_loop_failures(loop) loop.close() @@ -77,7 +159,9 @@ def gtk_loop(gtk_policy): # loop.set_application(Application(loop=loop)) setup_test_loop(loop) logger.debug("Create GtkEventLoopPolicy event loop") + yield loop + check_loop_failures(loop) loop.close() @@ -102,6 +186,24 @@ def mock_dataset_list(): yield dataset_list +@pytest.fixture(scope="function") +def mock_manifest(): + """Provide a mock dataset list""" + with open(os.path.join(_HERE, 'data', 'mock_manifest_response.json'), 'r') as f: + manifest = json.load(f) + + yield manifest + + +@pytest.fixture(scope="function") +def mock_readme(): + """Provide a mock dataset list""" + with open(os.path.join(_HERE, 'data', 'mock_readme_response.json'), 'r') as f: + readme = json.load(f) + + yield readme + + @pytest.fixture(scope="function") def mock_config_info(): """Provide a mock server config info""" @@ -111,27 +213,28 @@ def mock_config_info(): yield config_info -@pytest.fixture(scope="function") -def app_without_authentication(app, mock_token): - """Removes authentication from app.""" - with patch("dtool_lookup_api.core.LookupClient.authenticate", return_value=mock_token): - yield app - - @pytest.fixture(scope="function") def app_with_mock_dtool_lookup_api_calls( - app_without_authentication, mock_dataset_list, mock_config_info): + app, mock_dataset_list, mock_manifest, mock_readme, mock_config_info): """Replaces lookup api calls with mock methods that return fake lists of datasets.""" + import dtool_lookup_api.core.config + dtool_lookup_api.core.config.Config = MockDtoolLookupAPIConfig() + # TODO: figure out whether mocked methods work as they should with ( + patch("dtool_lookup_api.core.LookupClient.authenticate", return_value=mock_token), + patch("dtool_lookup_api.core.LookupClient.ConfigurationBasedLookupClient.connect", return_value=None), patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.all", return_value=mock_dataset_list), patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.search", return_value=mock_dataset_list), + patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.graph", return_value=[]), + patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.manifest", return_value=mock_manifest), + patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.readme", return_value=mock_readme), patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.config", return_value=mock_config_info), patch("dtool_lookup_api.core.LookupClient.ConfigurationBasedLookupClient.has_valid_token", return_value=True) ): - yield app_without_authentication + yield app @pytest.fixture(scope="function") diff --git a/test/data/mock_manifest_response.json b/test/data/mock_manifest_response.json new file mode 100644 index 00000000..aadf3905 --- /dev/null +++ b/test/data/mock_manifest_response.json @@ -0,0 +1,24 @@ +{ + "dtoolcore_version": "3.18.2", + "hash_function": "md5sum_hexdigest", + "items": { + "26f0d76fb3c3e34f0c7c8b7c3461b7495761835c": { + "hash": "06d80eb0c50b49a509b49f2424e8c805", + "relpath": "dog.txt", + "size_in_bytes": 3, + "utc_timestamp": 1689169434.016455 + }, + "d25102a700e072b528db79a0f22b3a5ffe5e8f5d": { + "hash": "68238cd792d215bdfdddc7bbb6d10db4", + "relpath": "parrot.txt", + "size_in_bytes": 6, + "utc_timestamp": 1689169434.019955 + }, + "e55aada093b34671ec2f9467fe83f0d3d8c31f30": { + "hash": "d077f244def8a70e5ea758bd8352fcd8", + "relpath": "cat.txt", + "size_in_bytes": 3, + "utc_timestamp": 1689169434.02241 + } + } +} \ No newline at end of file diff --git a/test/data/mock_readme_response.json b/test/data/mock_readme_response.json new file mode 100644 index 00000000..054916ae --- /dev/null +++ b/test/data/mock_readme_response.json @@ -0,0 +1 @@ +"creation_date: '2021-02-25 03:53:28.102172'\ndatetime: '2021-02-25 03:53:32.174840'\nderived_from:\n- {name: 2021-02-25-03-53-32-165117-probeonsubst--econversionminimizationandequilibration,\n uri: file://jwlogin04.juwels/p/project/chka18/hoermann4/dtool/DATASETS/2021-02-25-03-53-32-165117-probeonsubst--econversionminimizationandequilibration,\n uuid: c47b5095-9025-4fb7-9537-50272038472c}\ndescription: SDS on Au(111) substrate and probe trial\nexpiration_date: '2023-02-25 03:53:28.102172'\nfiles_in_info:\n probe_data_file:\n file_name: default.gro\n metadata_dtool_source_key: system->indenter\n metadata_fw_dest_key: metadata->system->indenter\n metadata_fw_source_key: metadata->system->indenter\n query: {uuid: 974b41b2-de1c-421c-897b-7e091facff3a}\n substrate_data_file:\n file_name: default.gro\n metadata_dtool_source_key: system->substrate\n metadata_fw_dest_key: metadata->system->substrate\n metadata_fw_source_key: metadata->system->substrate\n query: {uuid: b14873d7-0bba-4c2d-9915-ac9ee99f43c7}\nfunders:\n- {code: chfr13, organization: Gauss Centre for Supercomputing e.V. (www.gauss-centre.eu),\n program: John von Neumann Institute for Computing (NIC) project on the GCS Supercomputer\n JUWELS at Jülich Supercomputing Centre (JSC)}\n- {organization: University of Freiburg, program: Haushaltsstelle}\nmachine: juwels\nmode: production\nowners:\n- {email: johannes.hoermann@imtek.uni-freiburg.de, name: Johannes Laurin Hörmann,\n orcid: 0000-0001-5867-695X, username: fr_jh1130}\nproject: 2021-02-25-sds-on-au-111-probe-and-substrate-conversion\nproject_id: 2021-02-25-sds-on-au-111-probe-and-substrate-conversion\nstep: ProbeOnSubstrateMergeConversionMinimizationAndEquilibration:ProbeOnSubstrateMergeAndGROMACSEqulibration:GromacsMinimizationEquilibrationRelaxationNoSolvation:GromacsNVTEquilibration:push_dtool\nstep_specific:\n dtool_push:\n dtool_target: /p/project/chka18/hoermann4/dtool/DATASETS\n local_frozen_dataset: {name: 2021-02-25-03-53-32-165117-probeonsubst--econversionminimizationandequilibration,\n uri: file://jwlogin04.juwels/p/scratch/chka18/hoermann4/fireworks/launchpad/launcher_2021-02-26-19-25-05-539775/2021-02-25-03-53-32-165117-probeonsubst--econversionminimizationandequilibration,\n uuid: c47b5095-9025-4fb7-9537-50272038472c}\n local_proto_dataset: {name: 2021-02-25-03-53-32-165117-probeonsubst--econversionminimizationandequilibration,\n uri: file://jwlogin04.juwels/p/scratch/chka18/hoermann4/fireworks/launchpad/launcher_2021-02-26-19-25-05-539775/2021-02-25-03-53-32-165117-probeonsubst--econversionminimizationandequilibration,\n uuid: c47b5095-9025-4fb7-9537-50272038472c}\n remote_dataset: {name: 2021-02-25-03-53-32-165117-probeonsubst--econversionminimizationandequilibration,\n uri: file://jwlogin04.juwels/p/project/chka18/hoermann4/dtool/DATASETS/2021-02-25-03-53-32-165117-probeonsubst--econversionminimizationandequilibration,\n uuid: c47b5095-9025-4fb7-9537-50272038472c}\n equilibration:\n dpd: {coulomb_cutoff: 8.0, ewald_accuracy: 0.0001, freeze_substrate_layer: 14.0,\n neigh_check: true, neigh_delay: 2, neigh_every: 1, netcdf_frequency: 100, rigid_indenter_core_radius: 12.0,\n skin_distance: 3.0, steps: 10000, temperature: 298.0, thermo_average_frequency: 100,\n thermo_frequency: 100}\n npt: {barostat_damping: 10000, coulomb_cutoff: 8.0, ewald_accuracy: 0.0001, langevin_damping: 1000,\n neigh_check: true, neigh_delay: 2, neigh_every: 1, netcdf_frequency: 100, pressure: 1.0,\n skin_distance: 3.0, steps: 10000, temperature: 298.0, thermo_average_frequency: 100,\n thermo_frequency: 100}\n nvt: {coulomb_cutoff: 8.0, ewald_accuracy: 0.0001, initial_temperature: 1.0, langevin_damping: 1000,\n neigh_check: true, neigh_delay: 2, neigh_every: 1, netcdf_frequency: 100, skin_distance: 3.0,\n steps: 10000, temperature: 298.0, thermo_average_frequency: 100, thermo_frequency: 100}\n merge: {tol: 2.0, x_shift: 25.0, y_shift: 0.0, z_dist: 50.0}\n minimization: {coulomb_cutoff: 8.0, ewald_accuracy: 0.0001, ftol: '1e-06', maxeval: 10000,\n maxiter: 10000, neigh_check: true, neigh_delay: 2, neigh_every: 1, skin_distance: 3.0}\n psfgen:\n residues:\n - atoms:\n - {in: OW, out: OH2}\n - {in: HW1, out: H1}\n - {in: HW2, out: H2}\n in: SOL\n out: TIP3\n - atoms:\n - {in: NA, out: SOD}\n in: NA\n out: SOD\n - atoms:\n - {in: AU, out: AU}\n in: AUM\n out: AUM\n - atoms: []\n in: SDS\n out: SDS\n split_datafile: {region_tolerance: 5.0, shift_tolerance: 2.0}\nsystem:\n counterion:\n name: NA\n natoms: 469\n nmolecules: 469\n reference_atom: {name: NA}\n resname: NA\n indenter:\n bounding_sphere:\n center: [-0.021517430074867505, -0.17756268039516065, 0.4694604585787374]\n radius: 26.390609083217868\n name: AUM\n reference_atom: {name: AU}\n resname: AUM\n solvent:\n height: 180.0\n name: H2O\n natoms: 363213\n nmolecules: 121071\n reference_atom: {name: OW}\n resname: SOL\n substrate:\n approximate_measures: [150.0, 150.0, 150.0]\n bounding_box:\n - [-74.48, -74.667, -73.14]\n - [74.682, 74.876, 73.409]\n element: Au\n height: 148.2202478577067\n lattice_constant: 4.075\n length: 149.83592693342945\n lmp: {type: 11}\n measures: [149.83592693342945, 149.72506052762176, 148.2202478577067]\n name: AUM\n natoms: 200433\n nmolecules: 200433\n reference_atom: {name: AU}\n resname: AUM\n width: 149.72506052762176\n surfactant:\n aggregates: {shape: null}\n connector_atom: {index: 2}\n head_atom: {index: 1, name: S}\n name: SDS\n natoms: 19698\n nmolecules: 469\n resname: SDS\n surface_concentration: 0.015\n tail_atom: {index: 39, name: C12}\n" \ No newline at end of file diff --git a/test/test_main_window_actions.py b/test/test_main_window_actions.py index 00e3411c..367dd2a5 100644 --- a/test/test_main_window_actions.py +++ b/test/test_main_window_actions.py @@ -44,6 +44,7 @@ async def test_do_get_item_direct_call_fails_due_to_no_selected_item(app): @pytest.mark.asyncio +@pytest.mark.skip(reason="no way of currently testing this") async def test_populate_dataset_list(populated_app): """ Generates test data and selects the first URI. This test triggers the 'refresh-view' action @@ -158,7 +159,6 @@ async def wait_for_datasets_to_load(list_box, timeout=10): # Call the method with the test search query populated_app.main_window.do_search_select_and_show(None, mock_variant) - # Assertions to check the state of the application after the search assert len(populated_app.main_window.dataset_list_box.get_children()) > 0 first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] @@ -171,11 +171,11 @@ async def wait_for_datasets_to_load(list_box, timeout=10): #print("dataset.uri: ", dataset.uri) + # Need to await not-yet-finished tasks, otherwise will fail with + # Failed: Task was destroyed but it is pending! await asyncio.sleep(1) - - @pytest.mark.asyncio async def test_do_search_select_and_show_action_trigger(app): """Test if 'search-select-show' action triggers do_search_select_and_show method.""" From 112e06860d49f6ac81e3d63f47f4d17a1a185a26 Mon Sep 17 00:00:00 2001 From: ashdriod Date: Sat, 16 Dec 2023 22:06:41 +0100 Subject: [PATCH 04/11] Final Commit --- test/test_main_window_actions.py | 310 ++++++++++++++++++------------- 1 file changed, 184 insertions(+), 126 deletions(-) diff --git a/test/test_main_window_actions.py b/test/test_main_window_actions.py index 367dd2a5..db121f13 100644 --- a/test/test_main_window_actions.py +++ b/test/test_main_window_actions.py @@ -7,7 +7,7 @@ import time import pytest -from unittest.mock import patch, MagicMock, AsyncMock +from unittest.mock import patch, MagicMock, AsyncMock, Mock from gi.repository import Gtk, Gio, GLib @@ -31,96 +31,6 @@ async def test_refresh_method_triggered_by_action(app): # Assert that the refresh method was called once mock_refresh.assert_called_once() - -@pytest.mark.asyncio -async def test_do_get_item_direct_call_fails_due_to_no_selected_item(app): - """Test that the do_get_item method for copying a selected item fails when not item is selected.""" - - mock_variant = GLib.Variant.new_string("dummy_path") - - # Directly call the method with mock objects - with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'dataset'"): - app.main_window.do_get_item(None, mock_variant) - - -@pytest.mark.asyncio -@pytest.mark.skip(reason="no way of currently testing this") -async def test_populate_dataset_list(populated_app): - """ - Generates test data and selects the first URI. This test triggers the 'refresh-view' action - to simulate the data loading process, which is necessary for validating the functionality - of the application in a test environment. - """ - - - # Call the activate_action method to trigger the 'refresh-view' action - populated_app.main_window.activate_action('refresh-view') - - # Wait for a reasonable amount of time to ensure data loading and UI updates are complete - await asyncio.sleep(500) - - -@pytest.mark.asyncio -@pytest.mark.skip(reason="no way of currently testing this") -async def test_do_get_item_direct_call(app): - """Test the do_get_item method for copying a selected item directly.""" - - # Mock dependencies - # mock_dataset_list_box = MagicMock() - # app.main_window.dataset_list_box = mock_dataset_list_box - # mock_settings = MagicMock() - # app.main_window.settings = mock_settings - - # Mock _get_selected_items to return one item - # app.main_window._get_selected_items = MagicMock(return_value=[('item_name', 'item_uuid')]) - - # Create a mock action and variant - # mock_action = MagicMock(spec=Gio.SimpleAction) - mock_variant = GLib.Variant.new_string("dummy_path") - - # Mock async call in do_get_item - # mock_dataset = MagicMock() - # mock_dataset.get_item = AsyncMock() - # mock_dataset_list_box.get_selected_row.return_value = MagicMock(dataset=mock_dataset) - - # Directly call the method with mock objects - with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'dataset'"): - app.main_window.do_get_item(None, mock_variant) - - -@pytest.mark.asyncio -async def test_do_get_item_action_trigger(app): - """Test if 'get-item' action triggers do_get_item method.""" - - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box - mock_settings = MagicMock() - app.main_window.settings = mock_settings - - # Setup necessary mocks for the action trigger - mock_dataset = MagicMock() - mock_dataset.get_item = AsyncMock() - mock_dataset_list_box.get_selected_row.return_value = MagicMock(dataset=mock_dataset) - app.main_window._get_selected_items = MagicMock(return_value=[('item_name', 'item_uuid')]) - - # Create and add the action - dest_file_variant = GLib.Variant.new_string("dummy_path") - get_item_action = Gio.SimpleAction.new("get-item", dest_file_variant.get_type()) - app.main_window.add_action(get_item_action) - - # Patch do_get_item method after action is added - with patch.object(app.main_window, 'do_get_item', new_callable=MagicMock) as mock_do_get_item: - # Connect the action - get_item_action.connect("activate", app.main_window.do_get_item) - - # Trigger the action - get_item_action.activate(dest_file_variant) - - # Assert that do_get_item was called once - mock_do_get_item.assert_called_once_with(get_item_action, dest_file_variant) - -# TODO: let's start with this unit est and transform it into a proper test on GUI behavior @pytest.mark.asyncio async def test_do_search_select_and_show_direct_call(populated_app, mock_dataset_list): """ @@ -129,21 +39,12 @@ async def test_do_search_select_and_show_direct_call(populated_app, mock_dataset content is displayed. """ - # TODO: assert that - # * dataset list is populated with information returned by dtool_lookup_api.search - # * first dataset in list is selected in the GUI - # * content of dataset is displayed on the right hand side - # --> need to figure out how to inspect the GUI elements - - # Assert that _search_select_and_show was called with the correct query - # populated_app.main_window._search_select_and_show.assert_called_once_with("test_search_query") - async def wait_for_datasets_to_load(list_box, timeout=10): start_time = time.time() while time.time() - start_time < timeout: if len(list_box.get_children()) > 0: return True # Datasets are loaded - await asyncio.sleep(0.1) # Short sleep to yield control and wait + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run return False # Timeout reached # Trigger the 'refresh-view' action @@ -160,20 +61,17 @@ async def wait_for_datasets_to_load(list_box, timeout=10): populated_app.main_window.do_search_select_and_show(None, mock_variant) # Assertions to check the state of the application after the search - assert len(populated_app.main_window.dataset_list_box.get_children()) > 0 + assert len(populated_app.main_window.dataset_list_box.get_children()) > 0, "No datasets found in the list box" first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] - - # Access the dataset from the first row dataset = first_dataset_row.dataset - # Assert that the dataset's URI matches the first item in your mock dataset list - assert dataset.uri == mock_dataset_list[0]['uri'] + # Assert that the dataset's URI matches the expected URI + assert dataset.uri == mock_dataset_list[0]['uri'], f"Expected URI {mock_dataset_list[0]['uri']}, got {dataset.uri}" - #print("dataset.uri: ", dataset.uri) - - # Need to await not-yet-finished tasks, otherwise will fail with - # Failed: Task was destroyed but it is pending! - await asyncio.sleep(1) + # Await completion of all tasks related to the test + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + await asyncio.gather(*pending_tasks) @pytest.mark.asyncio @@ -204,27 +102,50 @@ async def test_do_search_select_and_show_action_trigger(app): # Assert that do_search_select_and_show was called once mock_do_search_select_and_show.assert_called_once_with(search_select_show_action, search_variant) - @pytest.mark.asyncio -async def test_do_show_dataset_details_by_uri_direct_call(app): - """Test the do_show_dataset_details_by_uri method for showing dataset details directly.""" +async def test_do_show_dataset_details_by_uri_direct_call(populated_app): + """ + Test the do_show_dataset_details_by_uri method for processing a URI directly. It verifies + if the correct dataset is selected and its content is displayed based on the URI. + """ - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run + return False # Timeout reached - # Mock _show_dataset_details_by_uri to simulate behavior - app.main_window._show_dataset_details_by_uri = MagicMock() + # Trigger the 'refresh-view' action + populated_app.main_window.activate_action('refresh-view') - # Create a mock action and variant - mock_action = MagicMock(spec=Gio.SimpleAction) - mock_variant = GLib.Variant.new_string("dummy_uri") + # Wait until datasets are loaded + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" - # Directly call the method with mock objects - app.main_window.do_show_dataset_details_by_uri(mock_action, mock_variant) + # Define a URI that corresponds to a dataset in the populated dataset list + test_uri = "smb://test-share/51e67a6e-3f38-43e9-bb79-01700dc09c61" + + # Create a GLib.Variant with the test URI + uri_variant = GLib.Variant.new_string(test_uri) + + # Call the do_show_dataset_details_by_uri method with the test URI + populated_app.main_window.do_show_dataset_details_by_uri(None, uri_variant) + + # Retrieve the dataset that should be selected based on the URI + index = populated_app.main_window.dataset_list_box.get_row_index_from_uri(test_uri) + selected_row = populated_app.main_window.dataset_list_box.get_row_at_index(index) + selected_dataset = selected_row.dataset if selected_row is not None else None + + # Assert that the dataset with the given URI is selected + assert selected_dataset is not None, "No dataset was selected" + assert selected_dataset.uri == test_uri, f"Expected URI {test_uri}, got {selected_dataset.uri}" - # Assert that _show_dataset_details_by_uri was called with the correct uri - app.main_window._show_dataset_details_by_uri.assert_called_once_with("dummy_uri") + # Await completion of all tasks related to the test + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + await asyncio.gather(*pending_tasks) @pytest.mark.asyncio @@ -256,6 +177,143 @@ async def test_do_show_dataset_details_by_uri_action_trigger(app): mock_do_show_dataset_details_by_uri.assert_called_once_with(show_dataset_by_uri_action, uri_variant) + + + + + + + +@pytest.mark.asyncio +async def test_do_get_item_direct_call_fails_due_to_no_selected_item(app): + """Test that the do_get_item method for copying a selected item fails when not item is selected.""" + + mock_variant = GLib.Variant.new_string("dummy_path") + + # Directly call the method with mock objects + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'dataset'"): + app.main_window.do_get_item(None, mock_variant) + + +@pytest.mark.asyncio +@pytest.mark.skip(reason="no way of currently testing this") +async def test_populate_dataset_list(populated_app): + """ + Generates test data and selects the first URI. This test triggers the 'refresh-view' action + to simulate the data loading process, which is necessary for validating the functionality + of the application in a test environment. + """ + + # Call the activate_action method to trigger the 'refresh-view' action + populated_app.main_window.activate_action('refresh-view') + + # Wait for a reasonable amount of time to ensure data loading and UI updates are complete + await asyncio.sleep(500) + + + +@pytest.mark.asyncio +@pytest.mark.skip(reason="no way of currently testing this") +async def test_do_get_item_direct_call(populated_app, tmp_path): + """ + Test the do_get_item method for copying a selected item directly. + It verifies if the selected item is correctly copied to the specified destination. + """ + + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run + return False # Timeout reached + + # Trigger the 'refresh-view' action to load datasets + populated_app.main_window.activate_action('refresh-view') + + # Wait until datasets are loaded + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Select the first dataset + first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] + dataset = first_dataset_row.dataset + + # Assuming the first dataset's UUID as the item UUID (Replace this with the actual item UUID logic) + item_uuid = dataset.uuid + populated_app.main_window._get_selected_items = lambda: [('item_name', item_uuid)] + + # Define the destination file path + dest_file = tmp_path / "destination_file" + + # Create a GLib.Variant with the destination file path + dest_file_variant = GLib.Variant.new_string(str(dest_file)) + + dest_file = tmp_path / "destination_file" + print(f"Destination file path: {dest_file}") + + # Call the do_get_item method without 'await' + try: + populated_app.main_window.do_get_item(None, dest_file_variant) + except Exception as e: + print(f"Error during 'do_get_item': {e}") + raise + + # Wait for all asynchronous tasks to complete + await asyncio.sleep(1) # Increase sleep time if necessary + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + completed_tasks = await asyncio.gather(*pending_tasks, return_exceptions=True) + for task in completed_tasks: + if isinstance(task, Exception): + print(f"Exception in async task: {task}") + + # Verify that the file was copied + file_exists = dest_file.exists() + print(f"File exists: {file_exists}") + assert file_exists, "The item was not copied to the specified destination" + + + + + +@pytest.mark.asyncio +async def test_do_get_item_action_trigger(app): + """Test if 'get-item' action triggers do_get_item method.""" + + # Mock dependencies + mock_dataset_list_box = MagicMock() + app.main_window.dataset_list_box = mock_dataset_list_box + mock_settings = MagicMock() + app.main_window.settings = mock_settings + + # Setup necessary mocks for the action trigger + mock_dataset = MagicMock() + mock_dataset.get_item = AsyncMock() + mock_dataset_list_box.get_selected_row.return_value = MagicMock(dataset=mock_dataset) + app.main_window._get_selected_items = MagicMock(return_value=[('item_name', 'item_uuid')]) + + # Create and add the action + dest_file_variant = GLib.Variant.new_string("dummy_path") + get_item_action = Gio.SimpleAction.new("get-item", dest_file_variant.get_type()) + app.main_window.add_action(get_item_action) + + # Patch do_get_item method after action is added + with patch.object(app.main_window, 'do_get_item', new_callable=MagicMock) as mock_do_get_item: + # Connect the action + get_item_action.connect("activate", app.main_window.do_get_item) + + # Trigger the action + get_item_action.activate(dest_file_variant) + + # Assert that do_get_item was called once + mock_do_get_item.assert_called_once_with(get_item_action, dest_file_variant) + + + + + + @pytest.mark.asyncio async def test_do_show_dataset_details_by_row_index_direct_call(app): """Test the do_show_dataset_details_by_row_index method for showing dataset details by index directly.""" From 7dd11d34790160243ad15871f72d8d860bbca95f Mon Sep 17 00:00:00 2001 From: ashdriod Date: Sun, 17 Dec 2023 02:42:01 +0100 Subject: [PATCH 05/11] TST:WIP --- test/test_main_window_actions.py | 516 +++++++++++++++++++------------ 1 file changed, 310 insertions(+), 206 deletions(-) diff --git a/test/test_main_window_actions.py b/test/test_main_window_actions.py index db121f13..91cb4618 100644 --- a/test/test_main_window_actions.py +++ b/test/test_main_window_actions.py @@ -11,96 +11,162 @@ from gi.repository import Gtk, Gio, GLib + @pytest.mark.asyncio -async def test_do_refresh_view_direct_call(app): - """Test the direct call of the do_refresh_view method.""" +async def test_do_search_direct_call(populated_app): + """ + Test the do_search method for directly processing a search in a populated application. + This test checks if the search action correctly triggers the search process and + verifies that the search results are appropriately loaded and displayed in the UI. + """ - # Directly call the do_refresh_view method - app.main_window.do_refresh_view(None, None) + # Helper function to wait for datasets to load in the list box. + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run + return False # Timeout reached if datasets are not loaded within the specified time + + # Trigger the 'refresh-view' action to load datasets + populated_app.main_window.activate_action('refresh-view') + + # Wait until datasets are loaded in the dataset list box + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Create a GLib.Variant with a test search query + search_query = "test_search_query" + search_text_variant = GLib.Variant.new_string(search_query) + + # Create and add the search action to the main window of the application + search_action = Gio.SimpleAction.new("search", search_text_variant.get_type()) + populated_app.main_window.add_action(search_action) + + # Connect the search action to the do_search method of the main window + search_action.connect("activate", populated_app.main_window.do_search) + + # Trigger the search action with the test search query + search_action.activate(search_text_variant) + + # Optionally, wait for the search results to be processed and displayed + # This step depends on the application's implementation and response time + await asyncio.sleep(1) # Adjust this sleep duration as needed + + # Perform assertions to verify that the search results are correctly displayed + assert len(populated_app.main_window.base_uri_list_box.get_children()) > 0, "No search results found" + # Additional assertions can be added here based on the expected outcomes of the search + # Await completion of any remaining asynchronous tasks related to the search + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + await asyncio.gather(*pending_tasks) @pytest.mark.asyncio -async def test_refresh_method_triggered_by_action(app): - """Test if the 'refresh-view' action triggers the refresh method.""" +async def test_do_search_action_trigger(app): + """Test if 'search' action triggers do_search method.""" - # Patch the main window's refresh method - with patch.object(app.main_window, 'refresh', new_callable=MagicMock) as mock_refresh: - # Trigger the 'refresh-view' action - app.main_window.activate_action('refresh-view') + # Mock dependencies + mock_base_uri_list_box = MagicMock() + app.main_window.base_uri_list_box = mock_base_uri_list_box - # Assert that the refresh method was called once - mock_refresh.assert_called_once() + # Setup necessary mocks for the action trigger + app.main_window._search = MagicMock() + + # Create and add the action + search_text_variant = GLib.Variant.new_string("test_search_query") + search_action = Gio.SimpleAction.new("search", search_text_variant.get_type()) + app.main_window.add_action(search_action) + + # Patch do_search method after action is added + with patch.object(app.main_window, 'do_search', new_callable=MagicMock) as mock_do_search: + # Connect the action + search_action.connect("activate", app.main_window.do_search) + + # Trigger the action + search_action.activate(search_text_variant) + + # Assert that do_search was called once + mock_do_search.assert_called_once_with(search_action, search_text_variant) @pytest.mark.asyncio -async def test_do_search_select_and_show_direct_call(populated_app, mock_dataset_list): +async def test_do_select_dataset_row_by_row_index_direct_call(populated_app): """ - Test the do_search_select_and_show method for processing a search directly. It verifies - if the dataset list is correctly populated, the first dataset is selected, and its - content is displayed. + Test the do_select_dataset_row_by_row_index method for directly selecting a dataset row by index. + It verifies if the dataset list is correctly populated and the specified dataset row is selected. """ async def wait_for_datasets_to_load(list_box, timeout=10): start_time = time.time() while time.time() - start_time < timeout: if len(list_box.get_children()) > 0: - return True # Datasets are loaded - await asyncio.sleep(0.1) # Yield control to allow other async tasks to run - return False # Timeout reached + return True + await asyncio.sleep(0.1) + return False - # Trigger the 'refresh-view' action + # Trigger the 'refresh-view' action and wait for datasets to load populated_app.main_window.activate_action('refresh-view') - - # Wait until datasets are loaded datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) assert datasets_loaded, "Datasets were not loaded in time" - # Create a GLib.Variant with the test search query - mock_variant = GLib.Variant.new_string("test_search_query") + # Create a GLib.Variant with a valid test row index (e.g., 0 for the first row) + row_index = 6 + row_index_variant = GLib.Variant.new_uint32(row_index) - # Call the method with the test search query - populated_app.main_window.do_search_select_and_show(None, mock_variant) + # Create and add the select dataset action to the main window of the application + select_dataset_action = Gio.SimpleAction.new("select-dataset", row_index_variant.get_type()) + populated_app.main_window.add_action(select_dataset_action) - # Assertions to check the state of the application after the search - assert len(populated_app.main_window.dataset_list_box.get_children()) > 0, "No datasets found in the list box" - first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] - dataset = first_dataset_row.dataset + # Connect the select dataset action to the do_select_dataset_row_by_row_index method + select_dataset_action.connect("activate", populated_app.main_window.do_select_dataset_row_by_row_index) - # Assert that the dataset's URI matches the expected URI - assert dataset.uri == mock_dataset_list[0]['uri'], f"Expected URI {mock_dataset_list[0]['uri']}, got {dataset.uri}" + # Trigger the select dataset action with the test row index + select_dataset_action.activate(row_index_variant) - # Await completion of all tasks related to the test + # Optionally, wait for the UI to update if necessary + await asyncio.sleep(0.1) # Adjust this sleep duration as needed + + # Perform assertions to verify that the correct dataset row is selected + selected_row = populated_app.main_window.dataset_list_box.get_selected_row() + + # Assert that the selected row is not None and has the expected index + assert selected_row is not None, "No dataset row was selected" + assert selected_row.get_index() == row_index, f"Expected row index {row_index}, got {selected_row.get_index()}" + + # Await completion of any remaining asynchronous tasks related to the selection pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] if pending_tasks: await asyncio.gather(*pending_tasks) - @pytest.mark.asyncio -async def test_do_search_select_and_show_action_trigger(app): - """Test if 'search-select-show' action triggers do_search_select_and_show method.""" +async def test_do_select_dataset_row_by_row_index_action_trigger(app): + """Test if 'select-dataset' action triggers do_select_dataset_row_by_row_index method.""" # Mock dependencies - mock_base_uri_list_box = MagicMock() - app.main_window.base_uri_list_box = mock_base_uri_list_box + mock_dataset_list_box = MagicMock() + app.main_window.dataset_list_box = mock_dataset_list_box # Setup necessary mocks for the action trigger - app.main_window._search_select_and_show = MagicMock() + app.main_window._select_dataset_row_by_row_index = MagicMock() # Create and add the action - search_variant = GLib.Variant.new_string("test_search_query") - search_select_show_action = Gio.SimpleAction.new("search-select-show", search_variant.get_type()) - app.main_window.add_action(search_select_show_action) + row_index_variant = GLib.Variant.new_uint32(1) # Example index + select_dataset_action = Gio.SimpleAction.new("select-dataset", row_index_variant.get_type()) + app.main_window.add_action(select_dataset_action) - # Patch do_search_select_and_show method after action is added - with patch.object(app.main_window, 'do_search_select_and_show', - new_callable=MagicMock) as mock_do_search_select_and_show: + # Patch do_select_dataset_row_by_row_index method after action is added + with patch.object(app.main_window, 'do_select_dataset_row_by_row_index', + new_callable=MagicMock) as mock_do_select_dataset_row_by_row_index: # Connect the action - search_select_show_action.connect("activate", app.main_window.do_search_select_and_show) + select_dataset_action.connect("activate", app.main_window.do_select_dataset_row_by_row_index) # Trigger the action - search_select_show_action.activate(search_variant) + select_dataset_action.activate(row_index_variant) + + # Assert that do_select_dataset_row_by_row_index was called once + mock_do_select_dataset_row_by_row_index.assert_called_once_with(select_dataset_action, row_index_variant) - # Assert that do_search_select_and_show was called once - mock_do_search_select_and_show.assert_called_once_with(search_select_show_action, search_variant) @pytest.mark.asyncio async def test_do_show_dataset_details_by_uri_direct_call(populated_app): @@ -147,7 +213,6 @@ async def wait_for_datasets_to_load(list_box, timeout=10): if pending_tasks: await asyncio.gather(*pending_tasks) - @pytest.mark.asyncio async def test_do_show_dataset_details_by_uri_action_trigger(app): """Test if 'show-dataset-by-uri' action triggers do_show_dataset_details_by_uri method.""" @@ -177,47 +242,95 @@ async def test_do_show_dataset_details_by_uri_action_trigger(app): mock_do_show_dataset_details_by_uri.assert_called_once_with(show_dataset_by_uri_action, uri_variant) +@pytest.mark.asyncio +async def test_do_show_dataset_details_by_row_index_direct_call(populated_app, mock_dataset_list): + """ + Test the do_show_dataset_details_by_row_index method for directly showing dataset details by row index. + It verifies if the dataset list is correctly populated and the specified dataset details are displayed. + """ + + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run + return False # Timeout reached if datasets are not loaded within the specified time + + # Trigger the 'refresh-view' action and wait for datasets to load + populated_app.main_window.activate_action('refresh-view') + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Create a GLib.Variant with a test row index (e.g., 0 for the first row) + row_index = 1 + row_index_variant = GLib.Variant.new_uint32(row_index) + # Create and add the show dataset action to the main window of the application + show_dataset_action = Gio.SimpleAction.new("show-dataset", row_index_variant.get_type()) + populated_app.main_window.add_action(show_dataset_action) + # Connect the show dataset action to the do_show_dataset_details_by_row_index method + show_dataset_action.connect("activate", populated_app.main_window.do_show_dataset_details_by_row_index) + # Trigger the show dataset action with the test row index + show_dataset_action.activate(row_index_variant) + # Optionally, wait for the UI to update and dataset details to be displayed + await asyncio.sleep(0.1) # Adjust this sleep duration as needed + # Perform assertions to verify that the dataset details are correctly displayed + selected_dataset = populated_app.main_window.dataset_list_box.get_row_at_index(row_index).dataset + expected_uri = mock_dataset_list[1]['uri'] + expected_uuid = mock_dataset_list[1]['uuid'] -@pytest.mark.asyncio -async def test_do_get_item_direct_call_fails_due_to_no_selected_item(app): - """Test that the do_get_item method for copying a selected item fails when not item is selected.""" + assert selected_dataset.uri == expected_uri, f"Expected URI {expected_uri}, got {selected_dataset.uri}" + assert selected_dataset.uuid == expected_uuid, f"Expected UUID {expected_uuid}, got {selected_dataset.uuid}" - mock_variant = GLib.Variant.new_string("dummy_path") + # Additional assertions can be made here based on the expected behavior and UI element updates - # Directly call the method with mock objects - with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'dataset'"): - app.main_window.do_get_item(None, mock_variant) + # Await completion of any remaining asynchronous tasks related to showing the dataset details + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + await asyncio.gather(*pending_tasks) @pytest.mark.asyncio -@pytest.mark.skip(reason="no way of currently testing this") -async def test_populate_dataset_list(populated_app): - """ - Generates test data and selects the first URI. This test triggers the 'refresh-view' action - to simulate the data loading process, which is necessary for validating the functionality - of the application in a test environment. - """ +async def test_do_show_dataset_details_by_row_index_action_trigger(app): + """Test if 'show-dataset' action triggers do_show_dataset_details_by_row_index method.""" - # Call the activate_action method to trigger the 'refresh-view' action - populated_app.main_window.activate_action('refresh-view') + # Mock dependencies + mock_dataset_list_box = MagicMock() + app.main_window.dataset_list_box = mock_dataset_list_box - # Wait for a reasonable amount of time to ensure data loading and UI updates are complete - await asyncio.sleep(500) + # Setup necessary mocks for the action trigger + app.main_window._show_dataset_details_by_row_index = MagicMock() + # Create and add the action + row_index_variant = GLib.Variant.new_uint32(1) # Example index + show_dataset_action = Gio.SimpleAction.new("show-dataset", row_index_variant.get_type()) + app.main_window.add_action(show_dataset_action) + + # Patch do_show_dataset_details_by_row_index method after action is added + with patch.object(app.main_window, 'do_show_dataset_details_by_row_index', + new_callable=MagicMock) as mock_do_show_dataset_details_by_row_index: + # Connect the action + show_dataset_action.connect("activate", app.main_window.do_show_dataset_details_by_row_index) + + # Trigger the action + show_dataset_action.activate(row_index_variant) + + # Assert that do_show_dataset_details_by_row_index was called once + mock_do_show_dataset_details_by_row_index.assert_called_once_with(show_dataset_action, row_index_variant) @pytest.mark.asyncio -@pytest.mark.skip(reason="no way of currently testing this") -async def test_do_get_item_direct_call(populated_app, tmp_path): +async def test_do_search_select_and_show_direct_call(populated_app, mock_dataset_list): """ - Test the do_get_item method for copying a selected item directly. - It verifies if the selected item is correctly copied to the specified destination. + Test the do_search_select_and_show method for processing a search directly. It verifies + if the dataset list is correctly populated, the first dataset is selected, and its + content is displayed. """ async def wait_for_datasets_to_load(list_box, timeout=10): @@ -228,50 +341,62 @@ async def wait_for_datasets_to_load(list_box, timeout=10): await asyncio.sleep(0.1) # Yield control to allow other async tasks to run return False # Timeout reached - # Trigger the 'refresh-view' action to load datasets + # Trigger the 'refresh-view' action populated_app.main_window.activate_action('refresh-view') # Wait until datasets are loaded datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) assert datasets_loaded, "Datasets were not loaded in time" - # Select the first dataset + # Create a GLib.Variant with the test search query + mock_variant = GLib.Variant.new_string("test_search_query") + + # Call the method with the test search query + populated_app.main_window.do_search_select_and_show(None, mock_variant) + + # Assertions to check the state of the application after the search + assert len(populated_app.main_window.dataset_list_box.get_children()) > 0, "No datasets found in the list box" first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] dataset = first_dataset_row.dataset - # Assuming the first dataset's UUID as the item UUID (Replace this with the actual item UUID logic) - item_uuid = dataset.uuid - populated_app.main_window._get_selected_items = lambda: [('item_name', item_uuid)] + # Assert that the dataset's URI matches the expected URI + assert dataset.uri == mock_dataset_list[0]['uri'], f"Expected URI {mock_dataset_list[0]['uri']}, got {dataset.uri}" - # Define the destination file path - dest_file = tmp_path / "destination_file" + # Await completion of all tasks related to the test + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + await asyncio.gather(*pending_tasks) - # Create a GLib.Variant with the destination file path - dest_file_variant = GLib.Variant.new_string(str(dest_file)) - dest_file = tmp_path / "destination_file" - print(f"Destination file path: {dest_file}") +@pytest.mark.asyncio +async def test_do_search_select_and_show_action_trigger(app): + """Test if 'search-select-show' action triggers do_search_select_and_show method.""" - # Call the do_get_item method without 'await' - try: - populated_app.main_window.do_get_item(None, dest_file_variant) - except Exception as e: - print(f"Error during 'do_get_item': {e}") - raise + # Mock dependencies + mock_base_uri_list_box = MagicMock() + app.main_window.base_uri_list_box = mock_base_uri_list_box + + # Setup necessary mocks for the action trigger + app.main_window._search_select_and_show = MagicMock() + + # Create and add the action + search_variant = GLib.Variant.new_string("test_search_query") + search_select_show_action = Gio.SimpleAction.new("search-select-show", search_variant.get_type()) + app.main_window.add_action(search_select_show_action) + + # Patch do_search_select_and_show method after action is added + with patch.object(app.main_window, 'do_search_select_and_show', + new_callable=MagicMock) as mock_do_search_select_and_show: + # Connect the action + search_select_show_action.connect("activate", app.main_window.do_search_select_and_show) + + # Trigger the action + search_select_show_action.activate(search_variant) + + # Assert that do_search_select_and_show was called once + mock_do_search_select_and_show.assert_called_once_with(search_select_show_action, search_variant) - # Wait for all asynchronous tasks to complete - await asyncio.sleep(1) # Increase sleep time if necessary - pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] - if pending_tasks: - completed_tasks = await asyncio.gather(*pending_tasks, return_exceptions=True) - for task in completed_tasks: - if isinstance(task, Exception): - print(f"Exception in async task: {task}") - # Verify that the file was copied - file_exists = dest_file.exists() - print(f"File exists: {file_exists}") - assert file_exists, "The item was not copied to the specified destination" @@ -310,157 +435,136 @@ async def test_do_get_item_action_trigger(app): mock_do_get_item.assert_called_once_with(get_item_action, dest_file_variant) +@pytest.mark.asyncio +async def test_do_refresh_view_direct_call(app): + """Test the direct call of the do_refresh_view method.""" + # Directly call the do_refresh_view method + app.main_window.do_refresh_view(None, None) +@pytest.mark.asyncio +async def test_refresh_method_triggered_by_action(app): + """Test if the 'refresh-view' action triggers the refresh method.""" + # Patch the main window's refresh method + with patch.object(app.main_window, 'refresh', new_callable=MagicMock) as mock_refresh: + # Trigger the 'refresh-view' action + app.main_window.activate_action('refresh-view') + + # Assert that the refresh method was called once + mock_refresh.assert_called_once() -@pytest.mark.asyncio -async def test_do_show_dataset_details_by_row_index_direct_call(app): - """Test the do_show_dataset_details_by_row_index method for showing dataset details by index directly.""" - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box - # Mock _show_dataset_details_by_row_index to simulate behavior - app.main_window._show_dataset_details_by_row_index = MagicMock() - # Create a mock action and variant - mock_action = MagicMock(spec=Gio.SimpleAction) - mock_variant = GLib.Variant.new_uint32(1) # Example index - # Directly call the method with mock objects - app.main_window.do_show_dataset_details_by_row_index(mock_action, mock_variant) - # Assert that _show_dataset_details_by_row_index was called with the correct index - app.main_window._show_dataset_details_by_row_index.assert_called_once_with(1) -@pytest.mark.asyncio -async def test_do_show_dataset_details_by_row_index_action_trigger(app): - """Test if 'show-dataset' action triggers do_show_dataset_details_by_row_index method.""" - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box - # Setup necessary mocks for the action trigger - app.main_window._show_dataset_details_by_row_index = MagicMock() - # Create and add the action - row_index_variant = GLib.Variant.new_uint32(1) # Example index - show_dataset_action = Gio.SimpleAction.new("show-dataset", row_index_variant.get_type()) - app.main_window.add_action(show_dataset_action) - # Patch do_show_dataset_details_by_row_index method after action is added - with patch.object(app.main_window, 'do_show_dataset_details_by_row_index', - new_callable=MagicMock) as mock_do_show_dataset_details_by_row_index: - # Connect the action - show_dataset_action.connect("activate", app.main_window.do_show_dataset_details_by_row_index) - # Trigger the action - show_dataset_action.activate(row_index_variant) - # Assert that do_show_dataset_details_by_row_index was called once - mock_do_show_dataset_details_by_row_index.assert_called_once_with(show_dataset_action, row_index_variant) -@pytest.mark.asyncio -async def test_do_select_dataset_row_by_row_index_direct_call(app): - """Test the do_select_dataset_row_by_row_index method for selecting a dataset row directly.""" - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box - # Mock _select_dataset_row_by_row_index to simulate behavior - app.main_window._select_dataset_row_by_row_index = MagicMock() - # Create a mock action and variant - mock_action = MagicMock(spec=Gio.SimpleAction) - mock_variant = GLib.Variant.new_uint32(1) # Example index + +@pytest.mark.asyncio +async def test_do_get_item_direct_call_fails_due_to_no_selected_item(app): + """Test that the do_get_item method for copying a selected item fails when not item is selected.""" + + mock_variant = GLib.Variant.new_string("dummy_path") # Directly call the method with mock objects - app.main_window.do_select_dataset_row_by_row_index(mock_action, mock_variant) + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'dataset'"): + app.main_window.do_get_item(None, mock_variant) + - # Assert that _select_dataset_row_by_row_index was called with the correct index - app.main_window._select_dataset_row_by_row_index.assert_called_once_with(1) @pytest.mark.asyncio -async def test_do_select_dataset_row_by_row_index_action_trigger(app): - """Test if 'select-dataset' action triggers do_select_dataset_row_by_row_index method.""" +@pytest.mark.skip(reason="no way of currently testing this") +async def test_do_get_item_direct_call(populated_app, tmp_path): + """ + Test the do_get_item method for copying a selected item directly. + It verifies if the selected item is correctly copied to the specified destination. + """ + + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run + return False # Timeout reached + + # Trigger the 'refresh-view' action to load datasets + populated_app.main_window.activate_action('refresh-view') + + # Wait until datasets are loaded + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Select the first dataset + first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] + dataset = first_dataset_row.dataset + + # Assuming the first dataset's UUID as the item UUID (Replace this with the actual item UUID logic) + item_uuid = dataset.uuid + populated_app.main_window._get_selected_items = lambda: [('item_name', item_uuid)] + + # Define the destination file path + dest_file = tmp_path / "destination_file" + + # Create a GLib.Variant with the destination file path + dest_file_variant = GLib.Variant.new_string(str(dest_file)) + + dest_file = tmp_path / "destination_file" + print(f"Destination file path: {dest_file}") + + # Call the do_get_item method without 'await' + try: + populated_app.main_window.do_get_item(None, dest_file_variant) + except Exception as e: + print(f"Error during 'do_get_item': {e}") + raise + + # Wait for all asynchronous tasks to complete + await asyncio.sleep(1) # Increase sleep time if necessary + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + completed_tasks = await asyncio.gather(*pending_tasks, return_exceptions=True) + for task in completed_tasks: + if isinstance(task, Exception): + print(f"Exception in async task: {task}") + + # Verify that the file was copied + file_exists = dest_file.exists() + print(f"File exists: {file_exists}") + assert file_exists, "The item was not copied to the specified destination" + - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box - # Setup necessary mocks for the action trigger - app.main_window._select_dataset_row_by_row_index = MagicMock() - # Create and add the action - row_index_variant = GLib.Variant.new_uint32(1) # Example index - select_dataset_action = Gio.SimpleAction.new("select-dataset", row_index_variant.get_type()) - app.main_window.add_action(select_dataset_action) - # Patch do_select_dataset_row_by_row_index method after action is added - with patch.object(app.main_window, 'do_select_dataset_row_by_row_index', - new_callable=MagicMock) as mock_do_select_dataset_row_by_row_index: - # Connect the action - select_dataset_action.connect("activate", app.main_window.do_select_dataset_row_by_row_index) - # Trigger the action - select_dataset_action.activate(row_index_variant) - # Assert that do_select_dataset_row_by_row_index was called once - mock_do_select_dataset_row_by_row_index.assert_called_once_with(select_dataset_action, row_index_variant) -@pytest.mark.asyncio -async def test_do_search_direct_call(app): - """Test the do_search method for initiating a search directly.""" - # Mock dependencies - mock_base_uri_list_box = MagicMock() - app.main_window.base_uri_list_box = mock_base_uri_list_box - # Mock _search to simulate behavior - app.main_window._search = MagicMock() - # Create a mock action and variant - mock_action = MagicMock(spec=Gio.SimpleAction) - mock_variant = GLib.Variant.new_string("test_search_query") - # Directly call the method with mock objects - app.main_window.do_search(mock_action, mock_variant) - # Assert that _search was called with the correct search text - app.main_window._search.assert_called_once_with("test_search_query") -@pytest.mark.asyncio -async def test_do_search_action_trigger(app): - """Test if 'search' action triggers do_search method.""" - # Mock dependencies - mock_base_uri_list_box = MagicMock() - app.main_window.base_uri_list_box = mock_base_uri_list_box - # Setup necessary mocks for the action trigger - app.main_window._search = MagicMock() - # Create and add the action - search_text_variant = GLib.Variant.new_string("test_search_query") - search_action = Gio.SimpleAction.new("search", search_text_variant.get_type()) - app.main_window.add_action(search_action) - # Patch do_search method after action is added - with patch.object(app.main_window, 'do_search', new_callable=MagicMock) as mock_do_search: - # Connect the action - search_action.connect("activate", app.main_window.do_search) - # Trigger the action - search_action.activate(search_text_variant) - # Assert that do_search was called once - mock_do_search.assert_called_once_with(search_action, search_text_variant) From d26a1d0991ec76b76a95a7047da590cbe57cfcf8 Mon Sep 17 00:00:00 2001 From: ashdriod Date: Sun, 17 Dec 2023 03:12:32 +0100 Subject: [PATCH 06/11] TST:WIP --- test/test_main_window_actions.py | 143 +++++++++++-------------------- 1 file changed, 50 insertions(+), 93 deletions(-) diff --git a/test/test_main_window_actions.py b/test/test_main_window_actions.py index 91cb4618..affe5819 100644 --- a/test/test_main_window_actions.py +++ b/test/test_main_window_actions.py @@ -11,7 +11,6 @@ from gi.repository import Gtk, Gio, GLib - @pytest.mark.asyncio async def test_do_search_direct_call(populated_app): """ @@ -63,6 +62,7 @@ async def wait_for_datasets_to_load(list_box, timeout=10): if pending_tasks: await asyncio.gather(*pending_tasks) + @pytest.mark.asyncio async def test_do_search_action_trigger(app): """Test if 'search' action triggers do_search method.""" @@ -90,6 +90,7 @@ async def test_do_search_action_trigger(app): # Assert that do_search was called once mock_do_search.assert_called_once_with(search_action, search_text_variant) + @pytest.mark.asyncio async def test_do_select_dataset_row_by_row_index_direct_call(populated_app): """ @@ -139,6 +140,7 @@ async def wait_for_datasets_to_load(list_box, timeout=10): if pending_tasks: await asyncio.gather(*pending_tasks) + @pytest.mark.asyncio async def test_do_select_dataset_row_by_row_index_action_trigger(app): """Test if 'select-dataset' action triggers do_select_dataset_row_by_row_index method.""" @@ -213,6 +215,7 @@ async def wait_for_datasets_to_load(list_box, timeout=10): if pending_tasks: await asyncio.gather(*pending_tasks) + @pytest.mark.asyncio async def test_do_show_dataset_details_by_uri_action_trigger(app): """Test if 'show-dataset-by-uri' action triggers do_show_dataset_details_by_uri method.""" @@ -397,95 +400,6 @@ async def test_do_search_select_and_show_action_trigger(app): mock_do_search_select_and_show.assert_called_once_with(search_select_show_action, search_variant) - - - - - -@pytest.mark.asyncio -async def test_do_get_item_action_trigger(app): - """Test if 'get-item' action triggers do_get_item method.""" - - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box - mock_settings = MagicMock() - app.main_window.settings = mock_settings - - # Setup necessary mocks for the action trigger - mock_dataset = MagicMock() - mock_dataset.get_item = AsyncMock() - mock_dataset_list_box.get_selected_row.return_value = MagicMock(dataset=mock_dataset) - app.main_window._get_selected_items = MagicMock(return_value=[('item_name', 'item_uuid')]) - - # Create and add the action - dest_file_variant = GLib.Variant.new_string("dummy_path") - get_item_action = Gio.SimpleAction.new("get-item", dest_file_variant.get_type()) - app.main_window.add_action(get_item_action) - - # Patch do_get_item method after action is added - with patch.object(app.main_window, 'do_get_item', new_callable=MagicMock) as mock_do_get_item: - # Connect the action - get_item_action.connect("activate", app.main_window.do_get_item) - - # Trigger the action - get_item_action.activate(dest_file_variant) - - # Assert that do_get_item was called once - mock_do_get_item.assert_called_once_with(get_item_action, dest_file_variant) - - -@pytest.mark.asyncio -async def test_do_refresh_view_direct_call(app): - """Test the direct call of the do_refresh_view method.""" - - # Directly call the do_refresh_view method - app.main_window.do_refresh_view(None, None) - -@pytest.mark.asyncio -async def test_refresh_method_triggered_by_action(app): - """Test if the 'refresh-view' action triggers the refresh method.""" - - # Patch the main window's refresh method - with patch.object(app.main_window, 'refresh', new_callable=MagicMock) as mock_refresh: - # Trigger the 'refresh-view' action - app.main_window.activate_action('refresh-view') - - # Assert that the refresh method was called once - mock_refresh.assert_called_once() - - - - - - - - - - - - - - - - - - - - -@pytest.mark.asyncio -async def test_do_get_item_direct_call_fails_due_to_no_selected_item(app): - """Test that the do_get_item method for copying a selected item fails when not item is selected.""" - - mock_variant = GLib.Variant.new_string("dummy_path") - - # Directly call the method with mock objects - with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'dataset'"): - app.main_window.do_get_item(None, mock_variant) - - - - @pytest.mark.asyncio @pytest.mark.skip(reason="no way of currently testing this") async def test_do_get_item_direct_call(populated_app, tmp_path): @@ -548,23 +462,66 @@ async def wait_for_datasets_to_load(list_box, timeout=10): assert file_exists, "The item was not copied to the specified destination" +@pytest.mark.asyncio +async def test_do_get_item_action_trigger(app): + """Test if 'get-item' action triggers do_get_item method.""" + # Mock dependencies + mock_dataset_list_box = MagicMock() + app.main_window.dataset_list_box = mock_dataset_list_box + mock_settings = MagicMock() + app.main_window.settings = mock_settings + # Setup necessary mocks for the action trigger + mock_dataset = MagicMock() + mock_dataset.get_item = AsyncMock() + mock_dataset_list_box.get_selected_row.return_value = MagicMock(dataset=mock_dataset) + app.main_window._get_selected_items = MagicMock(return_value=[('item_name', 'item_uuid')]) + # Create and add the action + dest_file_variant = GLib.Variant.new_string("dummy_path") + get_item_action = Gio.SimpleAction.new("get-item", dest_file_variant.get_type()) + app.main_window.add_action(get_item_action) + # Patch do_get_item method after action is added + with patch.object(app.main_window, 'do_get_item', new_callable=MagicMock) as mock_do_get_item: + # Connect the action + get_item_action.connect("activate", app.main_window.do_get_item) + # Trigger the action + get_item_action.activate(dest_file_variant) + # Assert that do_get_item was called once + mock_do_get_item.assert_called_once_with(get_item_action, dest_file_variant) +@pytest.mark.asyncio +async def test_do_refresh_view_direct_call(app): + """Test the direct call of the do_refresh_view method.""" + # Directly call the do_refresh_view method + app.main_window.do_refresh_view(None, None) +@pytest.mark.asyncio +async def test_refresh_method_triggered_by_action(app): + """Test if the 'refresh-view' action triggers the refresh method.""" + # Patch the main window's refresh method + with patch.object(app.main_window, 'refresh', new_callable=MagicMock) as mock_refresh: + # Trigger the 'refresh-view' action + app.main_window.activate_action('refresh-view') + # Assert that the refresh method was called once + mock_refresh.assert_called_once() +@pytest.mark.asyncio +async def test_do_get_item_direct_call_fails_due_to_no_selected_item(app): + """Test that the do_get_item method for copying a selected item fails when not item is selected.""" + mock_variant = GLib.Variant.new_string("dummy_path") - - - + # Directly call the method with mock objects + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'dataset'"): + app.main_window.do_get_item(None, mock_variant) From 5d312d612f90c01cf1008f9e160179a8a27dda34 Mon Sep 17 00:00:00 2001 From: Johannes Laurin Hoermann Date: Mon, 18 Dec 2023 12:24:38 +0100 Subject: [PATCH 07/11] MAINT: update license headers, tests as well --- dtool_lookup_gui/main.py | 4 +- dtool_lookup_gui/models/settings.py | 3 +- dtool_lookup_gui/utils/copy_manager.py | 2 +- dtool_lookup_gui/views/config_details.py | 23 + .../views/error_linting_dialog.py | 23 + dtool_lookup_gui/views/main_window.py | 4 +- maintenance/update_license_headers.sh | 2 +- test/conftest.py | 23 + test/test_app.py | 24 + test/test_main_app_actions.py | 24 + test/test_main_window_actions.py | 1073 +++++++++-------- 11 files changed, 672 insertions(+), 533 deletions(-) diff --git a/dtool_lookup_gui/main.py b/dtool_lookup_gui/main.py index 3fca5283..017f683c 100644 --- a/dtool_lookup_gui/main.py +++ b/dtool_lookup_gui/main.py @@ -1,5 +1,7 @@ # -# Copyright 2021-2022 Johannes Laurin Hörmann +# Copyright 2021-2023 Johannes Laurin Hörmann +# 2023 42440470+ashdriod@users.noreply.github.com +# 2023 Ashwin Vazhappilly # 2021 Lars Pastewka # # ### MIT license diff --git a/dtool_lookup_gui/models/settings.py b/dtool_lookup_gui/models/settings.py index ab3a86e6..2abe7667 100644 --- a/dtool_lookup_gui/models/settings.py +++ b/dtool_lookup_gui/models/settings.py @@ -1,5 +1,6 @@ # -# Copyright 2022 Johannes Laurin Hörmann +# Copyright 2023 Ashwin Vazhappilly +# 2022 Johannes Laurin Hörmann # 2021 Lars Pastewka # # ### MIT license diff --git a/dtool_lookup_gui/utils/copy_manager.py b/dtool_lookup_gui/utils/copy_manager.py index 882ed6ef..75581aa6 100644 --- a/dtool_lookup_gui/utils/copy_manager.py +++ b/dtool_lookup_gui/utils/copy_manager.py @@ -1,5 +1,5 @@ # -# Copyright 2022 Johannes Laurin Hörmann +# Copyright 2022-2023 Johannes Laurin Hörmann # 2021 Lars Pastewka # # ### MIT license diff --git a/dtool_lookup_gui/views/config_details.py b/dtool_lookup_gui/views/config_details.py index 2f250fbf..3c361f04 100644 --- a/dtool_lookup_gui/views/config_details.py +++ b/dtool_lookup_gui/views/config_details.py @@ -1,3 +1,26 @@ +# +# Copyright 2023 Ashwin Vazhappilly +# +# ### MIT license +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# import asyncio import logging import os diff --git a/dtool_lookup_gui/views/error_linting_dialog.py b/dtool_lookup_gui/views/error_linting_dialog.py index 518daa31..6c19ff6e 100644 --- a/dtool_lookup_gui/views/error_linting_dialog.py +++ b/dtool_lookup_gui/views/error_linting_dialog.py @@ -1,3 +1,26 @@ +# +# Copyright 2023 Ashwin Vazhappilly +# +# ### MIT license +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# import logging import os import gi diff --git a/dtool_lookup_gui/views/main_window.py b/dtool_lookup_gui/views/main_window.py index 9c186c8a..789a0d4e 100644 --- a/dtool_lookup_gui/views/main_window.py +++ b/dtool_lookup_gui/views/main_window.py @@ -1,6 +1,6 @@ # -# Copyright 2021-2023 Johannes Laurin Hörmann -# 2023 Ashwin Vazhappilly +# Copyright 2023 Ashwin Vazhappilly +# 2021-2023 Johannes Laurin Hörmann # 2021 Lars Pastewka # # ### MIT license diff --git a/maintenance/update_license_headers.sh b/maintenance/update_license_headers.sh index 1747feb3..e19dc5d0 100755 --- a/maintenance/update_license_headers.sh +++ b/maintenance/update_license_headers.sh @@ -1,7 +1,7 @@ #! /bin/sh # Updates all Python files with license taken from README.md and copyright information obtained from the git log. -for fn in setup.py `find dtool_lookup_gui -name "*.py"`; do +for fn in setup.py $(find test -name "*.py") $(find dtool_lookup_gui -name "*.py"); do echo $fn python3 maintenance/copyright.py $fn | cat - LICENSE-MIT.md | python3 maintenance/replace_header.py $fn done diff --git a/test/conftest.py b/test/conftest.py index e7d862c2..97746a0a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,3 +1,26 @@ +# +# Copyright 2022-2023 Johannes Laurin Hörmann +# +# ### MIT license +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# import json import logging import os diff --git a/test/test_app.py b/test/test_app.py index fe11e3c0..9088b0d6 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -1,3 +1,27 @@ +# +# Copyright 2022-2023 Johannes Laurin Hörmann +# 2023 Ashwin Vazhappilly +# +# ### MIT license +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# import asyncio import logging import pytest diff --git a/test/test_main_app_actions.py b/test/test_main_app_actions.py index 2c5cff68..82f42f13 100644 --- a/test/test_main_app_actions.py +++ b/test/test_main_app_actions.py @@ -1,3 +1,27 @@ +# +# Copyright 2023 Ashwin Vazhappilly +# 2022-2023 Johannes Laurin Hörmann +# +# ### MIT license +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# import json import logging import types diff --git a/test/test_main_window_actions.py b/test/test_main_window_actions.py index affe5819..97c2d7d9 100644 --- a/test/test_main_window_actions.py +++ b/test/test_main_window_actions.py @@ -1,527 +1,546 @@ -# 1. Direct calls in tests isolate specific functionalities for focused, independent testing of components. -# 2. Triggering actions and using mocks are crucial for integration testing, ensuring components interact correctly. -# 3. Mocking and action triggering simulate real-world user interactions and application responses. -# 4. Separating tests for direct calls and action triggers aids in maintaining clear, organized test structures. -# 5. This approach enhances test suite readability and makes it easier to understand and update. -import asyncio -import time - -import pytest -from unittest.mock import patch, MagicMock, AsyncMock, Mock -from gi.repository import Gtk, Gio, GLib - - -@pytest.mark.asyncio -async def test_do_search_direct_call(populated_app): - """ - Test the do_search method for directly processing a search in a populated application. - This test checks if the search action correctly triggers the search process and - verifies that the search results are appropriately loaded and displayed in the UI. - """ - - # Helper function to wait for datasets to load in the list box. - async def wait_for_datasets_to_load(list_box, timeout=10): - start_time = time.time() - while time.time() - start_time < timeout: - if len(list_box.get_children()) > 0: - return True # Datasets are loaded - await asyncio.sleep(0.1) # Yield control to allow other async tasks to run - return False # Timeout reached if datasets are not loaded within the specified time - - # Trigger the 'refresh-view' action to load datasets - populated_app.main_window.activate_action('refresh-view') - - # Wait until datasets are loaded in the dataset list box - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) - assert datasets_loaded, "Datasets were not loaded in time" - - # Create a GLib.Variant with a test search query - search_query = "test_search_query" - search_text_variant = GLib.Variant.new_string(search_query) - - # Create and add the search action to the main window of the application - search_action = Gio.SimpleAction.new("search", search_text_variant.get_type()) - populated_app.main_window.add_action(search_action) - - # Connect the search action to the do_search method of the main window - search_action.connect("activate", populated_app.main_window.do_search) - - # Trigger the search action with the test search query - search_action.activate(search_text_variant) - - # Optionally, wait for the search results to be processed and displayed - # This step depends on the application's implementation and response time - await asyncio.sleep(1) # Adjust this sleep duration as needed - - # Perform assertions to verify that the search results are correctly displayed - assert len(populated_app.main_window.base_uri_list_box.get_children()) > 0, "No search results found" - # Additional assertions can be added here based on the expected outcomes of the search - - # Await completion of any remaining asynchronous tasks related to the search - pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] - if pending_tasks: - await asyncio.gather(*pending_tasks) - - -@pytest.mark.asyncio -async def test_do_search_action_trigger(app): - """Test if 'search' action triggers do_search method.""" - - # Mock dependencies - mock_base_uri_list_box = MagicMock() - app.main_window.base_uri_list_box = mock_base_uri_list_box - - # Setup necessary mocks for the action trigger - app.main_window._search = MagicMock() - - # Create and add the action - search_text_variant = GLib.Variant.new_string("test_search_query") - search_action = Gio.SimpleAction.new("search", search_text_variant.get_type()) - app.main_window.add_action(search_action) - - # Patch do_search method after action is added - with patch.object(app.main_window, 'do_search', new_callable=MagicMock) as mock_do_search: - # Connect the action - search_action.connect("activate", app.main_window.do_search) - - # Trigger the action - search_action.activate(search_text_variant) - - # Assert that do_search was called once - mock_do_search.assert_called_once_with(search_action, search_text_variant) - - -@pytest.mark.asyncio -async def test_do_select_dataset_row_by_row_index_direct_call(populated_app): - """ - Test the do_select_dataset_row_by_row_index method for directly selecting a dataset row by index. - It verifies if the dataset list is correctly populated and the specified dataset row is selected. - """ - - async def wait_for_datasets_to_load(list_box, timeout=10): - start_time = time.time() - while time.time() - start_time < timeout: - if len(list_box.get_children()) > 0: - return True - await asyncio.sleep(0.1) - return False - - # Trigger the 'refresh-view' action and wait for datasets to load - populated_app.main_window.activate_action('refresh-view') - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) - assert datasets_loaded, "Datasets were not loaded in time" - - # Create a GLib.Variant with a valid test row index (e.g., 0 for the first row) - row_index = 6 - row_index_variant = GLib.Variant.new_uint32(row_index) - - # Create and add the select dataset action to the main window of the application - select_dataset_action = Gio.SimpleAction.new("select-dataset", row_index_variant.get_type()) - populated_app.main_window.add_action(select_dataset_action) - - # Connect the select dataset action to the do_select_dataset_row_by_row_index method - select_dataset_action.connect("activate", populated_app.main_window.do_select_dataset_row_by_row_index) - - # Trigger the select dataset action with the test row index - select_dataset_action.activate(row_index_variant) - - # Optionally, wait for the UI to update if necessary - await asyncio.sleep(0.1) # Adjust this sleep duration as needed - - # Perform assertions to verify that the correct dataset row is selected - selected_row = populated_app.main_window.dataset_list_box.get_selected_row() - - # Assert that the selected row is not None and has the expected index - assert selected_row is not None, "No dataset row was selected" - assert selected_row.get_index() == row_index, f"Expected row index {row_index}, got {selected_row.get_index()}" - - # Await completion of any remaining asynchronous tasks related to the selection - pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] - if pending_tasks: - await asyncio.gather(*pending_tasks) - - -@pytest.mark.asyncio -async def test_do_select_dataset_row_by_row_index_action_trigger(app): - """Test if 'select-dataset' action triggers do_select_dataset_row_by_row_index method.""" - - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box - - # Setup necessary mocks for the action trigger - app.main_window._select_dataset_row_by_row_index = MagicMock() - - # Create and add the action - row_index_variant = GLib.Variant.new_uint32(1) # Example index - select_dataset_action = Gio.SimpleAction.new("select-dataset", row_index_variant.get_type()) - app.main_window.add_action(select_dataset_action) - - # Patch do_select_dataset_row_by_row_index method after action is added - with patch.object(app.main_window, 'do_select_dataset_row_by_row_index', - new_callable=MagicMock) as mock_do_select_dataset_row_by_row_index: - # Connect the action - select_dataset_action.connect("activate", app.main_window.do_select_dataset_row_by_row_index) - - # Trigger the action - select_dataset_action.activate(row_index_variant) - - # Assert that do_select_dataset_row_by_row_index was called once - mock_do_select_dataset_row_by_row_index.assert_called_once_with(select_dataset_action, row_index_variant) - - -@pytest.mark.asyncio -async def test_do_show_dataset_details_by_uri_direct_call(populated_app): - """ - Test the do_show_dataset_details_by_uri method for processing a URI directly. It verifies - if the correct dataset is selected and its content is displayed based on the URI. - """ - - async def wait_for_datasets_to_load(list_box, timeout=10): - start_time = time.time() - while time.time() - start_time < timeout: - if len(list_box.get_children()) > 0: - return True # Datasets are loaded - await asyncio.sleep(0.1) # Yield control to allow other async tasks to run - return False # Timeout reached - - # Trigger the 'refresh-view' action - populated_app.main_window.activate_action('refresh-view') - - # Wait until datasets are loaded - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) - assert datasets_loaded, "Datasets were not loaded in time" - - # Define a URI that corresponds to a dataset in the populated dataset list - test_uri = "smb://test-share/51e67a6e-3f38-43e9-bb79-01700dc09c61" - - # Create a GLib.Variant with the test URI - uri_variant = GLib.Variant.new_string(test_uri) - - # Call the do_show_dataset_details_by_uri method with the test URI - populated_app.main_window.do_show_dataset_details_by_uri(None, uri_variant) - - # Retrieve the dataset that should be selected based on the URI - index = populated_app.main_window.dataset_list_box.get_row_index_from_uri(test_uri) - selected_row = populated_app.main_window.dataset_list_box.get_row_at_index(index) - selected_dataset = selected_row.dataset if selected_row is not None else None - - # Assert that the dataset with the given URI is selected - assert selected_dataset is not None, "No dataset was selected" - assert selected_dataset.uri == test_uri, f"Expected URI {test_uri}, got {selected_dataset.uri}" - - # Await completion of all tasks related to the test - pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] - if pending_tasks: - await asyncio.gather(*pending_tasks) - - -@pytest.mark.asyncio -async def test_do_show_dataset_details_by_uri_action_trigger(app): - """Test if 'show-dataset-by-uri' action triggers do_show_dataset_details_by_uri method.""" - - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box - - # Setup necessary mocks for the action trigger - app.main_window._show_dataset_details_by_uri = MagicMock() - - # Create and add the action - uri_variant = GLib.Variant.new_string("dummy_uri") - show_dataset_by_uri_action = Gio.SimpleAction.new("show-dataset-by-uri", uri_variant.get_type()) - app.main_window.add_action(show_dataset_by_uri_action) - - # Patch do_show_dataset_details_by_uri method after action is added - with patch.object(app.main_window, 'do_show_dataset_details_by_uri', - new_callable=MagicMock) as mock_do_show_dataset_details_by_uri: - # Connect the action - show_dataset_by_uri_action.connect("activate", app.main_window.do_show_dataset_details_by_uri) - - # Trigger the action - show_dataset_by_uri_action.activate(uri_variant) - - # Assert that do_show_dataset_details_by_uri was called once - mock_do_show_dataset_details_by_uri.assert_called_once_with(show_dataset_by_uri_action, uri_variant) - - -@pytest.mark.asyncio -async def test_do_show_dataset_details_by_row_index_direct_call(populated_app, mock_dataset_list): - """ - Test the do_show_dataset_details_by_row_index method for directly showing dataset details by row index. - It verifies if the dataset list is correctly populated and the specified dataset details are displayed. - """ - - async def wait_for_datasets_to_load(list_box, timeout=10): - start_time = time.time() - while time.time() - start_time < timeout: - if len(list_box.get_children()) > 0: - return True # Datasets are loaded - await asyncio.sleep(0.1) # Yield control to allow other async tasks to run - return False # Timeout reached if datasets are not loaded within the specified time - - # Trigger the 'refresh-view' action and wait for datasets to load - populated_app.main_window.activate_action('refresh-view') - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) - assert datasets_loaded, "Datasets were not loaded in time" - - # Create a GLib.Variant with a test row index (e.g., 0 for the first row) - row_index = 1 - row_index_variant = GLib.Variant.new_uint32(row_index) - - # Create and add the show dataset action to the main window of the application - show_dataset_action = Gio.SimpleAction.new("show-dataset", row_index_variant.get_type()) - populated_app.main_window.add_action(show_dataset_action) - - # Connect the show dataset action to the do_show_dataset_details_by_row_index method - show_dataset_action.connect("activate", populated_app.main_window.do_show_dataset_details_by_row_index) - - # Trigger the show dataset action with the test row index - show_dataset_action.activate(row_index_variant) - - # Optionally, wait for the UI to update and dataset details to be displayed - await asyncio.sleep(0.1) # Adjust this sleep duration as needed - - # Perform assertions to verify that the dataset details are correctly displayed - selected_dataset = populated_app.main_window.dataset_list_box.get_row_at_index(row_index).dataset - - expected_uri = mock_dataset_list[1]['uri'] - expected_uuid = mock_dataset_list[1]['uuid'] - - assert selected_dataset.uri == expected_uri, f"Expected URI {expected_uri}, got {selected_dataset.uri}" - assert selected_dataset.uuid == expected_uuid, f"Expected UUID {expected_uuid}, got {selected_dataset.uuid}" - - # Additional assertions can be made here based on the expected behavior and UI element updates - - # Await completion of any remaining asynchronous tasks related to showing the dataset details - pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] - if pending_tasks: - await asyncio.gather(*pending_tasks) - - -@pytest.mark.asyncio -async def test_do_show_dataset_details_by_row_index_action_trigger(app): - """Test if 'show-dataset' action triggers do_show_dataset_details_by_row_index method.""" - - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box - - # Setup necessary mocks for the action trigger - app.main_window._show_dataset_details_by_row_index = MagicMock() - - # Create and add the action - row_index_variant = GLib.Variant.new_uint32(1) # Example index - show_dataset_action = Gio.SimpleAction.new("show-dataset", row_index_variant.get_type()) - app.main_window.add_action(show_dataset_action) - - # Patch do_show_dataset_details_by_row_index method after action is added - with patch.object(app.main_window, 'do_show_dataset_details_by_row_index', - new_callable=MagicMock) as mock_do_show_dataset_details_by_row_index: - # Connect the action - show_dataset_action.connect("activate", app.main_window.do_show_dataset_details_by_row_index) - - # Trigger the action - show_dataset_action.activate(row_index_variant) - - # Assert that do_show_dataset_details_by_row_index was called once - mock_do_show_dataset_details_by_row_index.assert_called_once_with(show_dataset_action, row_index_variant) - - -@pytest.mark.asyncio -async def test_do_search_select_and_show_direct_call(populated_app, mock_dataset_list): - """ - Test the do_search_select_and_show method for processing a search directly. It verifies - if the dataset list is correctly populated, the first dataset is selected, and its - content is displayed. - """ - - async def wait_for_datasets_to_load(list_box, timeout=10): - start_time = time.time() - while time.time() - start_time < timeout: - if len(list_box.get_children()) > 0: - return True # Datasets are loaded - await asyncio.sleep(0.1) # Yield control to allow other async tasks to run - return False # Timeout reached - - # Trigger the 'refresh-view' action - populated_app.main_window.activate_action('refresh-view') - - # Wait until datasets are loaded - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) - assert datasets_loaded, "Datasets were not loaded in time" - - # Create a GLib.Variant with the test search query - mock_variant = GLib.Variant.new_string("test_search_query") - - # Call the method with the test search query - populated_app.main_window.do_search_select_and_show(None, mock_variant) - - # Assertions to check the state of the application after the search - assert len(populated_app.main_window.dataset_list_box.get_children()) > 0, "No datasets found in the list box" - first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] - dataset = first_dataset_row.dataset - - # Assert that the dataset's URI matches the expected URI - assert dataset.uri == mock_dataset_list[0]['uri'], f"Expected URI {mock_dataset_list[0]['uri']}, got {dataset.uri}" - - # Await completion of all tasks related to the test - pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] - if pending_tasks: - await asyncio.gather(*pending_tasks) - - -@pytest.mark.asyncio -async def test_do_search_select_and_show_action_trigger(app): - """Test if 'search-select-show' action triggers do_search_select_and_show method.""" - - # Mock dependencies - mock_base_uri_list_box = MagicMock() - app.main_window.base_uri_list_box = mock_base_uri_list_box - - # Setup necessary mocks for the action trigger - app.main_window._search_select_and_show = MagicMock() - - # Create and add the action - search_variant = GLib.Variant.new_string("test_search_query") - search_select_show_action = Gio.SimpleAction.new("search-select-show", search_variant.get_type()) - app.main_window.add_action(search_select_show_action) - - # Patch do_search_select_and_show method after action is added - with patch.object(app.main_window, 'do_search_select_and_show', - new_callable=MagicMock) as mock_do_search_select_and_show: - # Connect the action - search_select_show_action.connect("activate", app.main_window.do_search_select_and_show) - - # Trigger the action - search_select_show_action.activate(search_variant) - - # Assert that do_search_select_and_show was called once - mock_do_search_select_and_show.assert_called_once_with(search_select_show_action, search_variant) - - -@pytest.mark.asyncio -@pytest.mark.skip(reason="no way of currently testing this") -async def test_do_get_item_direct_call(populated_app, tmp_path): - """ - Test the do_get_item method for copying a selected item directly. - It verifies if the selected item is correctly copied to the specified destination. - """ - - async def wait_for_datasets_to_load(list_box, timeout=10): - start_time = time.time() - while time.time() - start_time < timeout: - if len(list_box.get_children()) > 0: - return True # Datasets are loaded - await asyncio.sleep(0.1) # Yield control to allow other async tasks to run - return False # Timeout reached - - # Trigger the 'refresh-view' action to load datasets - populated_app.main_window.activate_action('refresh-view') - - # Wait until datasets are loaded - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) - assert datasets_loaded, "Datasets were not loaded in time" - - # Select the first dataset - first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] - dataset = first_dataset_row.dataset - - # Assuming the first dataset's UUID as the item UUID (Replace this with the actual item UUID logic) - item_uuid = dataset.uuid - populated_app.main_window._get_selected_items = lambda: [('item_name', item_uuid)] - - # Define the destination file path - dest_file = tmp_path / "destination_file" - - # Create a GLib.Variant with the destination file path - dest_file_variant = GLib.Variant.new_string(str(dest_file)) - - dest_file = tmp_path / "destination_file" - print(f"Destination file path: {dest_file}") - - # Call the do_get_item method without 'await' - try: - populated_app.main_window.do_get_item(None, dest_file_variant) - except Exception as e: - print(f"Error during 'do_get_item': {e}") - raise - - # Wait for all asynchronous tasks to complete - await asyncio.sleep(1) # Increase sleep time if necessary - pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] - if pending_tasks: - completed_tasks = await asyncio.gather(*pending_tasks, return_exceptions=True) - for task in completed_tasks: - if isinstance(task, Exception): - print(f"Exception in async task: {task}") - - # Verify that the file was copied - file_exists = dest_file.exists() - print(f"File exists: {file_exists}") - assert file_exists, "The item was not copied to the specified destination" - - -@pytest.mark.asyncio -async def test_do_get_item_action_trigger(app): - """Test if 'get-item' action triggers do_get_item method.""" - - # Mock dependencies - mock_dataset_list_box = MagicMock() - app.main_window.dataset_list_box = mock_dataset_list_box - mock_settings = MagicMock() - app.main_window.settings = mock_settings - - # Setup necessary mocks for the action trigger - mock_dataset = MagicMock() - mock_dataset.get_item = AsyncMock() - mock_dataset_list_box.get_selected_row.return_value = MagicMock(dataset=mock_dataset) - app.main_window._get_selected_items = MagicMock(return_value=[('item_name', 'item_uuid')]) - - # Create and add the action - dest_file_variant = GLib.Variant.new_string("dummy_path") - get_item_action = Gio.SimpleAction.new("get-item", dest_file_variant.get_type()) - app.main_window.add_action(get_item_action) - - # Patch do_get_item method after action is added - with patch.object(app.main_window, 'do_get_item', new_callable=MagicMock) as mock_do_get_item: - # Connect the action - get_item_action.connect("activate", app.main_window.do_get_item) - - # Trigger the action - get_item_action.activate(dest_file_variant) - - # Assert that do_get_item was called once - mock_do_get_item.assert_called_once_with(get_item_action, dest_file_variant) - - -@pytest.mark.asyncio -async def test_do_refresh_view_direct_call(app): - """Test the direct call of the do_refresh_view method.""" - - # Directly call the do_refresh_view method - app.main_window.do_refresh_view(None, None) - - -@pytest.mark.asyncio -async def test_refresh_method_triggered_by_action(app): - """Test if the 'refresh-view' action triggers the refresh method.""" - - # Patch the main window's refresh method - with patch.object(app.main_window, 'refresh', new_callable=MagicMock) as mock_refresh: - # Trigger the 'refresh-view' action - app.main_window.activate_action('refresh-view') - - # Assert that the refresh method was called once - mock_refresh.assert_called_once() - - -@pytest.mark.asyncio -async def test_do_get_item_direct_call_fails_due_to_no_selected_item(app): - """Test that the do_get_item method for copying a selected item fails when not item is selected.""" - - mock_variant = GLib.Variant.new_string("dummy_path") - - # Directly call the method with mock objects - with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'dataset'"): - app.main_window.do_get_item(None, mock_variant) +# +# Copyright 2023 Ashwin Vazhappilly +# 2022-2023 Johannes Laurin Hörmann +# +# ### MIT license +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +import asyncio +import time + +import pytest +from unittest.mock import patch, MagicMock, AsyncMock, Mock +from gi.repository import Gtk, Gio, GLib + + +@pytest.mark.asyncio +async def test_do_search_direct_call(populated_app): + """ + Test the do_search method for directly processing a search in a populated application. + This test checks if the search action correctly triggers the search process and + verifies that the search results are appropriately loaded and displayed in the UI. + """ + + # Helper function to wait for datasets to load in the list box. + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run + return False # Timeout reached if datasets are not loaded within the specified time + + # Trigger the 'refresh-view' action to load datasets + populated_app.main_window.activate_action('refresh-view') + + # Wait until datasets are loaded in the dataset list box + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Create a GLib.Variant with a test search query + search_query = "test_search_query" + search_text_variant = GLib.Variant.new_string(search_query) + + # Create and add the search action to the main window of the application + search_action = Gio.SimpleAction.new("search", search_text_variant.get_type()) + populated_app.main_window.add_action(search_action) + + # Connect the search action to the do_search method of the main window + search_action.connect("activate", populated_app.main_window.do_search) + + # Trigger the search action with the test search query + search_action.activate(search_text_variant) + + # Optionally, wait for the search results to be processed and displayed + # This step depends on the application's implementation and response time + await asyncio.sleep(1) # Adjust this sleep duration as needed + + # Perform assertions to verify that the search results are correctly displayed + assert len(populated_app.main_window.base_uri_list_box.get_children()) > 0, "No search results found" + # Additional assertions can be added here based on the expected outcomes of the search + + # Await completion of any remaining asynchronous tasks related to the search + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + await asyncio.gather(*pending_tasks) + + +@pytest.mark.asyncio +async def test_do_search_action_trigger(app): + """Test if 'search' action triggers do_search method.""" + + # Mock dependencies + mock_base_uri_list_box = MagicMock() + app.main_window.base_uri_list_box = mock_base_uri_list_box + + # Setup necessary mocks for the action trigger + app.main_window._search = MagicMock() + + # Create and add the action + search_text_variant = GLib.Variant.new_string("test_search_query") + search_action = Gio.SimpleAction.new("search", search_text_variant.get_type()) + app.main_window.add_action(search_action) + + # Patch do_search method after action is added + with patch.object(app.main_window, 'do_search', new_callable=MagicMock) as mock_do_search: + # Connect the action + search_action.connect("activate", app.main_window.do_search) + + # Trigger the action + search_action.activate(search_text_variant) + + # Assert that do_search was called once + mock_do_search.assert_called_once_with(search_action, search_text_variant) + + +@pytest.mark.asyncio +async def test_do_select_dataset_row_by_row_index_direct_call(populated_app): + """ + Test the do_select_dataset_row_by_row_index method for directly selecting a dataset row by index. + It verifies if the dataset list is correctly populated and the specified dataset row is selected. + """ + + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True + await asyncio.sleep(0.1) + return False + + # Trigger the 'refresh-view' action and wait for datasets to load + populated_app.main_window.activate_action('refresh-view') + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Create a GLib.Variant with a valid test row index (e.g., 0 for the first row) + row_index = 6 + row_index_variant = GLib.Variant.new_uint32(row_index) + + # Create and add the select dataset action to the main window of the application + select_dataset_action = Gio.SimpleAction.new("select-dataset", row_index_variant.get_type()) + populated_app.main_window.add_action(select_dataset_action) + + # Connect the select dataset action to the do_select_dataset_row_by_row_index method + select_dataset_action.connect("activate", populated_app.main_window.do_select_dataset_row_by_row_index) + + # Trigger the select dataset action with the test row index + select_dataset_action.activate(row_index_variant) + + # Optionally, wait for the UI to update if necessary + await asyncio.sleep(0.1) # Adjust this sleep duration as needed + + # Perform assertions to verify that the correct dataset row is selected + selected_row = populated_app.main_window.dataset_list_box.get_selected_row() + + # Assert that the selected row is not None and has the expected index + assert selected_row is not None, "No dataset row was selected" + assert selected_row.get_index() == row_index, f"Expected row index {row_index}, got {selected_row.get_index()}" + + # Await completion of any remaining asynchronous tasks related to the selection + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + await asyncio.gather(*pending_tasks) + + +@pytest.mark.asyncio +async def test_do_select_dataset_row_by_row_index_action_trigger(app): + """Test if 'select-dataset' action triggers do_select_dataset_row_by_row_index method.""" + + # Mock dependencies + mock_dataset_list_box = MagicMock() + app.main_window.dataset_list_box = mock_dataset_list_box + + # Setup necessary mocks for the action trigger + app.main_window._select_dataset_row_by_row_index = MagicMock() + + # Create and add the action + row_index_variant = GLib.Variant.new_uint32(1) # Example index + select_dataset_action = Gio.SimpleAction.new("select-dataset", row_index_variant.get_type()) + app.main_window.add_action(select_dataset_action) + + # Patch do_select_dataset_row_by_row_index method after action is added + with patch.object(app.main_window, 'do_select_dataset_row_by_row_index', + new_callable=MagicMock) as mock_do_select_dataset_row_by_row_index: + # Connect the action + select_dataset_action.connect("activate", app.main_window.do_select_dataset_row_by_row_index) + + # Trigger the action + select_dataset_action.activate(row_index_variant) + + # Assert that do_select_dataset_row_by_row_index was called once + mock_do_select_dataset_row_by_row_index.assert_called_once_with(select_dataset_action, row_index_variant) + + +@pytest.mark.asyncio +async def test_do_show_dataset_details_by_uri_direct_call(populated_app): + """ + Test the do_show_dataset_details_by_uri method for processing a URI directly. It verifies + if the correct dataset is selected and its content is displayed based on the URI. + """ + + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run + return False # Timeout reached + + # Trigger the 'refresh-view' action + populated_app.main_window.activate_action('refresh-view') + + # Wait until datasets are loaded + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Define a URI that corresponds to a dataset in the populated dataset list + test_uri = "smb://test-share/51e67a6e-3f38-43e9-bb79-01700dc09c61" + + # Create a GLib.Variant with the test URI + uri_variant = GLib.Variant.new_string(test_uri) + + # Call the do_show_dataset_details_by_uri method with the test URI + populated_app.main_window.do_show_dataset_details_by_uri(None, uri_variant) + + # Retrieve the dataset that should be selected based on the URI + index = populated_app.main_window.dataset_list_box.get_row_index_from_uri(test_uri) + selected_row = populated_app.main_window.dataset_list_box.get_row_at_index(index) + selected_dataset = selected_row.dataset if selected_row is not None else None + + # Assert that the dataset with the given URI is selected + assert selected_dataset is not None, "No dataset was selected" + assert selected_dataset.uri == test_uri, f"Expected URI {test_uri}, got {selected_dataset.uri}" + + # Await completion of all tasks related to the test + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + await asyncio.gather(*pending_tasks) + + +@pytest.mark.asyncio +async def test_do_show_dataset_details_by_uri_action_trigger(app): + """Test if 'show-dataset-by-uri' action triggers do_show_dataset_details_by_uri method.""" + + # Mock dependencies + mock_dataset_list_box = MagicMock() + app.main_window.dataset_list_box = mock_dataset_list_box + + # Setup necessary mocks for the action trigger + app.main_window._show_dataset_details_by_uri = MagicMock() + + # Create and add the action + uri_variant = GLib.Variant.new_string("dummy_uri") + show_dataset_by_uri_action = Gio.SimpleAction.new("show-dataset-by-uri", uri_variant.get_type()) + app.main_window.add_action(show_dataset_by_uri_action) + + # Patch do_show_dataset_details_by_uri method after action is added + with patch.object(app.main_window, 'do_show_dataset_details_by_uri', + new_callable=MagicMock) as mock_do_show_dataset_details_by_uri: + # Connect the action + show_dataset_by_uri_action.connect("activate", app.main_window.do_show_dataset_details_by_uri) + + # Trigger the action + show_dataset_by_uri_action.activate(uri_variant) + + # Assert that do_show_dataset_details_by_uri was called once + mock_do_show_dataset_details_by_uri.assert_called_once_with(show_dataset_by_uri_action, uri_variant) + + +@pytest.mark.asyncio +async def test_do_show_dataset_details_by_row_index_direct_call(populated_app, mock_dataset_list): + """ + Test the do_show_dataset_details_by_row_index method for directly showing dataset details by row index. + It verifies if the dataset list is correctly populated and the specified dataset details are displayed. + """ + + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run + return False # Timeout reached if datasets are not loaded within the specified time + + # Trigger the 'refresh-view' action and wait for datasets to load + populated_app.main_window.activate_action('refresh-view') + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Create a GLib.Variant with a test row index (e.g., 0 for the first row) + row_index = 1 + row_index_variant = GLib.Variant.new_uint32(row_index) + + # Create and add the show dataset action to the main window of the application + show_dataset_action = Gio.SimpleAction.new("show-dataset", row_index_variant.get_type()) + populated_app.main_window.add_action(show_dataset_action) + + # Connect the show dataset action to the do_show_dataset_details_by_row_index method + show_dataset_action.connect("activate", populated_app.main_window.do_show_dataset_details_by_row_index) + + # Trigger the show dataset action with the test row index + show_dataset_action.activate(row_index_variant) + + # Optionally, wait for the UI to update and dataset details to be displayed + await asyncio.sleep(0.1) # Adjust this sleep duration as needed + + # Perform assertions to verify that the dataset details are correctly displayed + selected_dataset = populated_app.main_window.dataset_list_box.get_row_at_index(row_index).dataset + + expected_uri = mock_dataset_list[1]['uri'] + expected_uuid = mock_dataset_list[1]['uuid'] + + assert selected_dataset.uri == expected_uri, f"Expected URI {expected_uri}, got {selected_dataset.uri}" + assert selected_dataset.uuid == expected_uuid, f"Expected UUID {expected_uuid}, got {selected_dataset.uuid}" + + # Additional assertions can be made here based on the expected behavior and UI element updates + + # Await completion of any remaining asynchronous tasks related to showing the dataset details + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + await asyncio.gather(*pending_tasks) + + +@pytest.mark.asyncio +async def test_do_show_dataset_details_by_row_index_action_trigger(app): + """Test if 'show-dataset' action triggers do_show_dataset_details_by_row_index method.""" + + # Mock dependencies + mock_dataset_list_box = MagicMock() + app.main_window.dataset_list_box = mock_dataset_list_box + + # Setup necessary mocks for the action trigger + app.main_window._show_dataset_details_by_row_index = MagicMock() + + # Create and add the action + row_index_variant = GLib.Variant.new_uint32(1) # Example index + show_dataset_action = Gio.SimpleAction.new("show-dataset", row_index_variant.get_type()) + app.main_window.add_action(show_dataset_action) + + # Patch do_show_dataset_details_by_row_index method after action is added + with patch.object(app.main_window, 'do_show_dataset_details_by_row_index', + new_callable=MagicMock) as mock_do_show_dataset_details_by_row_index: + # Connect the action + show_dataset_action.connect("activate", app.main_window.do_show_dataset_details_by_row_index) + + # Trigger the action + show_dataset_action.activate(row_index_variant) + + # Assert that do_show_dataset_details_by_row_index was called once + mock_do_show_dataset_details_by_row_index.assert_called_once_with(show_dataset_action, row_index_variant) + + +@pytest.mark.asyncio +async def test_do_search_select_and_show_direct_call(populated_app, mock_dataset_list): + """ + Test the do_search_select_and_show method for processing a search directly. It verifies + if the dataset list is correctly populated, the first dataset is selected, and its + content is displayed. + """ + + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run + return False # Timeout reached + + # Trigger the 'refresh-view' action + populated_app.main_window.activate_action('refresh-view') + + # Wait until datasets are loaded + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Create a GLib.Variant with the test search query + mock_variant = GLib.Variant.new_string("test_search_query") + + # Call the method with the test search query + populated_app.main_window.do_search_select_and_show(None, mock_variant) + + # Assertions to check the state of the application after the search + assert len(populated_app.main_window.dataset_list_box.get_children()) > 0, "No datasets found in the list box" + first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] + dataset = first_dataset_row.dataset + + # Assert that the dataset's URI matches the expected URI + assert dataset.uri == mock_dataset_list[0]['uri'], f"Expected URI {mock_dataset_list[0]['uri']}, got {dataset.uri}" + + # Await completion of all tasks related to the test + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + await asyncio.gather(*pending_tasks) + + +@pytest.mark.asyncio +async def test_do_search_select_and_show_action_trigger(app): + """Test if 'search-select-show' action triggers do_search_select_and_show method.""" + + # Mock dependencies + mock_base_uri_list_box = MagicMock() + app.main_window.base_uri_list_box = mock_base_uri_list_box + + # Setup necessary mocks for the action trigger + app.main_window._search_select_and_show = MagicMock() + + # Create and add the action + search_variant = GLib.Variant.new_string("test_search_query") + search_select_show_action = Gio.SimpleAction.new("search-select-show", search_variant.get_type()) + app.main_window.add_action(search_select_show_action) + + # Patch do_search_select_and_show method after action is added + with patch.object(app.main_window, 'do_search_select_and_show', + new_callable=MagicMock) as mock_do_search_select_and_show: + # Connect the action + search_select_show_action.connect("activate", app.main_window.do_search_select_and_show) + + # Trigger the action + search_select_show_action.activate(search_variant) + + # Assert that do_search_select_and_show was called once + mock_do_search_select_and_show.assert_called_once_with(search_select_show_action, search_variant) + + +@pytest.mark.asyncio +@pytest.mark.skip(reason="no way of currently testing this") +async def test_do_get_item_direct_call(populated_app, tmp_path): + """ + Test the do_get_item method for copying a selected item directly. + It verifies if the selected item is correctly copied to the specified destination. + """ + + async def wait_for_datasets_to_load(list_box, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + if len(list_box.get_children()) > 0: + return True # Datasets are loaded + await asyncio.sleep(0.1) # Yield control to allow other async tasks to run + return False # Timeout reached + + # Trigger the 'refresh-view' action to load datasets + populated_app.main_window.activate_action('refresh-view') + + # Wait until datasets are loaded + datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + assert datasets_loaded, "Datasets were not loaded in time" + + # Select the first dataset + first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] + dataset = first_dataset_row.dataset + + # Assuming the first dataset's UUID as the item UUID (Replace this with the actual item UUID logic) + item_uuid = dataset.uuid + populated_app.main_window._get_selected_items = lambda: [('item_name', item_uuid)] + + # Define the destination file path + dest_file = tmp_path / "destination_file" + + # Create a GLib.Variant with the destination file path + dest_file_variant = GLib.Variant.new_string(str(dest_file)) + + dest_file = tmp_path / "destination_file" + print(f"Destination file path: {dest_file}") + + # Call the do_get_item method without 'await' + try: + populated_app.main_window.do_get_item(None, dest_file_variant) + except Exception as e: + print(f"Error during 'do_get_item': {e}") + raise + + # Wait for all asynchronous tasks to complete + await asyncio.sleep(1) # Increase sleep time if necessary + pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] + if pending_tasks: + completed_tasks = await asyncio.gather(*pending_tasks, return_exceptions=True) + for task in completed_tasks: + if isinstance(task, Exception): + print(f"Exception in async task: {task}") + + # Verify that the file was copied + file_exists = dest_file.exists() + print(f"File exists: {file_exists}") + assert file_exists, "The item was not copied to the specified destination" + + +@pytest.mark.asyncio +async def test_do_get_item_action_trigger(app): + """Test if 'get-item' action triggers do_get_item method.""" + + # Mock dependencies + mock_dataset_list_box = MagicMock() + app.main_window.dataset_list_box = mock_dataset_list_box + mock_settings = MagicMock() + app.main_window.settings = mock_settings + + # Setup necessary mocks for the action trigger + mock_dataset = MagicMock() + mock_dataset.get_item = AsyncMock() + mock_dataset_list_box.get_selected_row.return_value = MagicMock(dataset=mock_dataset) + app.main_window._get_selected_items = MagicMock(return_value=[('item_name', 'item_uuid')]) + + # Create and add the action + dest_file_variant = GLib.Variant.new_string("dummy_path") + get_item_action = Gio.SimpleAction.new("get-item", dest_file_variant.get_type()) + app.main_window.add_action(get_item_action) + + # Patch do_get_item method after action is added + with patch.object(app.main_window, 'do_get_item', new_callable=MagicMock) as mock_do_get_item: + # Connect the action + get_item_action.connect("activate", app.main_window.do_get_item) + + # Trigger the action + get_item_action.activate(dest_file_variant) + + # Assert that do_get_item was called once + mock_do_get_item.assert_called_once_with(get_item_action, dest_file_variant) + + +@pytest.mark.asyncio +async def test_do_refresh_view_direct_call(app): + """Test the direct call of the do_refresh_view method.""" + + # Directly call the do_refresh_view method + app.main_window.do_refresh_view(None, None) + + +@pytest.mark.asyncio +async def test_refresh_method_triggered_by_action(app): + """Test if the 'refresh-view' action triggers the refresh method.""" + + # Patch the main window's refresh method + with patch.object(app.main_window, 'refresh', new_callable=MagicMock) as mock_refresh: + # Trigger the 'refresh-view' action + app.main_window.activate_action('refresh-view') + + # Assert that the refresh method was called once + mock_refresh.assert_called_once() + + +@pytest.mark.asyncio +async def test_do_get_item_direct_call_fails_due_to_no_selected_item(app): + """Test that the do_get_item method for copying a selected item fails when not item is selected.""" + + mock_variant = GLib.Variant.new_string("dummy_path") + + # Directly call the method with mock objects + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'dataset'"): + app.main_window.do_get_item(None, mock_variant) From b5046504a0eb51455fde09132ecb54437cdbfebc Mon Sep 17 00:00:00 2001 From: Johannes Laurin Hoermann Date: Mon, 18 Dec 2023 12:36:07 +0100 Subject: [PATCH 08/11] DOC: Added short tests contribution guideline --- CONTRIBUTING.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9228710..1b87d0f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,4 +52,12 @@ When implementing new features, think about how to break them down into atomic a In most cases, you will want to use the [Gio.SimpleAction](https://lazka.github.io/pgi-docs/Gio-2.0/interfaces/SimpleAction.html) interface. You will find usage examples within the code, in the [GTK Python developer handbook: Working with GIO Actions](https://bharatkalluri.gitbook.io/gnome-developer-handbook/writing-apps-using-python/working-with-gio-gactions). Write actions as methods prefixed with `do_*`, e.g. `do_search` and attach them to the app itself or to the window they relate to. -Write at least one unit test for each action, e.g. `test_do_search` for the action `do_search`. \ No newline at end of file +Write at least one unit test for each action, e.g. `test_do_search` for the action `do_search`. + +Tests +----- + +For testing, we mock the `dtool_lookup_api` in `conftest.py` to return dummy data on all API calls used within `dtool-lookup-gui`. +With for functionality packed in atomic actions, it is then straight forward to write unit tests. +We aim at writing two test cases for each action. One test case directly calls the action, e.g. `app.do_reset_config(...)` and tests against its operation on the provided dummy data without mocking anything other than the `dtool_lookup_api` calls. +The second test mocks many internal functions evoked by an action, activates the action via the intended GTK framework mechanism, e.g. `app.activate_action('reset-config')` and subsequently asserts the expected evocation of mocked methods. \ No newline at end of file From 1e90d05ca05c38750c61bdb7c2b32bd89a6f98fc Mon Sep 17 00:00:00 2001 From: Johannes Laurin Hoermann Date: Mon, 18 Dec 2023 14:03:46 +0100 Subject: [PATCH 09/11] TST: test with actual local dataset --- test/conftest.py | 82 ++++++++++++++++++++++-- test/data/sample_dataset_content.txt | 1 + test/data/tiny.png | Bin 0 -> 276 bytes test/test_main_window_actions.py | 91 +++++++++++++++------------ 4 files changed, 129 insertions(+), 45 deletions(-) create mode 100644 test/data/sample_dataset_content.txt create mode 100644 test/data/tiny.png diff --git a/test/conftest.py b/test/conftest.py index 97746a0a..9ff80b3e 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -237,7 +237,42 @@ def mock_config_info(): @pytest.fixture(scope="function") -def app_with_mock_dtool_lookup_api_calls( +def local_dataset_uri(tmp_path): + """Create a proper local dataset to test against.""" + + from dtoolcore import ProtoDataSet, generate_admin_metadata + from dtoolcore.storagebroker import DiskStorageBroker + + name = "test_dataset" + admin_metadata = generate_admin_metadata(name) + dest_uri = DiskStorageBroker.generate_uri( + name=name, + uuid=admin_metadata["uuid"], + base_uri=str(tmp_path)) + + # create a proto dataset + proto_dataset = ProtoDataSet( + uri=dest_uri, + admin_metadata=admin_metadata, + config_path=None) + proto_dataset.create() + proto_dataset.put_readme("") + + # put two local files + handle = "sample_dataset_content.txt" + local_file_path = os.path.join(_HERE, 'data', handle) + proto_dataset.put_item(local_file_path, handle) + + second_fname = "tiny.png" + local_file_path = os.path.join(_HERE, 'data', second_fname) + proto_dataset.put_item(local_file_path, second_fname) + + proto_dataset.freeze() + + yield dest_uri + +@pytest.fixture(scope="function") +def populated_app_with_mock_data( app, mock_dataset_list, mock_manifest, mock_readme, mock_config_info): """Replaces lookup api calls with mock methods that return fake lists of datasets.""" @@ -261,13 +296,48 @@ def app_with_mock_dtool_lookup_api_calls( @pytest.fixture(scope="function") -def populated_app(app_with_mock_dtool_lookup_api_calls): - """Provides app populated with mock datasets.""" +def populated_app_with_local_dataset_data( + app, local_dataset_uri, mock_token, mock_readme, mock_config_info): + """Replaces lookup api calls with mock methods that return fake lists of datasets.""" + + import dtool_lookup_api.core.config + dtool_lookup_api.core.config.Config = MockDtoolLookupAPIConfig() + + from dtoolcore import DataSet + dataset = DataSet.from_uri(local_dataset_uri) - # TODO: figure out how to populate and refresh app with data here before app launched - # app.main_window.activate_action('refresh-view') + dataset_info = dataset._admin_metadata + # this returns a dict like + # {'uuid': 'f9904c40-aff3-43eb-b062-58919c00062a', + # 'dtoolcore_version': '3.18.2', + # 'name': 'test_dataset', + # 'type': 'dataset', + # 'creator_username': 'jotelha', + # 'created_at': 1702902057.239987, + # 'frozen_at': 1702902057.241831 + # } - yield app_with_mock_dtool_lookup_api_calls + + dataset_info["uri"] = dataset.uri + dataset_info["base_uri"] = os.path.dirname(local_dataset_uri) + + dataset_list = [dataset_info] + + manifest = dataset.generate_manifest() + + with ( + patch("dtool_lookup_api.core.LookupClient.authenticate", return_value=mock_token), + patch("dtool_lookup_api.core.LookupClient.ConfigurationBasedLookupClient.connect", return_value=None), + patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.all", return_value=dataset_list), + patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.search", return_value=dataset_list), + patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.graph", return_value=[]), + patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.manifest", return_value=manifest), + patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.readme", return_value=mock_readme), + patch("dtool_lookup_api.core.LookupClient.TokenBasedLookupClient.config", return_value=mock_config_info), + patch("dtool_lookup_api.core.LookupClient.ConfigurationBasedLookupClient.has_valid_token", return_value=True) + ): + + yield app # ================================================================ diff --git a/test/data/sample_dataset_content.txt b/test/data/sample_dataset_content.txt new file mode 100644 index 00000000..6769dd60 --- /dev/null +++ b/test/data/sample_dataset_content.txt @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/test/data/tiny.png b/test/data/tiny.png new file mode 100644 index 0000000000000000000000000000000000000000..8efbf681d47645580aa23a25765f231ef7722bc9 GIT binary patch literal 276 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$D3?#QN+_DTvv7|ftIx;Y9?C1WI2$EDt_6YK2 zV5m}KU}$JzVE6?TYIwoGP-?)y@G60U!DU$=lt9@jsL9Js^j@#M9T6{Q(cZ06TZ{wgp~5A#P6>#}JO|$q5aN3=CWhjL(1E z0@zm0Xkxq!^4049#>6jdTr7Lk!KV49u+z4YdsntPBhS{TV`0H00)| ZWTsW()}SD?<0Vi7gQu&X%Q~loCICU3Mk@dS literal 0 HcmV?d00001 diff --git a/test/test_main_window_actions.py b/test/test_main_window_actions.py index 97c2d7d9..57a3939a 100644 --- a/test/test_main_window_actions.py +++ b/test/test_main_window_actions.py @@ -25,13 +25,14 @@ import asyncio import time +import dtoolcore import pytest from unittest.mock import patch, MagicMock, AsyncMock, Mock from gi.repository import Gtk, Gio, GLib @pytest.mark.asyncio -async def test_do_search_direct_call(populated_app): +async def test_do_search_direct_call(populated_app_with_mock_data): """ Test the do_search method for directly processing a search in a populated application. This test checks if the search action correctly triggers the search process and @@ -48,10 +49,10 @@ async def wait_for_datasets_to_load(list_box, timeout=10): return False # Timeout reached if datasets are not loaded within the specified time # Trigger the 'refresh-view' action to load datasets - populated_app.main_window.activate_action('refresh-view') + populated_app_with_mock_data.main_window.activate_action('refresh-view') # Wait until datasets are loaded in the dataset list box - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + datasets_loaded = await wait_for_datasets_to_load(populated_app_with_mock_data.main_window.dataset_list_box) assert datasets_loaded, "Datasets were not loaded in time" # Create a GLib.Variant with a test search query @@ -60,10 +61,10 @@ async def wait_for_datasets_to_load(list_box, timeout=10): # Create and add the search action to the main window of the application search_action = Gio.SimpleAction.new("search", search_text_variant.get_type()) - populated_app.main_window.add_action(search_action) + populated_app_with_mock_data.main_window.add_action(search_action) # Connect the search action to the do_search method of the main window - search_action.connect("activate", populated_app.main_window.do_search) + search_action.connect("activate", populated_app_with_mock_data.main_window.do_search) # Trigger the search action with the test search query search_action.activate(search_text_variant) @@ -73,7 +74,7 @@ async def wait_for_datasets_to_load(list_box, timeout=10): await asyncio.sleep(1) # Adjust this sleep duration as needed # Perform assertions to verify that the search results are correctly displayed - assert len(populated_app.main_window.base_uri_list_box.get_children()) > 0, "No search results found" + assert len(populated_app_with_mock_data.main_window.base_uri_list_box.get_children()) > 0, "No search results found" # Additional assertions can be added here based on the expected outcomes of the search # Await completion of any remaining asynchronous tasks related to the search @@ -111,7 +112,7 @@ async def test_do_search_action_trigger(app): @pytest.mark.asyncio -async def test_do_select_dataset_row_by_row_index_direct_call(populated_app): +async def test_do_select_dataset_row_by_row_index_direct_call(populated_app_with_mock_data): """ Test the do_select_dataset_row_by_row_index method for directly selecting a dataset row by index. It verifies if the dataset list is correctly populated and the specified dataset row is selected. @@ -126,8 +127,8 @@ async def wait_for_datasets_to_load(list_box, timeout=10): return False # Trigger the 'refresh-view' action and wait for datasets to load - populated_app.main_window.activate_action('refresh-view') - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + populated_app_with_mock_data.main_window.activate_action('refresh-view') + datasets_loaded = await wait_for_datasets_to_load(populated_app_with_mock_data.main_window.dataset_list_box) assert datasets_loaded, "Datasets were not loaded in time" # Create a GLib.Variant with a valid test row index (e.g., 0 for the first row) @@ -136,10 +137,10 @@ async def wait_for_datasets_to_load(list_box, timeout=10): # Create and add the select dataset action to the main window of the application select_dataset_action = Gio.SimpleAction.new("select-dataset", row_index_variant.get_type()) - populated_app.main_window.add_action(select_dataset_action) + populated_app_with_mock_data.main_window.add_action(select_dataset_action) # Connect the select dataset action to the do_select_dataset_row_by_row_index method - select_dataset_action.connect("activate", populated_app.main_window.do_select_dataset_row_by_row_index) + select_dataset_action.connect("activate", populated_app_with_mock_data.main_window.do_select_dataset_row_by_row_index) # Trigger the select dataset action with the test row index select_dataset_action.activate(row_index_variant) @@ -148,7 +149,7 @@ async def wait_for_datasets_to_load(list_box, timeout=10): await asyncio.sleep(0.1) # Adjust this sleep duration as needed # Perform assertions to verify that the correct dataset row is selected - selected_row = populated_app.main_window.dataset_list_box.get_selected_row() + selected_row = populated_app_with_mock_data.main_window.dataset_list_box.get_selected_row() # Assert that the selected row is not None and has the expected index assert selected_row is not None, "No dataset row was selected" @@ -190,7 +191,7 @@ async def test_do_select_dataset_row_by_row_index_action_trigger(app): @pytest.mark.asyncio -async def test_do_show_dataset_details_by_uri_direct_call(populated_app): +async def test_do_show_dataset_details_by_uri_direct_call(populated_app_with_mock_data): """ Test the do_show_dataset_details_by_uri method for processing a URI directly. It verifies if the correct dataset is selected and its content is displayed based on the URI. @@ -205,10 +206,10 @@ async def wait_for_datasets_to_load(list_box, timeout=10): return False # Timeout reached # Trigger the 'refresh-view' action - populated_app.main_window.activate_action('refresh-view') + populated_app_with_mock_data.main_window.activate_action('refresh-view') # Wait until datasets are loaded - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + datasets_loaded = await wait_for_datasets_to_load(populated_app_with_mock_data.main_window.dataset_list_box) assert datasets_loaded, "Datasets were not loaded in time" # Define a URI that corresponds to a dataset in the populated dataset list @@ -218,11 +219,11 @@ async def wait_for_datasets_to_load(list_box, timeout=10): uri_variant = GLib.Variant.new_string(test_uri) # Call the do_show_dataset_details_by_uri method with the test URI - populated_app.main_window.do_show_dataset_details_by_uri(None, uri_variant) + populated_app_with_mock_data.main_window.do_show_dataset_details_by_uri(None, uri_variant) # Retrieve the dataset that should be selected based on the URI - index = populated_app.main_window.dataset_list_box.get_row_index_from_uri(test_uri) - selected_row = populated_app.main_window.dataset_list_box.get_row_at_index(index) + index = populated_app_with_mock_data.main_window.dataset_list_box.get_row_index_from_uri(test_uri) + selected_row = populated_app_with_mock_data.main_window.dataset_list_box.get_row_at_index(index) selected_dataset = selected_row.dataset if selected_row is not None else None # Assert that the dataset with the given URI is selected @@ -265,7 +266,7 @@ async def test_do_show_dataset_details_by_uri_action_trigger(app): @pytest.mark.asyncio -async def test_do_show_dataset_details_by_row_index_direct_call(populated_app, mock_dataset_list): +async def test_do_show_dataset_details_by_row_index_direct_call(populated_app_with_mock_data, mock_dataset_list): """ Test the do_show_dataset_details_by_row_index method for directly showing dataset details by row index. It verifies if the dataset list is correctly populated and the specified dataset details are displayed. @@ -280,8 +281,8 @@ async def wait_for_datasets_to_load(list_box, timeout=10): return False # Timeout reached if datasets are not loaded within the specified time # Trigger the 'refresh-view' action and wait for datasets to load - populated_app.main_window.activate_action('refresh-view') - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + populated_app_with_mock_data.main_window.activate_action('refresh-view') + datasets_loaded = await wait_for_datasets_to_load(populated_app_with_mock_data.main_window.dataset_list_box) assert datasets_loaded, "Datasets were not loaded in time" # Create a GLib.Variant with a test row index (e.g., 0 for the first row) @@ -290,10 +291,10 @@ async def wait_for_datasets_to_load(list_box, timeout=10): # Create and add the show dataset action to the main window of the application show_dataset_action = Gio.SimpleAction.new("show-dataset", row_index_variant.get_type()) - populated_app.main_window.add_action(show_dataset_action) + populated_app_with_mock_data.main_window.add_action(show_dataset_action) # Connect the show dataset action to the do_show_dataset_details_by_row_index method - show_dataset_action.connect("activate", populated_app.main_window.do_show_dataset_details_by_row_index) + show_dataset_action.connect("activate", populated_app_with_mock_data.main_window.do_show_dataset_details_by_row_index) # Trigger the show dataset action with the test row index show_dataset_action.activate(row_index_variant) @@ -302,7 +303,7 @@ async def wait_for_datasets_to_load(list_box, timeout=10): await asyncio.sleep(0.1) # Adjust this sleep duration as needed # Perform assertions to verify that the dataset details are correctly displayed - selected_dataset = populated_app.main_window.dataset_list_box.get_row_at_index(row_index).dataset + selected_dataset = populated_app_with_mock_data.main_window.dataset_list_box.get_row_at_index(row_index).dataset expected_uri = mock_dataset_list[1]['uri'] expected_uuid = mock_dataset_list[1]['uuid'] @@ -348,7 +349,7 @@ async def test_do_show_dataset_details_by_row_index_action_trigger(app): @pytest.mark.asyncio -async def test_do_search_select_and_show_direct_call(populated_app, mock_dataset_list): +async def test_do_search_select_and_show_direct_call(populated_app_with_mock_data, mock_dataset_list): """ Test the do_search_select_and_show method for processing a search directly. It verifies if the dataset list is correctly populated, the first dataset is selected, and its @@ -364,21 +365,21 @@ async def wait_for_datasets_to_load(list_box, timeout=10): return False # Timeout reached # Trigger the 'refresh-view' action - populated_app.main_window.activate_action('refresh-view') + populated_app_with_mock_data.main_window.activate_action('refresh-view') # Wait until datasets are loaded - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + datasets_loaded = await wait_for_datasets_to_load(populated_app_with_mock_data.main_window.dataset_list_box) assert datasets_loaded, "Datasets were not loaded in time" # Create a GLib.Variant with the test search query mock_variant = GLib.Variant.new_string("test_search_query") # Call the method with the test search query - populated_app.main_window.do_search_select_and_show(None, mock_variant) + populated_app_with_mock_data.main_window.do_search_select_and_show(None, mock_variant) # Assertions to check the state of the application after the search - assert len(populated_app.main_window.dataset_list_box.get_children()) > 0, "No datasets found in the list box" - first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] + assert len(populated_app_with_mock_data.main_window.dataset_list_box.get_children()) > 0, "No datasets found in the list box" + first_dataset_row = populated_app_with_mock_data.main_window.dataset_list_box.get_children()[0] dataset = first_dataset_row.dataset # Assert that the dataset's URI matches the expected URI @@ -420,8 +421,8 @@ async def test_do_search_select_and_show_action_trigger(app): @pytest.mark.asyncio -@pytest.mark.skip(reason="no way of currently testing this") -async def test_do_get_item_direct_call(populated_app, tmp_path): +# @pytest.mark.skip(reason="no way of currently testing this") +async def test_do_get_item_direct_call(populated_app_with_local_dataset_data, local_dataset_uri, tmp_path): """ Test the do_get_item method for copying a selected item directly. It verifies if the selected item is correctly copied to the specified destination. @@ -436,19 +437,31 @@ async def wait_for_datasets_to_load(list_box, timeout=10): return False # Timeout reached # Trigger the 'refresh-view' action to load datasets - populated_app.main_window.activate_action('refresh-view') + populated_app_with_local_dataset_data.main_window.activate_action('refresh-view') # Wait until datasets are loaded - datasets_loaded = await wait_for_datasets_to_load(populated_app.main_window.dataset_list_box) + datasets_loaded = await wait_for_datasets_to_load(populated_app_with_local_dataset_data.main_window.dataset_list_box) assert datasets_loaded, "Datasets were not loaded in time" # Select the first dataset - first_dataset_row = populated_app.main_window.dataset_list_box.get_children()[0] - dataset = first_dataset_row.dataset + # first_dataset_row = app_with_mock_dtool_lookup_api_calls_on_local_dataset.main_window.dataset_list_box.get_children()[0] + # dataset = first_dataset_row.dataset # Assuming the first dataset's UUID as the item UUID (Replace this with the actual item UUID logic) - item_uuid = dataset.uuid - populated_app.main_window._get_selected_items = lambda: [('item_name', item_uuid)] + + def get_item_uuid(dataset, relpath): + for identifier, properties in dataset.generate_manifest()["items"].items(): + if properties["relpath"] == relpath: + return identifier + return None + + dataset = dtoolcore.DataSet.from_uri(local_dataset_uri) + print(dataset.generate_manifest()) + + item_uuid = get_item_uuid(dataset, "tiny.png") + populated_app_with_local_dataset_data.main_window._get_selected_items = lambda: [('item_name', item_uuid)] + + # TODO: assert file size and file content agree with "tiny.png" in data folder # Define the destination file path dest_file = tmp_path / "destination_file" @@ -461,7 +474,7 @@ async def wait_for_datasets_to_load(list_box, timeout=10): # Call the do_get_item method without 'await' try: - populated_app.main_window.do_get_item(None, dest_file_variant) + populated_app_with_local_dataset_data.main_window.do_get_item(None, dest_file_variant) except Exception as e: print(f"Error during 'do_get_item': {e}") raise From d0e10037007e8345bcaa3fb668f2b21a37303497 Mon Sep 17 00:00:00 2001 From: ashdriod Date: Wed, 20 Dec 2023 17:50:40 +0100 Subject: [PATCH 10/11] TST:WIP --- test/test_main_window_actions.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/test_main_window_actions.py b/test/test_main_window_actions.py index 57a3939a..84b74be8 100644 --- a/test/test_main_window_actions.py +++ b/test/test_main_window_actions.py @@ -24,6 +24,7 @@ # import asyncio import time +from pathlib import Path import dtoolcore import pytest @@ -493,6 +494,19 @@ def get_item_uuid(dataset, relpath): print(f"File exists: {file_exists}") assert file_exists, "The item was not copied to the specified destination" + # Assert File Size + original_file = Path('test/data/tiny.png') # Path to the original file + assert dest_file.stat().st_size == original_file.stat().st_size, "Downloaded file size does not match the original file size" + + # Assert File Content + with open(dest_file, 'rb') as f_downloaded, open(original_file, 'rb') as f_original: + assert f_downloaded.read() == f_original.read(), "Downloaded file content does not match the original file content" + + # Assert Modification Time + current_time = time.time() + assert dest_file.stat().st_mtime <= current_time, "Downloaded file modification time is in the future" + + # Optional: Assert File Permissions @pytest.mark.asyncio async def test_do_get_item_action_trigger(app): From fabe7c571bd4bd5842457b7d57c9fd77337c96b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20L=2E=20H=C3=B6rmann?= Date: Mon, 5 Feb 2024 13:33:47 +0100 Subject: [PATCH 11/11] Update conftest.py --- test/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/conftest.py b/test/conftest.py index 760c30b2..b620fd06 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -23,6 +23,7 @@ # import json import logging +import os import pytest import gi @@ -341,4 +342,4 @@ def populated_app_with_local_dataset_data( @pytest.fixture(scope="function") def event_loop(gtk_loop, app): gtk_loop.set_application(app) - yield gtk_loop \ No newline at end of file + yield gtk_loop