diff --git a/tests/cmd_app/general/__init__.py b/tests/cmd_app/general/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/cmd_app/general/test_project.py b/tests/cmd_app/general/test_project.py new file mode 100644 index 00000000..6d5cca8a --- /dev/null +++ b/tests/cmd_app/general/test_project.py @@ -0,0 +1,130 @@ +""" +Unit tests for `geoprob_pipe.cmd_app.general.project.py`. +Tests performed for: + - created_project(app_settings: ApplicationSettings) -> bool +""" + +from unittest.mock import Mock + +import pytest + +import geoprob_pipe.cmd_app.general.project as module + + +# --- created_project(app_settings: ApplicationSettings) -> bool --- +@pytest.mark.parametrize( + "choice,function_name", + [ + ( + "Bestaand project openen", + "specify_path_to_existing_project", + ), + ( + "Nieuw project starten", + "specify_dir_for_new_project", + ), + ], +) +def test_created_project_happy_paths( + app_settings, + monkeypatch, + choice: str, + function_name: str, +) -> None: + """Test of the paths are correctly completed.""" + # Arrange + # Mock assigned function calls + select_mock = Mock() + select_mock.return_value.execute.return_value = choice + + monkeypatch.setattr("InquirerPy.inquirer.select", select_mock) + + # Mock called functions + action_mock = Mock() + monkeypatch.setattr( + module, + function_name, + action_mock, + ) + logging_mock = Mock() + monkeypatch.setattr( + module, + "enable_geopackage_logging", + logging_mock, + ) + + # Act + result: bool = module.created_project(app_settings) + + # Assert + # Check path reaches return + assert result is True + + # Check functions were called once with correct arguments + action_mock.assert_called_once_with(app_settings) + + logging_mock.assert_called_once_with(app_settings=app_settings) + + +def test_created_project_compare(monkeypatch) -> None: + """Test start compare function.""" + # Arrange + # Mock assigned function calls + prompt_mock = Mock() + choice: str = "Twee projectbestanden vergelijken" + prompt_mock.execute.return_value = choice + monkeypatch.setattr("InquirerPy.inquirer.select", Mock(return_value=prompt_mock)) + + # Mock called functions + compare_mock = Mock() + monkeypatch.setattr(module, "start_comparison", compare_mock) + + # Act + # While capturing the sysexit() run the function + with pytest.raises(SystemExit, match="Applicatie afgesloten"): + module.created_project(Mock()) + + # Assert + # Check function call + compare_mock.assert_called_once() + + +def test_created_project_single_calc(monkeypatch) -> None: + # Arrange + # Mock assigned function calls + prompt_mock = Mock() + choice: str = "Inspecteer een enkele berekening" + prompt_mock.execute.return_value = choice + monkeypatch.setattr("InquirerPy.inquirer.select", Mock(return_value=prompt_mock)) + + # Mock called functions + panel_instance = Mock() + panel_mock = Mock(return_value=panel_instance) + monkeypatch.setattr( + module, + "Panel", + panel_mock, + ) + console_instance = Mock() + console_mock = Mock(return_value=console_instance) + monkeypatch.setattr( + module, + "Console", + console_mock, + ) + # Act + # While capturing the sysexit() run the function + with pytest.raises(SystemExit, match="Applicatie afgesloten"): + module.created_project(Mock()) + + # Assert + # Check Panel construction + panel_mock.assert_called_once_with( + module.EXPLANATION_REPRODUCING_SINGLE_CALCULATION, + title="INSPECTEER EEN ENKELE BEREKENING", + title_align="left", + border_style="bright_blue", + padding=(0, 2), + ) + # Check print call + console_instance.print.assert_called_once_with(panel_instance) diff --git a/tests/cmd_app/general/test_traject_parameters.py b/tests/cmd_app/general/test_traject_parameters.py new file mode 100644 index 00000000..45bbac99 --- /dev/null +++ b/tests/cmd_app/general/test_traject_parameters.py @@ -0,0 +1,122 @@ +""" +Unit tests for `geoprob_pipe.cmd_app.general.traject_parameters.py`. +Tests performed for: + - _specify_w(app_settings: ApplicationSettings) +""" + +from unittest.mock import Mock + +import pytest + +import geoprob_pipe.cmd_app.general.traject_parameters as module + + +# --- _specify_w(app_settings: ApplicationSettings) --- +@pytest.mark.parametrize( + "input_value, expected_answer", + [ + ("", "geen w gespecificeerd"), + ("abc", "geen decimaal getal"), + ("1.5", "groter dan 1.0"), + ("0", "kleiner of gelijk aan 0.0"), + ("-1", "kleiner of gelijk aan 0.0"), + ], +) +def test_specify_w_validations( + app_settings, + monkeypatch, + capsys, + input_value: str, + expected_answer: str, +) -> None: + """Test for all branches of validation and correct storage of user input.""" + # Arrange + # Setup user inputs + user_inputs = iter([input_value, "0.24"]) + + # Mock assigned function calls + prompt_mock = Mock() + prompt_mock.execute.side_effect = lambda: next(user_inputs) + + monkeypatch.setattr( + "InquirerPy.inquirer.text", + Mock(return_value=prompt_mock), + ) + + # Mock called functions + append_mock = Mock() + monkeypatch.setattr( + module, + "_append_to_db", + append_mock, + ) + + # Act + # Run tested function: + module._specify_w(app_settings) + + # Assert + # Check correct message printed + captured = capsys.readouterr() + assert expected_answer in captured.out + + # Check continue loop + assert prompt_mock.execute.call_count == 2 + + # Check accepted value stored correctly + append_mock.assert_called_once_with( + app_settings=app_settings, + key="w", + value=0.24, # as a float + ) + + +@pytest.mark.parametrize("input_value", ["0.01", "1.0"]) +def test_specify_w_valid_boundaries(app_settings, monkeypatch, input_value: str) -> None: + """Test that the valid boundaries are accepted.""" + # Arrange + prompt_mock = Mock() + prompt_mock.execute.return_value = input_value + monkeypatch.setattr( + "InquirerPy.inquirer.text", + Mock(return_value=prompt_mock), + ) + + append_mock = Mock() + monkeypatch.setattr(module, "_append_to_db", append_mock) + + # Act + module._specify_w(app_settings) + + # Assert + prompt_mock.execute.assert_called_once() + append_mock.assert_called_once_with( + app_settings=app_settings, + key="w", + value=float(input_value), + ) + + +def test_specify_w_strips_surrounding_spaces(app_settings, monkeypatch) -> None: + """Test that surrounding spaces are removed before validation.""" + # Arrange + prompt_mock = Mock() + prompt_mock.execute.return_value = " 0.24 " + monkeypatch.setattr( + "InquirerPy.inquirer.text", + Mock(return_value=prompt_mock), + ) + + append_mock = Mock() + monkeypatch.setattr(module, "_append_to_db", append_mock) + + # Act + module._specify_w(app_settings) + + # Assert + prompt_mock.execute.assert_called_once() + append_mock.assert_called_once_with( + app_settings=app_settings, + key="w", + value=0.24, + ) diff --git a/tests/cmd_app/spatial_layers/test_binnenteenlijn.py b/tests/cmd_app/spatial_layers/test_binnenteenlijn.py new file mode 100644 index 00000000..63344032 --- /dev/null +++ b/tests/cmd_app/spatial_layers/test_binnenteenlijn.py @@ -0,0 +1,92 @@ +""" +Unit tests for `geoprob_pipe.cmd_app.spatial_layers.binnenteenlijn.py`. +Tests performed for: + - added_binnenteenlijn(app_settings: ApplicationSettings) -> bool +""" + +from unittest.mock import Mock + +import geoprob_pipe.cmd_app.spatial_layers.binnenteenlijn as module + + +# --- added_binnenteenlijn(app_settings: ApplicationSettings) -> bool --- +def test_added_binnenteenlijn_already_included( + app_settings, + monkeypatch, + capsys, +) -> None: + """Test case `binnenteenlijn` already added to gpkg.""" + # Arrange + # Monkey patch assigned function calls + monkeypatch.setattr( + module.fiona, + "listlayers", + Mock( + return_value=[ + "trajectlijn", + "binnenteenlijn", + "vakindeling", + ] + ), + ) + + # Mock called module fuctions + request_mock = Mock() + monkeypatch.setattr( + module, + "request_binnenteenlijn_filepath", + request_mock, + ) + + # Act + # Run tested function: + result: bool = module.added_binnenteenlijn(app_settings) + + # Assert + # Check expected return + assert result is True + + # Check correct message printed + captured = capsys.readouterr() + assert "Binnenteenlijn al toegevoegd" in captured.out + + # Check function in other branch not called + request_mock.assert_not_called() + + +def test_added_binnenteenlijn_not_yet_included( + app_settings, + monkeypatch, +) -> None: + """Test case `binnenteenlijn` not yet added to gpkg.""" + # Arrange + # Monkey patch assigned function call(s) + monkeypatch.setattr( + module.fiona, + "listlayers", + Mock( + return_value=[ + "trajectlijn", + "vakindeling", + ] + ), + ) + + # Mock called module fuction(s) + request_mock = Mock() + monkeypatch.setattr( + module, + "request_binnenteenlijn_filepath", + request_mock, + ) + + # Act + # Run tested function: + result: bool = module.added_binnenteenlijn(app_settings) + + # Assert + # Check expected return + assert result is True + + # Check function call with correct argument + request_mock.assert_called_once_with(app_settings=app_settings) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..37970c6b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,13 @@ +import pytest + +from geoprob_pipe.cmd_app.cmd import ApplicationSettings + + +@pytest.fixture +def app_settings(tmp_path) -> ApplicationSettings: + settings = ApplicationSettings() + + settings.workspace_dir = str(tmp_path) + settings.geopackage_filename = "dummy.gpkg" + + return settings \ No newline at end of file diff --git a/tests/results/__init__.py b/tests/results/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/results/assemblage/__init__.py b/tests/results/assemblage/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/results/assemblage/test_assemblage.py b/tests/results/assemblage/test_assemblage.py deleted file mode 100644 index 976378b1..00000000 --- a/tests/results/assemblage/test_assemblage.py +++ /dev/null @@ -1,59 +0,0 @@ -from geoprob_pipe.results.assemblage.functions import combine_series -from geoprob_pipe.results.assemblage.functions import window_collect -from geoprob_pipe.results.assemblage.functions import scaled_collect -from geoprob_pipe.results.assemblage.objects import UittredepuntElement -import pytest - - -def test_combine(): - """Test combining very small floats. - """ - pfs = [1.123e-17, 3.78e-15, 6.7e-15] - sum_pf, max_pf = combine_series(pfs) - assert sum_pf == pytest.approx(1.049123e-14) - assert max_pf == pytest.approx(6.7e-15) - - -def test_window(): - """Test window on selecting max in window and combining. - """ - list_dsn = [ - UittredepuntElement(m_value=11, a=0.9, converged=True, pf=1e-12, - flow_chart_number=11, advise="-"), - UittredepuntElement(m_value=12, a=0.9, converged=True, pf=2e-13, - flow_chart_number=11, advise="-"), - UittredepuntElement(m_value=20, a=0.9, converged=True, pf=7.5e-13, - flow_chart_number=11, advise="-"), - UittredepuntElement(m_value=30, a=0.9, converged=True, pf=3e-14, - flow_chart_number=11, advise="-") - ] - sum_pf, max_pf, elements = window_collect( - window_size=10, point_list=list_dsn, - m_van=0, m_tot=40 - ) - assert sum_pf == pytest.approx(1.78e-12) - assert max_pf == pytest.approx(1e-12) - assert elements.__len__() == 4 - assert elements[0].kans_dsn.pf == 0.0 - - -def test_scaled(): - """Test scaled on taking max in cluster and combining. - """ - list_dsn = [ - UittredepuntElement(m_value=11, a=0.9, converged=True, pf=1e-12, - flow_chart_number=11, advise="-"), - UittredepuntElement(m_value=12, a=0.9, converged=True, pf=2e-13, - flow_chart_number=11, advise="-"), - UittredepuntElement(m_value=20, a=0.9, converged=True, pf=7.5e-13, - flow_chart_number=11, advise="-"), - UittredepuntElement(m_value=30, a=0.9, converged=True, pf=3e-14, - flow_chart_number=11, advise="-") - ] - sum_pf, max_pf, elements = scaled_collect( - dL=200, point_list=list_dsn, - m_van=0, m_tot=50 - ) - assert sum_pf == pytest.approx(1.78e-12) - assert max_pf == pytest.approx(1e-12) - assert elements.__len__() == 3 diff --git a/tests/results/assemblage/test_functions_assemblage.py b/tests/results/assemblage/test_functions_assemblage.py new file mode 100644 index 00000000..20f347b3 --- /dev/null +++ b/tests/results/assemblage/test_functions_assemblage.py @@ -0,0 +1,197 @@ +""" +Unit tests for `assemblage.functions` and `assemblage.objects` in `geoprob_pipe.results.assemblage.functions`. +""" + +import pytest + +import geoprob_pipe.results.assemblage.functions as functions +import geoprob_pipe.results.assemblage.objects as objects + + +@pytest.fixture +def uittredepunten() -> list[objects.UittredepuntElement]: + return [ + objects.UittredepuntElement( + m_value=11, + a=0.9, + converged=True, + pf=1e-12, + flow_chart_number=11, + advise="-", + ), + objects.UittredepuntElement( + m_value=12, + a=0.9, + converged=True, + pf=2e-13, + flow_chart_number=11, + advise="-", + ), + objects.UittredepuntElement( + m_value=20, + a=0.9, + converged=True, + pf=7.5e-13, + flow_chart_number=11, + advise="-", + ), + objects.UittredepuntElement( + m_value=30, + a=0.9, + converged=True, + pf=3e-14, + flow_chart_number=11, + advise="-", + ), + ] + + +def test_combine_series_with_small_probabilities() -> None: + """Check addition of very small probabilities""" + + # Arrange + pfs: list[float] = [1.123e-17, 3.78e-15, 6.7e-15] + + # Act + sum_pf: float + max_pf: float + sum_pf, max_pf = functions.combine_series(pfs) + + # Assert + assert sum_pf == pytest.approx(1.049123e-14) + assert max_pf == pytest.approx(6.7e-15) + + +def test_combine_series_empty_list() -> None: + """Assert correct return on input of empty list.""" + # Arrange + pfs: list[float] = [] + + # Act + sum_pf: float + max_pf: float + sum_pf, max_pf = functions.combine_series(pfs) + + # Assert + assert sum_pf == 0.0 + assert max_pf == 0.0 + + +@pytest.mark.parametrize( + "L, a, dL, expected", + [ + (100.0, 0.5, 20.0, 2.5), # larger than 1 + (10.0, 0.5, 20.0, 1.0), # minimum = 1 + (20.0, 1.0, 20.0, 1.0), # exact 1 + (200.0, 0.8, 40.0, 4.0), # normal case + ], +) +def test_bepaal_N_vak(L: float, a: float, dL: float, expected: float) -> None: + """Controleer correcte calculation of N_vak.""" + result = functions.bepaal_N_vak(L=L, a=a, dL=dL) + + assert result == pytest.approx(expected) + + +@pytest.mark.parametrize( + "a", + [-0.1, -1.0, -100.0], +) +def test_bepaal_N_vak_raises_for_negative_a(a: float) -> None: + """Assert that a negative value of a raise a ValueError""" + with pytest.raises(ValueError, match="a moet groter zijn dan 0"): + functions.bepaal_N_vak(L=100.0, a=a, dL=20.0) + + +@pytest.mark.parametrize( + "L, dL", + [ + (-1.0, 20.0), + (100.0, -20.0), + (-1.0, -20.0), + ], +) +def test_bepaal_N_vak_raises_for_negative_lengths(L: float, dL: float) -> None: + """Assert that negative values for L and dL raise a ValueError.""" + with pytest.raises( + ValueError, + match="De lengte L en dL moeten groter zijn dan 0", + ): + functions.bepaal_N_vak(L=L, a=0.5, dL=dL) + + +def test_window_collect_returns_expected_values(uittredepunten) -> None: + """Check selection inside windows and combined probability.""" + + # Act + sum_pf: float + max_pf: float + elements: list[objects.WindowElement] + sum_pf, max_pf, elements = functions.window_collect( + window_size=10, + point_list=uittredepunten, + m_van=0, + m_tot=40, + ) + + # Assert + assert sum_pf == pytest.approx(1.78e-12) + assert max_pf == pytest.approx(1e-12) + assert len(elements) == 4 + assert elements[0].kans_dsn.pf == pytest.approx(0.0) + + +def test_window_collect_empty_list() -> None: + # Act + sum_pf: float + max_pf: float + elements: list[objects.WindowElement] + sum_pf, max_pf, elements = functions.window_collect( + window_size=10, + point_list=[], + m_van=0, + m_tot=50, + ) + + # Assert + assert sum_pf == 0.0 + assert max_pf == 0.0 + assert len(elements) == 0 + + +def test_scaled_collect_returns_expected_values(uittredepunten) -> None: + """Check clustering, scale factor en combined probability.""" + + # Act + sum_pf: float + max_pf: float + elements: list[objects.WindowElement] + sum_pf, max_pf, elements = functions.scaled_collect( + dL=200, + point_list=uittredepunten, + m_van=0, + m_tot=50, + ) + + # Assert + assert sum_pf == pytest.approx(1.78e-12) + assert max_pf == pytest.approx(1e-12) + assert len(elements) == 3 + + +def test_scaled_collect_empty_list() -> None: + # Act + sum_pf: float + max_pf: float + elements: list[objects.WindowElement] + sum_pf, max_pf, elements = functions.scaled_collect( + dL=200, + point_list=[], + m_van=0, + m_tot=50, + ) + + # Assert + assert sum_pf == 0.0 + assert max_pf == 0.0 + assert len(elements) == 0 \ No newline at end of file