From 804e4768a95e06028cb104ff84f39a669aaa5dbc Mon Sep 17 00:00:00 2001 From: Antony Nyagah Date: Tue, 12 Mar 2024 21:52:51 +0300 Subject: [PATCH 1/4] add tests for the dataset model in the core app - modify the dataset model with a __str__ return - modify .gitignore by adding env folders --- .gitignore | 4 +++ backend/core/models.py | 3 ++ backend/tests/test_core_dataset_model.py | 40 ++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 backend/tests/test_core_dataset_model.py diff --git a/.gitignore b/.gitignore index 8b0ba420..413475d6 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,7 @@ trainings/* backend/.env backend/config.txt backend/postgres-data + +# virtualenvs +.venv/ +.env/ diff --git a/backend/core/models.py b/backend/core/models.py index 5ad0e284..85ff6ea9 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -22,6 +22,9 @@ class DatasetStatus(models.IntegerChoices): default=-1, choices=DatasetStatus.choices ) # 0 for active , 1 for archieved + def __str__(self): + return self.name + class AOI(models.Model): class DownloadStatus(models.IntegerChoices): diff --git a/backend/tests/test_core_dataset_model.py b/backend/tests/test_core_dataset_model.py new file mode 100644 index 00000000..635531ff --- /dev/null +++ b/backend/tests/test_core_dataset_model.py @@ -0,0 +1,40 @@ +from core.models import Dataset +from django.test import TestCase +from login.models import OsmUser + + +class DatasetModelTest(TestCase): + @classmethod + def setUpTestData(cls): + # Set up non-modified objects used by all test methods + cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") + cls.dataset = Dataset.objects.create( + name="Test Dataset", + created_by=cls.osm_user, + source_imagery="http://example.com/image.png", + status=Dataset.DatasetStatus.ACTIVE, + ) + + def test_dataset_creation(self): + # Test the Dataset instance has been created properly. + self.assertTrue(isinstance(self.dataset, Dataset)) + self.assertEqual(self.dataset.__str__(), self.dataset.name) + + def test_dataset_status(self): + # Test the status field works as expected + self.assertEqual(self.dataset.status, Dataset.DatasetStatus.ACTIVE) + + def test_dataset_fields(self): + # Test all fields for correct values + self.assertEqual(self.dataset.name, "Test Dataset") + self.assertEqual(self.dataset.created_by, self.osm_user) + self.assertEqual(self.dataset.source_imagery, "http://example.com/image.png") + self.assertEqual(self.dataset.status, Dataset.DatasetStatus.ACTIVE) + # Ensure auto_now_add fields are populated + self.assertIsNotNone(self.dataset.created_at) + # Ensure auto_now fields are populated + self.assertIsNotNone(self.dataset.last_modified) + + def test_string_representation(self): + # Test the string representation of a Dataset instance + self.assertEqual(str(self.dataset), "Test Dataset") From ea78c0428a4617815ad6edf15941d1c82290f17c Mon Sep 17 00:00:00 2001 From: Antony Nyagah Date: Wed, 13 Mar 2024 06:45:13 +0300 Subject: [PATCH 2/4] add tests for models in core app --- backend/tests/test_models_core.py | 93 +++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 backend/tests/test_models_core.py diff --git a/backend/tests/test_models_core.py b/backend/tests/test_models_core.py new file mode 100644 index 00000000..fddda735 --- /dev/null +++ b/backend/tests/test_models_core.py @@ -0,0 +1,93 @@ +from core.models import AOI, Dataset +from django.contrib.gis.geos import Polygon +from django.test import TestCase +from login.models import OsmUser + + +class DatasetModelTest(TestCase): + @classmethod + def setUpTestData(cls): + # Set up non-modified objects used by all test methods + cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") + cls.dataset = Dataset.objects.create( + name="Test Dataset", + created_by=cls.osm_user, + source_imagery="http://example.com/image.png", + status=Dataset.DatasetStatus.ACTIVE, + ) + + def test_dataset_creation(self): + # Test the Dataset instance has been created properly. + self.assertTrue(isinstance(self.dataset, Dataset)) + self.assertEqual(self.dataset.__str__(), self.dataset.name) + + def test_invalid_dataset_creation(self): + # Raise an exception if an invalid dataset is created + with self.assertRaises(Exception): + self.dataset = Dataset.objects.create( + name="Test Dataset", + created_by=None, + source_imagery="http://example.com/image.png", + status=Dataset.DatasetStatus.ACTIVE, + ) + + def test_dataset_status(self): + # Test the status field works as expected + self.assertEqual(self.dataset.status, Dataset.DatasetStatus.ACTIVE) + + def test_dataset_fields(self): + # Test all fields for correct values + self.assertEqual(self.dataset.name, "Test Dataset") + self.assertEqual(self.dataset.created_by, self.osm_user) + self.assertEqual(self.dataset.source_imagery, "http://example.com/image.png") + self.assertEqual(self.dataset.status, Dataset.DatasetStatus.ACTIVE) + # Ensure auto_now_add fields are populated + self.assertIsNotNone(self.dataset.created_at) + # Ensure auto_now fields are populated + self.assertIsNotNone(self.dataset.last_modified) + + def test_string_representation(self): + # Test the string representation of a Dataset instance + self.assertEqual(str(self.dataset), "Test Dataset") + + +class AOIModelTest(TestCase): + @classmethod + def setUpTestData(cls): + # Set up non-modified objects used by all test methods + cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") + cls.aoi = AOI.objects.create( + dataset=Dataset.objects.create( + name="Test Dataset", + created_by=cls.osm_user, + source_imagery="http://example.com/image.png", + status=Dataset.DatasetStatus.ACTIVE, + ), + geom=Polygon( + ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), + ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), + ), + ) + + def test_aoi_creation(self): + # Test the AOI instance has been created properly. + self.assertTrue(isinstance(self.aoi, AOI)) + self.assertEqual( + self.aoi.__str__(), f"{self.aoi.dataset.name} - {self.aoi.geom}" + ) + + def test_invalid_AOI_creation(self): + # Raise an exception if an invalid AOI is created + with self.assertRaises(Exception): + self.aoi = AOI.objects.create( + dataset=None, + geom=Polygon( + ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), + ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), + ), + label_status=AOI.DownloadStatus.DOWNLOADED, + ) + + def test_default_label_status(self): + # The default label status should be NOT_DOWNLOADED + self.assertEqual(self.aoi.label_status, AOI.DownloadStatus.NOT_DOWNLOADED) From 50688ecee964ecfa9f067a794a2a594e707f0e4d Mon Sep 17 00:00:00 2001 From: Antony Nyagah Date: Wed, 13 Mar 2024 12:30:28 +0300 Subject: [PATCH 3/4] add tests for models in the core app --- backend/core/models.py | 20 +- backend/tests/test_core_dataset_model.py | 40 --- backend/tests/test_models_core.py | 312 ++++++++++++++++++++++- 3 files changed, 317 insertions(+), 55 deletions(-) delete mode 100644 backend/tests/test_core_dataset_model.py diff --git a/backend/core/models.py b/backend/core/models.py index 85ff6ea9..94e32ed4 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -39,6 +39,9 @@ class DownloadStatus(models.IntegerChoices): created_at = models.DateTimeField(auto_now_add=True) last_modified = models.DateTimeField(auto_now=True) + def __str__(self): + return f"{self.dataset.name} - {self.geom}" + class Label(models.Model): aoi = models.ForeignKey(AOI, to_field="id", on_delete=models.CASCADE) @@ -47,6 +50,9 @@ class Label(models.Model): tags = models.JSONField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) + def __str__(self): + return f"{self.aoi} - {self.geom}" + class Model(models.Model): class ModelStatus(models.IntegerChoices): @@ -60,7 +66,10 @@ class ModelStatus(models.IntegerChoices): last_modified = models.DateTimeField(auto_now=True) created_by = models.ForeignKey(OsmUser, to_field="osm_id", on_delete=models.CASCADE) published_training = models.PositiveIntegerField(null=True, blank=True) - status = models.IntegerField(default=-1, choices=ModelStatus.choices) # + status = models.IntegerField(default=-1, choices=ModelStatus.choices) + + def __str__(self): + return self.name class Training(models.Model): @@ -90,6 +99,9 @@ class Training(models.Model): batch_size = models.PositiveIntegerField() freeze_layers = models.BooleanField(default=False) + def __str__(self): + return self.model.name + class Feedback(models.Model): FEEDBACK_TYPE = ( @@ -109,6 +121,9 @@ class Feedback(models.Model): user = models.ForeignKey(OsmUser, to_field="osm_id", on_delete=models.CASCADE) source_imagery = models.URLField() + def __str__(self): + return f"{self.user} - {self.training} - {self.feedback_type}" + class FeedbackAOI(models.Model): class DownloadStatus(models.IntegerChoices): @@ -125,6 +140,9 @@ class DownloadStatus(models.IntegerChoices): source_imagery = models.URLField() user = models.ForeignKey(OsmUser, to_field="osm_id", on_delete=models.CASCADE) + def __str__(self): + return f"{self.user} - {self.training} - {self.source_imagery}" + class FeedbackLabel(models.Model): osm_id = models.BigIntegerField(null=True, blank=True) diff --git a/backend/tests/test_core_dataset_model.py b/backend/tests/test_core_dataset_model.py deleted file mode 100644 index 635531ff..00000000 --- a/backend/tests/test_core_dataset_model.py +++ /dev/null @@ -1,40 +0,0 @@ -from core.models import Dataset -from django.test import TestCase -from login.models import OsmUser - - -class DatasetModelTest(TestCase): - @classmethod - def setUpTestData(cls): - # Set up non-modified objects used by all test methods - cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") - cls.dataset = Dataset.objects.create( - name="Test Dataset", - created_by=cls.osm_user, - source_imagery="http://example.com/image.png", - status=Dataset.DatasetStatus.ACTIVE, - ) - - def test_dataset_creation(self): - # Test the Dataset instance has been created properly. - self.assertTrue(isinstance(self.dataset, Dataset)) - self.assertEqual(self.dataset.__str__(), self.dataset.name) - - def test_dataset_status(self): - # Test the status field works as expected - self.assertEqual(self.dataset.status, Dataset.DatasetStatus.ACTIVE) - - def test_dataset_fields(self): - # Test all fields for correct values - self.assertEqual(self.dataset.name, "Test Dataset") - self.assertEqual(self.dataset.created_by, self.osm_user) - self.assertEqual(self.dataset.source_imagery, "http://example.com/image.png") - self.assertEqual(self.dataset.status, Dataset.DatasetStatus.ACTIVE) - # Ensure auto_now_add fields are populated - self.assertIsNotNone(self.dataset.created_at) - # Ensure auto_now fields are populated - self.assertIsNotNone(self.dataset.last_modified) - - def test_string_representation(self): - # Test the string representation of a Dataset instance - self.assertEqual(str(self.dataset), "Test Dataset") diff --git a/backend/tests/test_models_core.py b/backend/tests/test_models_core.py index fddda735..c78aeabb 100644 --- a/backend/tests/test_models_core.py +++ b/backend/tests/test_models_core.py @@ -1,4 +1,13 @@ -from core.models import AOI, Dataset +from core.models import ( + AOI, + Dataset, + Feedback, + FeedbackAOI, + FeedbackLabel, + Label, + Model, + Training, +) from django.contrib.gis.geos import Polygon from django.test import TestCase from login.models import OsmUser @@ -13,13 +22,15 @@ def setUpTestData(cls): name="Test Dataset", created_by=cls.osm_user, source_imagery="http://example.com/image.png", - status=Dataset.DatasetStatus.ACTIVE, ) def test_dataset_creation(self): # Test the Dataset instance has been created properly. self.assertTrue(isinstance(self.dataset, Dataset)) - self.assertEqual(self.dataset.__str__(), self.dataset.name) + + def test_string_representation(self): + # Test the string representation of a Dataset instance + self.assertEqual(str(self.dataset), "Test Dataset") def test_invalid_dataset_creation(self): # Raise an exception if an invalid dataset is created @@ -31,25 +42,20 @@ def test_invalid_dataset_creation(self): status=Dataset.DatasetStatus.ACTIVE, ) - def test_dataset_status(self): - # Test the status field works as expected - self.assertEqual(self.dataset.status, Dataset.DatasetStatus.ACTIVE) + def test_default_dataset_status(self): + # Test the default dataset status is DRAFT + self.assertEqual(self.dataset.status, Dataset.DatasetStatus.DRAFT) def test_dataset_fields(self): # Test all fields for correct values self.assertEqual(self.dataset.name, "Test Dataset") self.assertEqual(self.dataset.created_by, self.osm_user) self.assertEqual(self.dataset.source_imagery, "http://example.com/image.png") - self.assertEqual(self.dataset.status, Dataset.DatasetStatus.ACTIVE) # Ensure auto_now_add fields are populated self.assertIsNotNone(self.dataset.created_at) # Ensure auto_now fields are populated self.assertIsNotNone(self.dataset.last_modified) - def test_string_representation(self): - # Test the string representation of a Dataset instance - self.assertEqual(str(self.dataset), "Test Dataset") - class AOIModelTest(TestCase): @classmethod @@ -61,7 +67,6 @@ def setUpTestData(cls): name="Test Dataset", created_by=cls.osm_user, source_imagery="http://example.com/image.png", - status=Dataset.DatasetStatus.ACTIVE, ), geom=Polygon( ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), @@ -69,11 +74,15 @@ def setUpTestData(cls): ), ) - def test_aoi_creation(self): + def test_AOI_creation(self): # Test the AOI instance has been created properly. self.assertTrue(isinstance(self.aoi, AOI)) + + def test_string_representation(self): + # Test the string representation of an AOI instance self.assertEqual( - self.aoi.__str__(), f"{self.aoi.dataset.name} - {self.aoi.geom}" + str(self.aoi), + f"Test Dataset - {self.aoi.geom}", ) def test_invalid_AOI_creation(self): @@ -91,3 +100,278 @@ def test_invalid_AOI_creation(self): def test_default_label_status(self): # The default label status should be NOT_DOWNLOADED self.assertEqual(self.aoi.label_status, AOI.DownloadStatus.NOT_DOWNLOADED) + + +class LabelModelTest(TestCase): + @classmethod + def setUpTestData(cls): + # Set up non-modified objects used by all test methods + cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") + cls.aoi = AOI.objects.create( + dataset=Dataset.objects.create( + name="Test Dataset", + created_by=cls.osm_user, + source_imagery="http://example.com/image.png", + status=Dataset.DatasetStatus.ACTIVE, + ), + geom=Polygon( + ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), + ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), + ), + ) + cls.label = Label.objects.create( + aoi=cls.aoi, + geom=Polygon( + ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), + ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), + ), + osm_id=123456789, + tags={"key": "value"}, + ) + + def test_label_creation(self): + # Test the Label instance has been created properly. + self.assertTrue(isinstance(self.label, Label)) + + def test_string_representation(self): + # Test the string representation of an Label instance + self.assertEqual( + str(self.label), + f"{self.label.aoi} - {self.label.geom}", + ) + + def test_invalid_label_creation(self): + # Raise an exception if an invalid label is created + with self.assertRaises(Exception): + self.label = Label.objects.create( + aoi=None, + geom=Polygon( + ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), + ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), + ), + osm_id=123456789, + tags={"key": "value"}, + ) + + def test_created_at_field(self): + # Test the created_by field is not empty + self.assertIsNotNone(self.label.created_at) + + +class ModelTest(TestCase): + @classmethod + def setUpTestData(cls): + # Set up non-modified objects used by all test methods + cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") + cls.dataset = Dataset.objects.create( + name="Test Dataset", + created_by=cls.osm_user, + source_imagery="http://example.com/image.png", + ) + cls.model = Model.objects.create( + name="Test Model", + created_by=cls.osm_user, + dataset=cls.dataset, + ) + + def test_model_creation(self): + # Test the Model instance has been created properly. + self.assertTrue(isinstance(self.model, Model)) + + def test_invalid_model_creation(self): + # Raise an exception if an invalid model is created + with self.assertRaises(Exception): + self.model = Model.objects.create( + name="Test Model", + created_by=None, + dataset=self.dataset, + ) + + def test_string_representation(self): + # Test the string representation of a Model instance + self.assertEqual(str(self.model), self.model.name) + + def test_created_by_field(self): + # Test the created_by field is not empty + self.assertIsNotNone(self.model.created_by) + + +class TrainingModelTest(TestCase): + @classmethod + def setUpTestData(cls): + # Set up non-modified objects used by all test methods + cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") + cls.dataset = Dataset.objects.create( + name="Test Dataset", + created_by=cls.osm_user, + ) + cls.model = Model.objects.create( + name="Test Model", + created_by=cls.osm_user, + dataset=cls.dataset, + ) + cls.training = Training.objects.create( + model=cls.model, + zoom_level=[19, 20, 21, 22], + created_by=cls.osm_user, + epochs=10, + batch_size=32, + ) + cls.source_imagery = "http://example.com/image.png" + cls.description = "Test description" + + def test_training_creation(self): + # Test the training instance has been created properly. + self.assertTrue(isinstance(self.training, Training)) + self.assertIsNotNone(self.training) + + def test_string_representation(self): + # Test the string representation of an AOI instance + self.assertEqual(str(self.training), (self.model.name)) + + def test_default_training_status(self): + # Test the default status is "SUBMITTED" + self.assertEqual(self.training.status, "SUBMITTED") + + +class FeedbackModelTest(TestCase): + @classmethod + def setUpTestData(cls): + # Set up non-modified objects used by all test methods + cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") + cls.model = Model.objects.create( + name="Test Model", + created_by=cls.osm_user, + dataset=Dataset.objects.create( + name="Test Dataset", + created_by=cls.osm_user, + ), + ) + cls.feedback = Feedback.objects.create( + user=cls.osm_user, + training=Training.objects.create( + model=cls.model, + zoom_level=[19, 20, 21, 22], + created_by=cls.osm_user, + epochs=10, + batch_size=32, + ), + geom=Polygon( + ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), + ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), + ), + zoom_level=19, + source_imagery="http://example.com/image.png", + ) + + def test_feedback_creation(self): + # Test the feedback instance has been created properly. + self.assertTrue(isinstance(self.feedback, Feedback)) + self.assertIsNotNone(self.feedback) + + def test_string_representation(self): + # Test the string representation of a Feedback instance + self.assertEqual( + str(self.feedback), + f"{self.feedback.user} - {self.feedback.training} - {self.feedback.feedback_type}", + ) + + def test_created_at_field(self): + # Test the created_at field is not empty + self.assertIsNotNone(self.feedback.created_at) + + +class FeedbackAOITest(TestCase): + @classmethod + def setUpTestData(cls): + # Set up non-modified objects used by all test methods + cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") + cls.dataset = Dataset.objects.create( + name="Test Dataset", + created_by=cls.osm_user, + ) + cls.model = Model.objects.create( + name="Test Model", + created_by=cls.osm_user, + dataset=cls.dataset, + ) + cls.training = Training.objects.create( + model=cls.model, + zoom_level=[19, 20, 21, 22], + created_by=cls.osm_user, + epochs=10, + batch_size=32, + ) + cls.feedbackAOI = FeedbackAOI.objects.create( + user=cls.osm_user, + training=cls.training, + source_imagery="http://example.com/aoi_image.png", + geom=Polygon( + ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), + ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), + ), + ) + + def test_feedback_aoi_creation(self): + # Test the feedback aoi instance has been created properly. + self.assertTrue(isinstance(self.feedbackAOI, FeedbackAOI)) + self.assertIsNotNone(self.feedbackAOI) + + def test_string_representation(self): + # Test the string representation of a FeedbackAOI instance + self.assertEqual( + str(self.feedbackAOI), + f"{self.feedbackAOI.user} - {self.feedbackAOI.training} - {self.feedbackAOI.source_imagery}", + ) + + +class FeedbackLabelTest(TestCase): + @classmethod + def setUpTestData(cls): + # Set up non-modified objects used by all test methods + cls.osm_user = OsmUser.objects.create( + osm_id="987654321", username="feedbacklabeluser" + ) + cls.dataset = Dataset.objects.create( + name="Feedback Label Dataset", + created_by=cls.osm_user, + ) + cls.model = Model.objects.create( + name="Feedback Label Model", + created_by=cls.osm_user, + dataset=cls.dataset, + ) + cls.training = Training.objects.create( + model=cls.model, + zoom_level=[19, 20, 21, 22], + created_by=cls.osm_user, + epochs=5, + batch_size=16, + ) + cls.feedbackAOI = FeedbackAOI.objects.create( + user=cls.osm_user, + training=cls.training, + source_imagery="http://example.com/feedback_aoi_image.png", + geom=Polygon( + ((0, 0), (0, 2), (2, 2), (2, 0), (0, 0)), + ((0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)), + ), + ) + cls.feedbackLabel = FeedbackLabel.objects.create( + osm_id=123456789, + feedback_aoi=cls.feedbackAOI, + tags={"natural": "tree"}, + geom=Polygon(((0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5))), + ) + + def test_feedback_label_creation(self): + # Test the feedback label instance has been created properly. + self.assertTrue(isinstance(self.feedbackLabel, FeedbackLabel)) + self.assertIsNotNone(self.feedbackLabel) + + def test_feedback_label_fields(self): + # Test the fields of the feedback label instance + self.assertEqual(self.feedbackLabel.osm_id, 123456789) + self.assertEqual(self.feedbackLabel.feedback_aoi, self.feedbackAOI) + self.assertEqual(self.feedbackLabel.tags, {"natural": "tree"}) + self.assertTrue(isinstance(self.feedbackLabel.geom, Polygon)) From ce0a2d2dd2cc8a18b9578c649b51884b411692bd Mon Sep 17 00:00:00 2001 From: Antony Nyagah Date: Wed, 13 Mar 2024 20:56:08 +0300 Subject: [PATCH 4/4] modify test to use model bakery for easier testing - add model bakery to requirements --- backend/core/models.py | 3 + backend/requirements.txt | 3 +- backend/tests/test_models_core.py | 359 ++++++++++++++++-------------- 3 files changed, 200 insertions(+), 165 deletions(-) diff --git a/backend/core/models.py b/backend/core/models.py index 94e32ed4..dafa77ae 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -153,3 +153,6 @@ class FeedbackLabel(models.Model): geom = geomodels.PolygonField(srid=4326) created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.osm_id} - {self.feedback_aoi} - {self.tags}" diff --git a/backend/requirements.txt b/backend/requirements.txt index 3ac6c0cd..be9673cb 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -22,4 +22,5 @@ geojson2osm==0.0.1 osmconflator orthogonalizer fairpredictor==0.0.26 -tflite-runtime==2.14.0 \ No newline at end of file +tflite-runtime==2.14.0 +model-bakery==1.17.0 # for testing \ No newline at end of file diff --git a/backend/tests/test_models_core.py b/backend/tests/test_models_core.py index c78aeabb..d45e45de 100644 --- a/backend/tests/test_models_core.py +++ b/backend/tests/test_models_core.py @@ -11,120 +11,102 @@ from django.contrib.gis.geos import Polygon from django.test import TestCase from login.models import OsmUser +from model_bakery import baker class DatasetModelTest(TestCase): - @classmethod - def setUpTestData(cls): - # Set up non-modified objects used by all test methods - cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") - cls.dataset = Dataset.objects.create( - name="Test Dataset", - created_by=cls.osm_user, - source_imagery="http://example.com/image.png", - ) + def setUp(self): + # Create a Dataset instance for testing + self.dataset = baker.make(Dataset, name="Test Dataset") def test_dataset_creation(self): - # Test the Dataset instance has been created properly. - self.assertTrue(isinstance(self.dataset, Dataset)) - - def test_string_representation(self): - # Test the string representation of a Dataset instance - self.assertEqual(str(self.dataset), "Test Dataset") + # Test that the Dataset instance has been created properly. + self.assertEqual(self.dataset.name, "Test Dataset") def test_invalid_dataset_creation(self): - # Raise an exception if an invalid dataset is created + # Test thet the Dataset instance return an exception if missing needed values. with self.assertRaises(Exception): - self.dataset = Dataset.objects.create( - name="Test Dataset", - created_by=None, - source_imagery="http://example.com/image.png", - status=Dataset.DatasetStatus.ACTIVE, - ) - - def test_default_dataset_status(self): - # Test the default dataset status is DRAFT - self.assertEqual(self.dataset.status, Dataset.DatasetStatus.DRAFT) + self.dataset = baker.make(created_by=None) def test_dataset_fields(self): # Test all fields for correct values self.assertEqual(self.dataset.name, "Test Dataset") - self.assertEqual(self.dataset.created_by, self.osm_user) - self.assertEqual(self.dataset.source_imagery, "http://example.com/image.png") # Ensure auto_now_add fields are populated self.assertIsNotNone(self.dataset.created_at) # Ensure auto_now fields are populated self.assertIsNotNone(self.dataset.last_modified) + def test_dataset_string_representation(self): + # Test the string representation of a Dataset instance + self.assertEqual(str(self.dataset), "Test Dataset") + class AOIModelTest(TestCase): - @classmethod - def setUpTestData(cls): - # Set up non-modified objects used by all test methods - cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") - cls.aoi = AOI.objects.create( - dataset=Dataset.objects.create( - name="Test Dataset", - created_by=cls.osm_user, - source_imagery="http://example.com/image.png", - ), - geom=Polygon( - ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), - ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), - ), + def setUp(self): + # Create a Dataset instance for testing + self.dataset = baker.make(Dataset, name="Test Dataset") + # Create an AOI instance for testing + self.aoi = baker.make( + AOI, + dataset=self.dataset, + geom="POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", + label_status=AOI.DownloadStatus.DOWNLOADED, ) - def test_AOI_creation(self): - # Test the AOI instance has been created properly. - self.assertTrue(isinstance(self.aoi, AOI)) + def test_aoi_creation(self): + # Test that the AOI instance has been created properly. + self.assertEqual(self.aoi.dataset, self.dataset) - def test_string_representation(self): - # Test the string representation of an AOI instance - self.assertEqual( - str(self.aoi), - f"Test Dataset - {self.aoi.geom}", - ) - - def test_invalid_AOI_creation(self): - # Raise an exception if an invalid AOI is created + def test_invalid_aoi_creation(self): + # Test that an invalid AOI instance creation returns an exception. with self.assertRaises(Exception): - self.aoi = AOI.objects.create( - dataset=None, - geom=Polygon( - ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), - ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), - ), - label_status=AOI.DownloadStatus.DOWNLOADED, - ) + self.aoi = baker.make(AOI, dataset=None) + + def test_aoi_geom(self): + # Test the geom field of the AOI instance. + self.assertIsInstance(self.aoi.geom, Polygon) + + def test_aoi_label_default_status(self): + # Test the default label_status field of the AOI instance is DOWNLOADED. + self.assertEqual(self.aoi.label_status, AOI.DownloadStatus.DOWNLOADED) + + def test_aoi_label_fetched(self): + # Test the label_fetched field of the AOI instance. + self.assertIsNone(self.aoi.label_fetched) - def test_default_label_status(self): - # The default label status should be NOT_DOWNLOADED - self.assertEqual(self.aoi.label_status, AOI.DownloadStatus.NOT_DOWNLOADED) + def test_aoi_created_at(self): + # Test the created_at field of the AOI instance. + self.assertIsNotNone(self.aoi.created_at) + + def test_aoi_last_modified(self): + # Test the last_modified field of the AOI instance is not empty after creation. + self.assertIsNotNone(self.aoi.last_modified) + + def test_aoi_string_representation(self): + # Test the string representation of an AOI instance. + self.assertEqual(str(self.aoi), f"Test Dataset - {self.aoi.geom}") class LabelModelTest(TestCase): - @classmethod - def setUpTestData(cls): - # Set up non-modified objects used by all test methods - cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") - cls.aoi = AOI.objects.create( - dataset=Dataset.objects.create( - name="Test Dataset", - created_by=cls.osm_user, - source_imagery="http://example.com/image.png", - status=Dataset.DatasetStatus.ACTIVE, - ), - geom=Polygon( - ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), - ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), - ), + def setUp(self): + # Create a user for testing + self.osm_user = baker.make(OsmUser, osm_id="123456789", username="testuser") + + # Create a Dataset instance for testing + self.dataset = baker.make( + Dataset, name="Test Dataset", created_by=self.osm_user ) - cls.label = Label.objects.create( - aoi=cls.aoi, - geom=Polygon( - ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), - ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), - ), + + # Create an AOI instance for testing + self.aoi = baker.make( + AOI, dataset=self.dataset, geom="POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))" + ) + + # Create a Label instance for testing + self.label = baker.make( + Label, + aoi=self.aoi, + geom="POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", osm_id=123456789, tags={"key": "value"}, ) @@ -143,12 +125,10 @@ def test_string_representation(self): def test_invalid_label_creation(self): # Raise an exception if an invalid label is created with self.assertRaises(Exception): - self.label = Label.objects.create( + self.label = baker.make( + Label, aoi=None, - geom=Polygon( - ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), - ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4)), - ), + geom="POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", osm_id=123456789, tags={"key": "value"}, ) @@ -162,16 +142,15 @@ class ModelTest(TestCase): @classmethod def setUpTestData(cls): # Set up non-modified objects used by all test methods - cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") - cls.dataset = Dataset.objects.create( + cls.osm_user = baker.make(OsmUser, osm_id="123456789", username="testuser") + cls.dataset = baker.make( + Dataset, name="Test Dataset", created_by=cls.osm_user, source_imagery="http://example.com/image.png", ) - cls.model = Model.objects.create( - name="Test Model", - created_by=cls.osm_user, - dataset=cls.dataset, + cls.model = baker.make( + Model, name="Test Model", created_by=cls.osm_user, dataset=cls.dataset ) def test_model_creation(self): @@ -181,10 +160,8 @@ def test_model_creation(self): def test_invalid_model_creation(self): # Raise an exception if an invalid model is created with self.assertRaises(Exception): - self.model = Model.objects.create( - name="Test Model", - created_by=None, - dataset=self.dataset, + self.model = baker.make( + Model, name="Test Model", created_by=None, dataset=self.dataset ) def test_string_representation(self): @@ -198,27 +175,23 @@ def test_created_by_field(self): class TrainingModelTest(TestCase): @classmethod - def setUpTestData(cls): + def setUp(self): # Set up non-modified objects used by all test methods - cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") - cls.dataset = Dataset.objects.create( - name="Test Dataset", - created_by=cls.osm_user, + self.osm_user = baker.make(OsmUser, osm_id="123456789", username="testuser") + self.dataset = baker.make( + Dataset, name="Test Dataset", created_by=self.osm_user ) - cls.model = Model.objects.create( - name="Test Model", - created_by=cls.osm_user, - dataset=cls.dataset, + self.model = baker.make( + Model, name="Test Model", created_by=self.osm_user, dataset=self.dataset ) - cls.training = Training.objects.create( - model=cls.model, + self.training = baker.make( + Training, + model=self.model, zoom_level=[19, 20, 21, 22], - created_by=cls.osm_user, + created_by=self.osm_user, epochs=10, batch_size=32, ) - cls.source_imagery = "http://example.com/image.png" - cls.description = "Test description" def test_training_creation(self): # Test the training instance has been created properly. @@ -235,24 +208,23 @@ def test_default_training_status(self): class FeedbackModelTest(TestCase): - @classmethod - def setUpTestData(cls): + def setUp(self): # Set up non-modified objects used by all test methods - cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") - cls.model = Model.objects.create( + self.osm_user = baker.make(OsmUser, osm_id="123456789", username="testuser") + self.model = baker.make( + Model, name="Test Model", - created_by=cls.osm_user, - dataset=Dataset.objects.create( - name="Test Dataset", - created_by=cls.osm_user, - ), + created_by=self.osm_user, + dataset=baker.make(Dataset, name="Test Dataset", created_by=self.osm_user), ) - cls.feedback = Feedback.objects.create( - user=cls.osm_user, - training=Training.objects.create( - model=cls.model, + self.feedback = baker.make( + Feedback, + user=self.osm_user, + training=baker.make( + Training, + model=self.model, zoom_level=[19, 20, 21, 22], - created_by=cls.osm_user, + created_by=self.osm_user, epochs=10, batch_size=32, ), @@ -282,29 +254,27 @@ def test_created_at_field(self): class FeedbackAOITest(TestCase): - @classmethod - def setUpTestData(cls): + def setUp(self): # Set up non-modified objects used by all test methods - cls.osm_user = OsmUser.objects.create(osm_id="123456789", username="testuser") - cls.dataset = Dataset.objects.create( - name="Test Dataset", - created_by=cls.osm_user, + self.osm_user = baker.make(OsmUser, osm_id="123456789", username="testuser") + self.dataset = baker.make( + Dataset, name="Test Dataset", created_by=self.osm_user ) - cls.model = Model.objects.create( - name="Test Model", - created_by=cls.osm_user, - dataset=cls.dataset, + self.model = baker.make( + Model, name="Test Model", created_by=self.osm_user, dataset=self.dataset ) - cls.training = Training.objects.create( - model=cls.model, + self.training = baker.make( + Training, + model=self.model, zoom_level=[19, 20, 21, 22], - created_by=cls.osm_user, + created_by=self.osm_user, epochs=10, batch_size=32, ) - cls.feedbackAOI = FeedbackAOI.objects.create( - user=cls.osm_user, - training=cls.training, + self.feedbackAOI = baker.make( + FeedbackAOI, + user=self.osm_user, + training=self.training, source_imagery="http://example.com/aoi_image.png", geom=Polygon( ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)), @@ -324,42 +294,77 @@ def test_string_representation(self): f"{self.feedbackAOI.user} - {self.feedbackAOI.training} - {self.feedbackAOI.source_imagery}", ) + def test_string_representation_with_different_source_imagery(self): + # Test the string representation of a FeedbackAOI instance with a different source imagery + self.feedbackAOI.source_imagery = "http://example.com/different_image.png" + self.assertEqual( + str(self.feedbackAOI), + f"{self.feedbackAOI.user} - {self.feedbackAOI.training} - {self.feedbackAOI.source_imagery}", + ) + + def test_string_representation_with_different_user(self): + # Test the string representation of a FeedbackAOI instance with a different user + self.feedbackAOI.user = baker.make( + OsmUser, osm_id="987654321", username="differentuser" + ) + self.assertEqual( + str(self.feedbackAOI), + f"{self.feedbackAOI.user} - {self.feedbackAOI.training} - {self.feedbackAOI.source_imagery}", + ) + + def test_string_representation_with_different_training(self): + # Test the string representation of a FeedbackAOI instance with a different training + self.feedbackAOI.training = baker.make( + Training, + model=self.feedbackAOI.training.model, + zoom_level=[18, 19, 20, 21], + created_by=self.feedbackAOI.training.created_by, + epochs=5, + batch_size=16, + ) + self.assertEqual( + str(self.feedbackAOI), + f"{self.feedbackAOI.user} - {self.feedbackAOI.training} - {self.feedbackAOI.source_imagery}", + ) + class FeedbackLabelTest(TestCase): - @classmethod - def setUpTestData(cls): + def setUp(self): # Set up non-modified objects used by all test methods - cls.osm_user = OsmUser.objects.create( - osm_id="987654321", username="feedbacklabeluser" + self.osm_user = baker.make( + OsmUser, osm_id="987654321", username="feedbacklabeluser" ) - cls.dataset = Dataset.objects.create( - name="Feedback Label Dataset", - created_by=cls.osm_user, + self.dataset = baker.make( + Dataset, name="Feedback Label Dataset", created_by=self.osm_user ) - cls.model = Model.objects.create( + self.model = baker.make( + Model, name="Feedback Label Model", - created_by=cls.osm_user, - dataset=cls.dataset, + created_by=self.osm_user, + dataset=self.dataset, ) - cls.training = Training.objects.create( - model=cls.model, + self.training = baker.make( + Training, + model=self.model, zoom_level=[19, 20, 21, 22], - created_by=cls.osm_user, + created_by=self.osm_user, epochs=5, batch_size=16, ) - cls.feedbackAOI = FeedbackAOI.objects.create( - user=cls.osm_user, - training=cls.training, + self.feedbackAOI = baker.make( + FeedbackAOI, + user=self.osm_user, + training=self.training, source_imagery="http://example.com/feedback_aoi_image.png", geom=Polygon( ((0, 0), (0, 2), (2, 2), (2, 0), (0, 0)), ((0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)), ), ) - cls.feedbackLabel = FeedbackLabel.objects.create( + self.feedbackLabel = baker.make( + FeedbackLabel, osm_id=123456789, - feedback_aoi=cls.feedbackAOI, + feedback_aoi=self.feedbackAOI, tags={"natural": "tree"}, geom=Polygon(((0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5))), ) @@ -370,8 +375,34 @@ def test_feedback_label_creation(self): self.assertIsNotNone(self.feedbackLabel) def test_feedback_label_fields(self): - # Test the fields of the feedback label instance +clear # Test the fields of the feedback label instance self.assertEqual(self.feedbackLabel.osm_id, 123456789) self.assertEqual(self.feedbackLabel.feedback_aoi, self.feedbackAOI) self.assertEqual(self.feedbackLabel.tags, {"natural": "tree"}) self.assertTrue(isinstance(self.feedbackLabel.geom, Polygon)) + + def test_string_representation_with_different_osm_id(self): + # Test the string representation of a FeedbackLabel instance with a different osm_id + self.feedbackLabel.osm_id = 987654321 + self.assertEqual( + str(self.feedbackLabel), + f"{self.feedbackLabel.osm_id} - {self.feedbackLabel.feedback_aoi} - {self.feedbackLabel.tags}", + ) + + def test_string_representation_with_different_tags(self): + # Test the string representation of a FeedbackLabel instance with different tags + self.feedbackLabel.tags = {"natural": "water"} + self.assertEqual( + str(self.feedbackLabel), + f"{self.feedbackLabel.osm_id} - {self.feedbackLabel.feedback_aoi} - {self.feedbackLabel.tags}", + ) + + def test_string_representation_with_different_geom(self): + # Test the string representation of a FeedbackLabel instance with different geom + self.feedbackLabel.geom = Polygon( + ((0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)) + ) + self.assertEqual( + str(self.feedbackLabel), + f"{self.feedbackLabel.osm_id} - {self.feedbackLabel.feedback_aoi} - {self.feedbackLabel.tags}", + )