diff --git a/geest/core/workflows/acled_impact_workflow.py b/geest/core/workflows/acled_impact_workflow.py index 640b24a..0f1d97a 100644 --- a/geest/core/workflows/acled_impact_workflow.py +++ b/geest/core/workflows/acled_impact_workflow.py @@ -33,15 +33,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.csv_file = self.attributes.get("use_csv_to_point_layer_csv_file", "") if not self.csv_file: diff --git a/geest/core/workflows/aggregation_workflow_base.py b/geest/core/workflows/aggregation_workflow_base.py index 56faef4..eae5083 100644 --- a/geest/core/workflows/aggregation_workflow_base.py +++ b/geest/core/workflows/aggregation_workflow_base.py @@ -23,16 +23,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.guids = None # This should be set by the child class - a list of guids of JSONTreeItems to aggregate self.id = None # This should be set by the child class diff --git a/geest/core/workflows/analysis_aggregation_workflow.py b/geest/core/workflows/analysis_aggregation_workflow.py index 34a6c99..57c27d7 100644 --- a/geest/core/workflows/analysis_aggregation_workflow.py +++ b/geest/core/workflows/analysis_aggregation_workflow.py @@ -19,16 +19,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.guids = ( self.item.getAnalysisDimensionGuids() diff --git a/geest/core/workflows/classified_polygon_workflow.py b/geest/core/workflows/classified_polygon_workflow.py index b472b86..a3d3a11 100644 --- a/geest/core/workflows/classified_polygon_workflow.py +++ b/geest/core/workflows/classified_polygon_workflow.py @@ -25,16 +25,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. - :param context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.workflow_name = "use_classify_polygon_into_classes" layer_path = self.attributes.get( diff --git a/geest/core/workflows/dimension_aggregation_workflow.py b/geest/core/workflows/dimension_aggregation_workflow.py index 20d4af1..d5ccecf 100644 --- a/geest/core/workflows/dimension_aggregation_workflow.py +++ b/geest/core/workflows/dimension_aggregation_workflow.py @@ -20,16 +20,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.guids = ( self.item.getDimensionFactorGuids() diff --git a/geest/core/workflows/dont_use_workflow.py b/geest/core/workflows/dont_use_workflow.py index 6619e53..fdcd7ed 100644 --- a/geest/core/workflows/dont_use_workflow.py +++ b/geest/core/workflows/dont_use_workflow.py @@ -20,16 +20,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.workflow_name = "Do Not Use" self.attributes["result_file"] = "" diff --git a/geest/core/workflows/factor_aggregation_workflow.py b/geest/core/workflows/factor_aggregation_workflow.py index 79dccb2..3987223 100644 --- a/geest/core/workflows/factor_aggregation_workflow.py +++ b/geest/core/workflows/factor_aggregation_workflow.py @@ -14,19 +14,21 @@ class FactorAggregationWorkflow(AggregationWorkflowBase): def __init__( self, - item: dict, + item: JsonTreeItem, cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.guids = ( diff --git a/geest/core/workflows/index_score_workflow.py b/geest/core/workflows/index_score_workflow.py index a3df85b..39063a5 100644 --- a/geest/core/workflows/index_score_workflow.py +++ b/geest/core/workflows/index_score_workflow.py @@ -28,16 +28,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.index_score = float((self.attributes.get("index_score", 0) / 100) * 5) self.features_layer = True # Normally we would set this to a QgsVectorLayer but in this workflow it is not needed diff --git a/geest/core/workflows/multi_buffer_distances_workflow.py b/geest/core/workflows/multi_buffer_distances_workflow.py index 1d09be8..8f2a6f4 100644 --- a/geest/core/workflows/multi_buffer_distances_workflow.py +++ b/geest/core/workflows/multi_buffer_distances_workflow.py @@ -46,16 +46,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param: item: Item containing workflow parameters. - :cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.workflow_name = "use_multi_buffer_point" self.distances = self.attributes.get("multi_buffer_travel_distances", None) diff --git a/geest/core/workflows/point_per_cell_workflow.py b/geest/core/workflows/point_per_cell_workflow.py index 8f9dbdf..0b2fdf4 100644 --- a/geest/core/workflows/point_per_cell_workflow.py +++ b/geest/core/workflows/point_per_cell_workflow.py @@ -26,16 +26,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.workflow_name = "use_point_per_cell" layer_path = self.attributes.get("point_per_cell_shapefile", None) diff --git a/geest/core/workflows/polygon_per_cell_workflow.py b/geest/core/workflows/polygon_per_cell_workflow.py index 226190e..2ee140a 100644 --- a/geest/core/workflows/polygon_per_cell_workflow.py +++ b/geest/core/workflows/polygon_per_cell_workflow.py @@ -25,16 +25,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree # TODO fix inconsistent abbreviation below for Poly self.workflow_name = "use_polygon_per_cell" diff --git a/geest/core/workflows/polyline_per_cell_workflow.py b/geest/core/workflows/polyline_per_cell_workflow.py index b5da8ed..cae3c4a 100644 --- a/geest/core/workflows/polyline_per_cell_workflow.py +++ b/geest/core/workflows/polyline_per_cell_workflow.py @@ -26,16 +26,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.workflow_name = "use_polyline_per_cell" diff --git a/geest/core/workflows/raster_reclassification_workflow.py b/geest/core/workflows/raster_reclassification_workflow.py index 9e7389e..b1fdf07 100644 --- a/geest/core/workflows/raster_reclassification_workflow.py +++ b/geest/core/workflows/raster_reclassification_workflow.py @@ -27,16 +27,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. - :param context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.workflow_name = "use_environmental_hazards" diff --git a/geest/core/workflows/safety_polygon_workflow.py b/geest/core/workflows/safety_polygon_workflow.py index e298a14..acdeb70 100644 --- a/geest/core/workflows/safety_polygon_workflow.py +++ b/geest/core/workflows/safety_polygon_workflow.py @@ -25,16 +25,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. - :param context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.workflow_name = "use_classify_safety_polygon_into_classes" layer_path = self.attributes.get( diff --git a/geest/core/workflows/safety_raster_workflow.py b/geest/core/workflows/safety_raster_workflow.py index 500e70f..161dbe0 100644 --- a/geest/core/workflows/safety_raster_workflow.py +++ b/geest/core/workflows/safety_raster_workflow.py @@ -26,14 +26,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. + :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.workflow_name = "use_nighttime_lights" layer_name = self.attributes.get("nighttime_lights_raster", None) diff --git a/geest/core/workflows/single_point_buffer_workflow.py b/geest/core/workflows/single_point_buffer_workflow.py index 8aee5f1..c869e4f 100644 --- a/geest/core/workflows/single_point_buffer_workflow.py +++ b/geest/core/workflows/single_point_buffer_workflow.py @@ -27,16 +27,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.workflow_name = "use_single_buffer_point" diff --git a/geest/core/workflows/street_lights_buffer_workflow.py b/geest/core/workflows/street_lights_buffer_workflow.py index 2424bf8..5eac572 100644 --- a/geest/core/workflows/street_lights_buffer_workflow.py +++ b/geest/core/workflows/street_lights_buffer_workflow.py @@ -28,16 +28,17 @@ def __init__( cell_size_m: float, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. - :param item: Item containing workflow parameters. - :param cell_size_m: Cell size in meters. + :param attributes: Item containing workflow parameters. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__( - item, cell_size_m, feedback, context + item, cell_size_m, feedback, context, working_directory ) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree self.workflow_name = "use_street_lights" diff --git a/geest/core/workflows/workflow_base.py b/geest/core/workflows/workflow_base.py index 5d633bf..54d5280 100644 --- a/geest/core/workflows/workflow_base.py +++ b/geest/core/workflows/workflow_base.py @@ -42,6 +42,7 @@ def __init__( cell_size_m: 100.0, feedback: QgsFeedback, context: QgsProcessingContext, + working_directory: str = None, ): """ Initialize the workflow with attributes and feedback. @@ -49,6 +50,7 @@ def __init__( :param cell_size_m: The cell size in meters for the analysis. :param feedback: QgsFeedback object for progress reporting and cancellation. :context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance + :working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings. """ super().__init__() self.item = item # ⭐️ This is a reference - whatever you change in this item will directly update the tree @@ -59,7 +61,10 @@ def __init__( # This is set in the setup panel self.settings = QSettings() # This is the top level folder for work files - self.working_directory = self.settings.value("last_working_directory", "") + if working_directory: + self.workflow_directory = working_directory + else: + self.working_directory = self.settings.value("last_working_directory", "") if not self.working_directory: raise ValueError("Working directory not set.") # This is the lower level directory for this workflow diff --git a/test/test_acled_impact_workflow.py b/test/test_acled_impact_workflow.py new file mode 100644 index 0000000..cbd3a45 --- /dev/null +++ b/test/test_acled_impact_workflow.py @@ -0,0 +1,159 @@ +import unittest +import os +from unittest.mock import patch, MagicMock, mock_open +from qgis.core import QgsVectorLayer +from geest.core import JsonTreeItem +from geest.core.workflows import AcledImpactWorkflow +from utilities_for_testing import prepare_fixtures + + +class TestAcledImpactWorkflow(unittest.TestCase): + """Tests for the AcledImpactWorkflow class.""" + + def setUp(self): + """Set up test data.""" + # Mock JsonTreeItem with required attributes + self.mock_item = MagicMock(spec=JsonTreeItem) + self.mock_item.attributes = { + "use_csv_to_point_layer_csv_file": "mock_csv_file.csv", + "id": "TestLayer", + } + + # Mock QgsProcessingContext and QgsFeedback + self.mock_context = MagicMock() + self.mock_feedback = MagicMock() + + # Define working directories + self.test_data_directory = prepare_fixtures() + self.working_directory = os.path.join(self.test_data_directory, "output") + + # Create the output directory if it doesn't exist + if not os.path.exists(self.working_directory): + os.makedirs(self.working_directory) + + @unittest.skip("This test is not ready") + @patch("geest.workflow.AcledImpactWorkflow._load_csv_as_point_layer") + def test_workflow_initialization_valid_csv(self, mock_load_csv): + """Test initialization with a valid CSV file.""" + mock_layer = MagicMock(spec=QgsVectorLayer) + mock_layer.isValid.return_value = True + mock_load_csv.return_value = mock_layer + + workflow = AcledImpactWorkflow( + self.mock_item, + cell_size_m=1000, + feedback=self.mock_feedback, + context=self.mock_context, + working_directory=self.working_directory, + ) + + self.assertEqual(workflow.csv_file, "mock_csv_file.csv") + self.assertTrue(workflow.features_layer.isValid()) + mock_load_csv.assert_called_once() + + @unittest.skip("This test is not ready") + @patch("geest.workflow.AcledImpactWorkflow._load_csv_as_point_layer") + def test_workflow_initialization_invalid_csv(self, mock_load_csv): + """Test initialization with an invalid CSV file.""" + mock_layer = MagicMock(spec=QgsVectorLayer) + mock_layer.isValid.return_value = False + mock_load_csv.return_value = mock_layer + + with self.assertRaises(Exception) as cm: + AcledImpactWorkflow( + self.mock_item, + cell_size_m=1000, + feedback=self.mock_feedback, + context=self.mock_context, + working_directory=self.working_directory, + ) + + self.assertIn("ACLED CSV layer is not valid", str(cm.exception)) + + @unittest.skip("This test is not ready") + @patch( + "builtins.open", + new_callable=mock_open, + read_data="latitude,longitude,event_type\n0,0,TestEvent", + ) + @patch("qgis.core.QgsVectorFileWriter.writeAsVectorFormat") + @patch("qgis.core.QgsCoordinateTransform.transform") + def test_load_csv_as_point_layer(self, mock_transform, mock_writer, mock_open_file): + """Test the loading of CSV as a point layer.""" + # Mock CRS and transform + mock_transform.return_value = MagicMock() + + # Mock context.project().transformContext() + self.mock_context.project().transformContext.return_value = MagicMock() + + # Create workflow and call _load_csv_as_point_layer + workflow = AcledImpactWorkflow( + self.mock_item, + cell_size_m=1000, + feedback=self.mock_feedback, + context=self.mock_context, + working_directory=self.working_directory, + ) + + with patch.object( + workflow, "target_crs", MagicMock(authid=lambda: "EPSG:4326") + ): + layer = workflow._load_csv_as_point_layer() + + self.assertIsInstance(layer, QgsVectorLayer) + mock_open_file.assert_called_once_with( + "mock_csv_file.csv", newline="", encoding="utf-8" + ) + mock_writer.assert_called_once() + + @unittest.skip("This test is not ready") + @patch("geest.workflow.AcledImpactWorkflow._buffer_features") + @patch("geest.workflow.AcledImpactWorkflow._assign_scores") + @patch("geest.workflow.AcledImpactWorkflow._overlay_analysis") + @patch("geest.workflow.AcledImpactWorkflow._rasterize") + def test_process_features_for_area( + self, mock_rasterize, mock_overlay, mock_scores, mock_buffer + ): + """Test the processing of features for an area.""" + mock_geometry = MagicMock() + mock_layer = MagicMock(spec=QgsVectorLayer) + mock_buffer.return_value = mock_layer + mock_scores.return_value = mock_layer + mock_overlay.return_value = mock_layer + mock_rasterize.return_value = "mock_raster.tif" + + workflow = AcledImpactWorkflow( + self.mock_item, + cell_size_m=1000, + feedback=self.mock_feedback, + context=self.mock_context, + working_directory=self.working_directoryConfigured, + ) + + result = workflow._process_features_for_area( + mock_geometry, mock_geometry, mock_layer, 0 + ) + self.assertEqual(result, "mock_raster.tif") + mock_buffer.assert_called_once() + mock_scores.assert_called_once() + mock_overlay.assert_called_once() + mock_rasterize.assert_called_once() + + @unittest.skip("This test is not ready") + def test_workflow_fails_without_csv(self): + """Test initialization without a CSV file.""" + self.mock_item.attributes.pop("use_csv_to_point_layer_csv_file") + + with self.assertRaises(Exception) as cm: + AcledImpactWorkflow( + self.mock_item, + cell_size_m=1000, + feedback=self.mock_feedback, + context=self.mock_context, + working_directory=self.working_directory, + ) + self.assertIn("No CSV file provided.", str(cm.exception)) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_json_tree_item.py b/test/test_json_tree_item.py new file mode 100644 index 0000000..efe7d41 --- /dev/null +++ b/test/test_json_tree_item.py @@ -0,0 +1,100 @@ +import unittest +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QColor +from uuid import UUID +from geest.core.json_tree_item import JsonTreeItem + + +class TestJsonTreeItem(unittest.TestCase): + """Tests for the JsonTreeItem class.""" + + def setUp(self): + """Set up test data.""" + self.test_data = [ + "Test Item", + "Configured", + 1.0, # Example weight value + { # Attributes dictionary + "analysis_mode": "use_csv_to_point_layer", + "default_factor_weighting": 1.0, + "default_multi_buffer_distances": "0,0,0", + "default_single_buffer_distance": 5000, + "description": "", + "error": "", + "error_file": "", + "execution_end_time": "", + "execution_start_time": "", + "factor_weighting": 1.0, + "guid": "10c49ccc-50ae-4b08-a68f-899c2f55b370", + "id": "FCV", + "index_score": 0, + "indicator": "ACLED data (Violence Estimated Events)", + "output_filename": "FCV_output", + "result": "Not Run", + "result_file": "", + "use_classify_polygon_into_classes": 0, + "use_classify_safety_polygon_into_classes": 0, + "use_csv_to_point_layer": 1, + "use_csv_to_point_layer_csv_file": "/home/timlinux/dev/python/GEEST2/data/StLucia/Place Characterization/FCV/2022-05-01-2024-05-01-Saint_Lucia.csv", + "use_csv_to_point_layer_distance": 1000, + "use_environmental_hazards": 0, + "use_index_score": 0, + "use_multi_buffer_point": 0, + "use_nighttime_lights": 0, + "use_point_per_cell": 0, + "use_polygon_per_cell": 0, + "use_polyline_per_cell": 0, + "use_single_buffer_point": 1, + "use_street_lights": 0, + }, + ] + + def test_json_tree_item_creation(self): + """Test creating a JsonTreeItem instance.""" + item = JsonTreeItem(self.test_data, role="indicator") + + # Check that the item is created correctly + self.assertEqual(item.data(0), "Test Item") + self.assertEqual(item.data(1), "Configured") + self.assertEqual(item.data(2), 1.0) + self.assertEqual(item.role, "indicator") + self.assertEqual(item.attributes().get("id"), "FCV") + self.assertEqual( + item.attributes().get("analysis_mode"), "use_csv_to_point_layer" + ) + self.assertIsInstance(item.attributes(), dict) + + # Check GUID + self.assertTrue(UUID(item.guid)) # Validates the GUID format + + # Check font and color + self.assertEqual(item.font_color, QColor(Qt.black)) + + # Check methods + self.assertTrue(item.isIndicator()) + self.assertFalse(item.isFactor()) + self.assertFalse(item.isDimension()) + self.assertFalse(item.isAnalysis()) + + # Test visibility toggle + self.assertTrue(item.is_visible()) + item.set_visibility(False) + self.assertFalse(item.is_visible()) + + # Test status + self.assertTrue(item.getStatus() == "WRITE TOOL TIP", msg=item.getStatus()) + + def test_json_tree_item_append_child(self): + """Test appending child items.""" + parent_item = JsonTreeItem(self.test_data, role="dimension") + child_item = JsonTreeItem(self.test_data, role="factor", parent=parent_item) + + parent_item.appendChild(child_item) + + self.assertEqual(parent_item.childCount(), 1) + self.assertIs(parent_item.child(0), child_item) + self.assertEqual(child_item.parent(), parent_item) + + +if __name__ == "__main__": + unittest.main()