From 56d928a15a77589b03963e74ac0e527f8e01f28b Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Mon, 15 Jul 2024 08:54:31 +0100 Subject: [PATCH 01/32] test(exclude-coverage): excludes callback function from coverage --- backend/login/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/login/views.py b/backend/login/views.py index bb75c9d2..321af752 100644 --- a/backend/login/views.py +++ b/backend/login/views.py @@ -38,7 +38,7 @@ def get(self, request, format=None): class callback(APIView): - def get(self, request, format=None): + def get(self, request, format=None): # pragma: no cover """Callback method redirected from osm callback method Args: From 8721fd8fb3fd5b51ec9aba4263f9589b0ef2a29e Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Mon, 15 Jul 2024 08:59:44 +0100 Subject: [PATCH 02/32] test(model-factories): adds model factories to isolate test data --- backend/tests/factories.py | 137 +++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 backend/tests/factories.py diff --git a/backend/tests/factories.py b/backend/tests/factories.py new file mode 100644 index 00000000..829c1b4e --- /dev/null +++ b/backend/tests/factories.py @@ -0,0 +1,137 @@ +import factory +from login.models import OsmUser +from django.contrib.gis.geos import Polygon +from core.models import ( + Dataset, + AOI, + Label, + Model, + Training, + Feedback, + FeedbackAOI, + FeedbackLabel, +) + + +class OsmUserFactory(factory.django.DjangoModelFactory): + class Meta: + model = OsmUser + + osm_id = 123456 + + +class DatasetFactory(factory.django.DjangoModelFactory): + class Meta: + model = Dataset + + name = "My test dataset" + source_imagery = "https://tiles.openaerialmap.org/5ac4fc6f26964b0010033112/0/5ac4fc6f26964b0010033113/{z}/{x}/{y}" + created_by = factory.SubFactory(OsmUserFactory) + + +class AoiFactory(factory.django.DjangoModelFactory): + class Meta: + model = AOI + + geom = Polygon( + ( + (32.588507094820351, 0.348666499011499), + (32.588517512656978, 0.348184682976698), + (32.588869114643053, 0.348171660921362), + (32.588840465592334, 0.348679521066151), + (32.588507094820351, 0.348666499011499), + ) + ) + dataset = factory.SubFactory(DatasetFactory) + + +class LabelFactory(factory.django.DjangoModelFactory): + class Meta: + model = Label + + aoi = factory.SubFactory(AoiFactory) + geom = Polygon( + ( + (32.588507094820351, 0.348666499011499), + (32.588517512656978, 0.348184682976698), + (32.588869114643053, 0.348171660921362), + (32.588840465592334, 0.348679521066151), + (32.588507094820351, 0.348666499011499), + ) + ) + + +class ModelFactory(factory.django.DjangoModelFactory): + class Meta: + model = Model + + dataset = factory.SubFactory(DatasetFactory) + name = "My test model" + created_by = factory.SubFactory(OsmUserFactory) + + +class TrainingFactory(factory.django.DjangoModelFactory): + class Meta: + model = Training + + model = factory.SubFactory(ModelFactory) + description = "My very first training" + created_by = factory.SubFactory(OsmUserFactory) + epochs = 1 + zoom_level = [20, 21] + batch_size = 1 + + +class FeedbackFactory(factory.django.DjangoModelFactory): + class Meta: + model = Feedback + + geom = Polygon( + ( + (32.588507094820351, 0.348666499011499), + (32.588517512656978, 0.348184682976698), + (32.588869114643053, 0.348171660921362), + (32.588840465592334, 0.348679521066151), + (32.588507094820351, 0.348666499011499), + ) + ) + training = factory.SubFactory(TrainingFactory) + zoom_level = 19 + feedback_type = "TP" + user = factory.SubFactory(OsmUserFactory) + source_imagery = "https://tiles.openaerialmap.org/5ac4fc6f26964b0010033112/0/5ac4fc6f26964b0010033113/{z}/{x}/{y}" + + +class FeedbackAoiFactory(factory.django.DjangoModelFactory): + class Meta: + model = FeedbackAOI + + training = factory.SubFactory(TrainingFactory) + geom = Polygon( + ( + (32.588507094820351, 0.348666499011499), + (32.588517512656978, 0.348184682976698), + (32.588869114643053, 0.348171660921362), + (32.588840465592334, 0.348679521066151), + (32.588507094820351, 0.348666499011499), + ) + ) + label_status = -1 + source_imagery = "https://tiles.openaerialmap.org/5ac4fc6f26964b0010033112/0/5ac4fc6f26964b0010033113/{z}/{x}/{y}" + user = factory.SubFactory(OsmUserFactory) + + +class FeedbackLabelFactory(factory.django.DjangoModelFactory): + class Meta: + model = FeedbackLabel + + feedback_aoi = factory.SubFactory(FeedbackAoiFactory) + geom = Polygon( + ( + (32.588507094820351, 0.348666499011499), + (32.588517512656978, 0.348184682976698), + (32.588869114643053, 0.348171660921362), + (32.588840465592334, 0.348679521066151), + (32.588507094820351, 0.348666499011499), + ) + ) From 8c366fad2845610f28d37623001ca3215ec92cbc Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Mon, 15 Jul 2024 09:07:27 +0100 Subject: [PATCH 03/32] test(endpoints-&-view): adds tests for the endpoints also adds test for view home - redirect --- backend/tests/test_endpoints.py | 268 ++++++++++++++++++++++++++------ backend/tests/test_views.py | 17 ++ 2 files changed, 234 insertions(+), 51 deletions(-) create mode 100644 backend/tests/test_views.py diff --git a/backend/tests/test_endpoints.py b/backend/tests/test_endpoints.py index a0447590..7b962117 100644 --- a/backend/tests/test_endpoints.py +++ b/backend/tests/test_endpoints.py @@ -2,9 +2,17 @@ import os import validators -from django.conf import settings from rest_framework import status from rest_framework.test import APILiveServerTestCase, RequestsClient +from .factories import ( + OsmUserFactory, + TrainingFactory, + DatasetFactory, + AoiFactory, + LabelFactory, + ModelFactory, + FeedbackAoiFactory, +) API_BASE = "http://testserver/api/v1" @@ -19,6 +27,12 @@ class TaskApiTest(APILiveServerTestCase): def setUp(self): # Create a request factory instance self.client = RequestsClient() + self.user = OsmUserFactory(osm_id=123) + self.dataset = DatasetFactory(created_by=self.user) + self.aoi = AoiFactory(dataset=self.dataset) + self.model = ModelFactory(dataset=self.dataset, created_by=self.user) + self.json_type_header = headersList.copy() + self.json_type_header["content-type"] = "application/json" def test_auth_me(self): res = self.client.get(f"{API_BASE}/auth/me/", headers=headersList) @@ -32,9 +46,11 @@ def test_auth_login(self): self.assertEqual(validators.url(res_body["login_url"]), True) def test_create_dataset(self): + # create dataset + payload = { - "name": "My test dataset", - "source_imagery": "https://tiles.openaerialmap.org/5ac4fc6f26964b0010033112/0/5ac4fc6f26964b0010033113/{z}/{x}/{y}", + "name": self.dataset.name, + "source_imagery": self.dataset.source_imagery, } # test without authentication should be forbidden res = self.client.post(f"{API_BASE}/dataset/", payload) @@ -43,22 +59,11 @@ def test_create_dataset(self): res = self.client.post(f"{API_BASE}/dataset/", payload, headers=headersList) self.assertEqual(res.status_code, status.HTTP_201_CREATED) - # now dataset is created , create first aoi inside it - payload_second = { - "geom": { - "type": "Polygon", - "coordinates": [ - [ - [32.588507094820351, 0.348666499011499], - [32.588517512656978, 0.348184682976698], - [32.588869114643053, 0.348171660921362], - [32.588840465592334, 0.348679521066151], - [32.588507094820351, 0.348666499011499], - ] - ], - }, - "dataset": 1, - } + def test_create_training(self): + # now dataset is created, create first aoi inside it + + payload_second = {"geom": self.aoi.geom.json, "dataset": self.dataset.id} + json_type_header = headersList json_type_header["content-type"] = "application/json" res = self.client.post( @@ -66,75 +71,115 @@ def test_create_dataset(self): ) self.assertEqual(res.status_code, status.HTTP_201_CREATED) - # create second aoi too , to test multiple aois + # create second aoi too, to test multiple aois + payload_third = { - "geom": { - "type": "Polygon", - "coordinates": [ - [ - [32.588046105549715, 0.349843692679227], - [32.588225813231475, 0.349484284008701], - [32.588624295482369, 0.349734307433132], - [32.588371662944233, 0.350088507273009], - [32.588046105549715, 0.349843692679227], - ] - ], - }, - "dataset": 1, + "geom": self.aoi.geom.json, + "dataset": self.dataset.id, } res = self.client.post( f"{API_BASE}/aoi/", json.dumps(payload_third), headers=json_type_header ) self.assertEqual(res.status_code, status.HTTP_201_CREATED) + # create model + + model_payload = {"name": self.model.name, "dataset": self.dataset.id} + res = self.client.post( + f"{API_BASE}/model/", json.dumps(model_payload), headers=json_type_header + ) + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + + # create training without label + + training_payload = { + "description": "My very first training", + "epochs": 1, + "zoom_level": [20, 21], + "batch_size": 1, + "model": self.model.id, + } + res = self.client.post( + f"{API_BASE}/training/", + json.dumps(training_payload), + headers=json_type_header, + ) + print(res.json()) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + # download labels from osm for 1 res = self.client.post( - f"{API_BASE}/label/osm/fetch/1/", "", headers=headersList + f"{API_BASE}/label/osm/fetch/{self.aoi.id}/", "", headers=headersList ) self.assertEqual(res.status_code, status.HTTP_201_CREATED) # download labels from osm for 2 res = self.client.post( - f"{API_BASE}/label/osm/fetch/2/", "", headers=headersList + f"{API_BASE}/label/osm/fetch/{self.aoi.id}/", "", headers=headersList ) self.assertEqual(res.status_code, status.HTTP_201_CREATED) - # build the dataset + # create training with epochs greater than the limit - build_dt_payload = {"dataset_id": 1, "zoom_level": ["19"]} + training_payload = { + "description": "My very first training", + "epochs": 31, + "zoom_level": [20, 21], + "batch_size": 1, + "model": self.model.id, + } res = self.client.post( - f"{API_BASE}/dataset/image/build/", - json.dumps(build_dt_payload), + f"{API_BASE}/training/", + json.dumps(training_payload), headers=json_type_header, ) - self.assertEqual(res.status_code, status.HTTP_201_CREATED) + print(res.json()) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) - # build dataset on multiple zoom levels + # create training with batch size greater than the limit - build_dt_payload = {"dataset_id": 1, "zoom_level": ["19", "20"]} + training_payload = { + "description": "My very first training", + "epochs": 1, + "zoom_level": [20, 21], + "batch_size": 9, + "model": self.model.id, + } res = self.client.post( - f"{API_BASE}/dataset/image/build/", - json.dumps(build_dt_payload), + f"{API_BASE}/training/", + json.dumps(training_payload), headers=json_type_header, ) - self.assertEqual(res.status_code, status.HTTP_201_CREATED) + print(res.json()) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) - # create model + # create training inside model - model_payload = {"name": "My test model", "dataset": 1} + training_payload = { + "description": "My very first training", + "epochs": 1, + "zoom_level": [20, 21], + "batch_size": 1, + "model": self.model.id, + } res = self.client.post( - f"{API_BASE}/model/", json.dumps(model_payload), headers=json_type_header + f"{API_BASE}/training/", + json.dumps(training_payload), + headers=json_type_header, ) + print(res.json()) self.assertEqual(res.status_code, status.HTTP_201_CREATED) - # create training inside model + # create another training for the same model + training_payload = { "description": "My very first training", "epochs": 1, + "zoom_level": [20, 21], "batch_size": 1, - "model": 1, + "model": self.model.id, } res = self.client.post( f"{API_BASE}/training/", @@ -142,5 +187,126 @@ def test_create_dataset(self): headers=json_type_header, ) print(res.json()) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + self.training = TrainingFactory(model=self.model, created_by=self.user) + + def test_create_label(self): + self.label = LabelFactory(aoi=self.aoi) + self.training = TrainingFactory(model=self.model, created_by=self.user) + + # create label + + label_payload = { + "geom": self.label.geom.json, + "aoi": self.aoi.id, + } + + res = self.client.post( + f"{API_BASE}/label/", + json.dumps(label_payload), + headers=self.json_type_header, + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) # 201- for create + + # create another label with the same geom and aoi + + label_payload2 = { + "geom": self.label.geom.json, + "aoi": self.aoi.id, + } + + res = self.client.post( + f"{API_BASE}/label/", + json.dumps(label_payload2), + headers=self.json_type_header, + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) # 200- for update + + # create another label with error + + label_payload3 = { + "geom": self.label.geom.json, + "aoi": 40, # non-existent aoi + } + res = self.client.post( + f"{API_BASE}/label/", + json.dumps(label_payload3), + headers=self.json_type_header, + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + def test_fetch_feedbackAoi_osm_label(self): + # create feedback aoi + training = TrainingFactory(model=self.model, created_by=self.user) + feedbackAoi = FeedbackAoiFactory(training=training, user=self.user) + + # download available osm data as labels for the feedback aoi + + res = self.client.post( + f"{API_BASE}/label/feedback/osm/fetch/{feedbackAoi.id}/", + "", + headers=headersList, + ) + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + + def test_get_runStatus(self): + training = TrainingFactory(model=self.model, created_by=self.user) + + # get running training status + + res = self.client.get( + f"{API_BASE}/training/status/{training.id}/", headers=headersList + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) + + def test_submit_training_feedback(self): + training = TrainingFactory(model=self.model, created_by=self.user) + + # apply feedback to training published checkpoints + + training_feedback_payload = { + "training_id": training.id, + "epochs": 20, + "batch_size": 8, + "zoom_level": [19, 20], + } + res = self.client.post( + f"{API_BASE}/feedback/training/submit/", + json.dumps(training_feedback_payload), + headers=self.json_type_header, + ) + # submit unfished/unpublished training feedback should not pass + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + def test_publish_training(self): + training = TrainingFactory(model=self.model, created_by=self.user) + + # publish an unfinished training should not pass + + res = self.client.post( + f"{API_BASE}/training/publish/{training.id}/", headers=headersList + ) + self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND) + + def test_get_GpxView(self): + training = TrainingFactory(model=self.model, created_by=self.user) + feedbackAoi = FeedbackAoiFactory(training=training, user=self.user) + + # generate aoi GPX view - aoi_id + + res = self.client.get(f"{API_BASE}/aoi/gpx/{self.aoi.id}/", headers=headersList) + self.assertEqual(res.status_code, status.HTTP_200_OK) + + # generate feedback aoi GPX view - feedback aoi_id + + res = self.client.get( + f"{API_BASE}/feedback-aoi/gpx/{feedbackAoi.id}/", headers=headersList + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) + + def test_get_workspace(self): + # get training workspace + + res = self.client.get(f"{API_BASE}/workspace/", headers=headersList) self.assertEqual(res.status_code, status.HTTP_201_CREATED) - # test diff --git a/backend/tests/test_views.py b/backend/tests/test_views.py new file mode 100644 index 00000000..991ae1bd --- /dev/null +++ b/backend/tests/test_views.py @@ -0,0 +1,17 @@ +from django.test import TestCase +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APIClient + +BASE_URL = "http://testserver/api" + + +class CoreViewsTest(TestCase): + def setUp(self): + self.client = APIClient() + self.home_url = f"{BASE_URL}/" + + def test_home_redirect(self): + res = self.client.get(self.home_url) + self.assertEqual(res.status_code, status.HTTP_302_FOUND) + self.assertRedirects(res, reverse("schema-swagger-ui")) From 47f59fec4a3119773f8753c5bc0b7e44bc602ec9 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:38:09 +0100 Subject: [PATCH 04/32] test(exclude-coverage): omit everything in /usr from coverage --- backend/pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 3c813601..e1eb0f5b 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -46,6 +46,7 @@ distribution = true dev = [ "commitizen>=3.27.0", "ruff>=0.4.9", + "coverage>=7.6.0", ] [tool.commitizen] @@ -54,3 +55,8 @@ tag_format = "\"v$version\"" version_scheme = "semver2" version = "1.0.1" update_changelog_on_bump = true + +[tool.coverage.run] +omit = [ + "/usr/*" +] From 0aee788eaa91c03973d4e8d6051898232fdc7833 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:50:56 +0100 Subject: [PATCH 05/32] test(test-backend-build): adds run project tests in Github action workflow to run automatic tests on PR --- .github/workflows/backend_build.yml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 7983b9a6..aff2922b 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -55,10 +55,15 @@ jobs: - name: Unzip and Move Basemodel run: unzip checkpoint.tf.zip -d ramp-code/ramp + - name: Install numpy + run: | + pip install numpy + - name: Install gdal run: | sudo apt-get update && sudo apt-get -y install gdal-bin libgdal-dev python3-gdal && sudo apt-get -y autoremove && sudo apt-get clean pip install GDAL==$(gdal-config --version) + - name: Install ramp dependecies run: | cd ramp-code && cd colab && make install @@ -85,7 +90,8 @@ jobs: - name: Install Dependencies run: | cd backend/ - pip install -r requirements.txt + pip install pdm + pdm install - name: Creating env run: | @@ -104,12 +110,6 @@ jobs: cd backend/ celery -A aiproject --broker=redis://localhost:6379/ flower & - - name: Fix gdal array - run: | - pip uninstall -y gdal - pip install numpy - pip install GDAL==$(gdal-config --version) --global-option=build_ext --global-option="-I/usr/include/gdal" - - name: Check Opencv version run: | pip freeze | grep opencv @@ -121,11 +121,5 @@ jobs: run: | cd backend/ - - export TESTING_TOKEN=$TESTING_TOKEN - python manage.py makemigrations - python manage.py makemigrations core - python manage.py makemigrations login - python manage.py migrate - python manage.py migrate login - python manage.py migrate core + coverage run manage.py test tests + coverage report From f86dc4d7249244ab56feafe520e6fb5c6caf44d1 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:06:37 +0100 Subject: [PATCH 06/32] test(fix-backend-build): fixes install dependecies failing --- .github/workflows/backend_build.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index aff2922b..06abd1b0 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -55,15 +55,10 @@ jobs: - name: Unzip and Move Basemodel run: unzip checkpoint.tf.zip -d ramp-code/ramp - - name: Install numpy - run: | - pip install numpy - - name: Install gdal run: | sudo apt-get update && sudo apt-get -y install gdal-bin libgdal-dev python3-gdal && sudo apt-get -y autoremove && sudo apt-get clean pip install GDAL==$(gdal-config --version) - - name: Install ramp dependecies run: | cd ramp-code && cd colab && make install @@ -110,6 +105,12 @@ jobs: cd backend/ celery -A aiproject --broker=redis://localhost:6379/ flower & + - name: Fix gdal array + run: | + pip uninstall -y gdal + pip install numpy + pip install GDAL==$(gdal-config --version) --global-option=build_ext --global-option="-I/usr/include/gdal" + - name: Check Opencv version run: | pip freeze | grep opencv @@ -121,5 +122,7 @@ jobs: run: | cd backend/ + + export TESTING_TOKEN=$TESTING_TOKEN coverage run manage.py test tests coverage report From 9c0e5ff2d78437857bf59240ba387ecc547e2f51 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:20:51 +0100 Subject: [PATCH 07/32] test(backend-build): restores install project dependencies --- .github/workflows/backend_build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 06abd1b0..b0e23575 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -85,8 +85,7 @@ jobs: - name: Install Dependencies run: | cd backend/ - pip install pdm - pdm install + pip install -r requirements.txt - name: Creating env run: | From 656e26858d1fdac4d9de8bac66f9bb5104d4aa9c Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:19:29 +0100 Subject: [PATCH 08/32] test(backend-build): adds install coverage --- .github/workflows/backend_build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index b0e23575..f0fe307b 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -121,6 +121,7 @@ jobs: run: | cd backend/ + pip install coverage export TESTING_TOKEN=$TESTING_TOKEN coverage run manage.py test tests From 7b8ac72aba11e876a7d05f29af6723483b3b49cb Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:56:48 +0100 Subject: [PATCH 09/32] test(github-action): restores makemigrations --- .github/workflows/backend_build.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index f0fe307b..992e1eac 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -121,8 +121,15 @@ jobs: run: | cd backend/ - pip install coverage export TESTING_TOKEN=$TESTING_TOKEN + python manage.py makemigrations + python manage.py makemigrations core + python manage.py makemigrations login + python manage.py migrate + python manage.py migrate login + python manage.py migrate core + + pip install coverage coverage run manage.py test tests coverage report From 7fd9f858fd8709b20ce14d532002207e77d972f2 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:14:49 +0100 Subject: [PATCH 10/32] test(github-action): adds install factory boy --- .github/workflows/backend_build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 992e1eac..c27c7675 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -131,5 +131,6 @@ jobs: python manage.py migrate core pip install coverage + pip install factory-boy coverage run manage.py test tests coverage report From c66dcbc04eaedfd3cb7ab5a5b6eb660b6053fb66 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:31:33 +0100 Subject: [PATCH 11/32] test(gothub-action): sets TESTING_TOKEN --- .github/workflows/backend_build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index c27c7675..ecbc3337 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -91,6 +91,7 @@ jobs: run: | cd backend/ mv sample_env .env + sed -i 's/TESTING_TOKEN = .*/TESTING_TOKEN=${{ secrets.TESTING_TOKEN }}/' .env export DATABASE_URL=postgis://admin:password@localhost:5432/ai export RAMP_HOME="/home/runner/work/fAIr/fAIr" export TRAINING_WORKSPACE="/home/runner/work/fAIr/fAIr/backend/training" From 8110271e3524ef4fcf183f926f2a666ee056271e Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 26 Jul 2024 14:31:23 +0100 Subject: [PATCH 12/32] test(github-action): add OSM client id, secret, secret key --- .github/workflows/backend_build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index ecbc3337..900ba2b0 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -119,6 +119,9 @@ jobs: - name: Run tests env: TESTING_TOKEN: ${{ secrets.TESTING_TOKEN }} + OSM_CLIENT_ID: ${{ secrets.OSM_CLIENT_ID}} + OSM_CLIENT_SECRET: ${{ secrets.OSM_CLIENT_SECRET}} + OSM_SECRET_KEY: ${{ secrets.OSM_SECRET_KEY}} run: | cd backend/ From 4c3a4abfd03f8ae985179706c5ac4db3e8a9a79a Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 26 Jul 2024 19:13:35 +0100 Subject: [PATCH 13/32] test(github-action): updates env setting --- .github/workflows/backend_build.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 900ba2b0..22a47605 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -87,11 +87,8 @@ jobs: cd backend/ pip install -r requirements.txt - - name: Creating env + - name: Set environment variables run: | - cd backend/ - mv sample_env .env - sed -i 's/TESTING_TOKEN = .*/TESTING_TOKEN=${{ secrets.TESTING_TOKEN }}/' .env export DATABASE_URL=postgis://admin:password@localhost:5432/ai export RAMP_HOME="/home/runner/work/fAIr/fAIr" export TRAINING_WORKSPACE="/home/runner/work/fAIr/fAIr/backend/training" @@ -125,8 +122,13 @@ jobs: run: | cd backend/ - export TESTING_TOKEN=$TESTING_TOKEN + export OSM_CLIENT_ID=$OSM_CLIENT_ID + export OSM_CLIENT_SECRET=$OSM_CLIENT_SECRET + export OSM_SECRET_KEY=$OSM_SECRET_KEY + + echo "TESTING_TOKEN starts with: ${TESTING_TOKEN:0:6}..." + python manage.py makemigrations python manage.py makemigrations core python manage.py makemigrations login From 567fbef1688c4b27f84e26a23068d1d20fca319f Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:29:37 +0100 Subject: [PATCH 14/32] test(github-action): updates env variables --- .github/workflows/backend_build.yml | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 22a47605..48b5ae53 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -38,11 +38,6 @@ jobs: - name: Get my current working dir run: pwd - - name: Test env vars for python - env: - TESTING_TOKEN: ${{ secrets.TESTING_TOKEN }} - run: python -c "import os; print(os.environ['TESTING_TOKEN'])" - - name: Clone Ramp run: git clone https://github.com/kshitijrajsharma/ramp-code-fAIr.git ramp-code @@ -87,11 +82,11 @@ jobs: cd backend/ pip install -r requirements.txt - - name: Set environment variables - run: | - export DATABASE_URL=postgis://admin:password@localhost:5432/ai - export RAMP_HOME="/home/runner/work/fAIr/fAIr" - export TRAINING_WORKSPACE="/home/runner/work/fAIr/fAIr/backend/training" + - name: Creating env + env: + DATABASE_URL: postgis://admin:password@localhost:5432/ai + RAMP_HOME: "/home/runner/work/fAIr/fAIr" + TRAINING_WORKSPACE: "/home/runner/work/fAIr/fAIr/backend/training" - name: Run celery worker run: | @@ -127,8 +122,6 @@ jobs: export OSM_CLIENT_SECRET=$OSM_CLIENT_SECRET export OSM_SECRET_KEY=$OSM_SECRET_KEY - echo "TESTING_TOKEN starts with: ${TESTING_TOKEN:0:6}..." - python manage.py makemigrations python manage.py makemigrations core python manage.py makemigrations login From 595639bd846a7f94740284baee7b19f19baac814 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:38:42 +0100 Subject: [PATCH 15/32] test(github-action): updates env setting --- .github/workflows/backend_build.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 48b5ae53..a27c7fd7 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -38,6 +38,11 @@ jobs: - name: Get my current working dir run: pwd + - name: Test env vars for python + env: + TESTING_TOKEN: ${{ secrets.TESTING_TOKEN }} + run: python -c "import os; print(os.environ['TESTING_TOKEN'])" + - name: Clone Ramp run: git clone https://github.com/kshitijrajsharma/ramp-code-fAIr.git ramp-code @@ -83,10 +88,16 @@ jobs: pip install -r requirements.txt - name: Creating env - env: - DATABASE_URL: postgis://admin:password@localhost:5432/ai - RAMP_HOME: "/home/runner/work/fAIr/fAIr" - TRAINING_WORKSPACE: "/home/runner/work/fAIr/fAIr/backend/training" + run: | + cd backend/ + mv sample_env .env + sed -i 's/TESTING_TOKEN = .*/TESTING_TOKEN=${{ secrets.TESTING_TOKEN }}/' .env + sed -i 's/OSM_CLIENT_ID = .*/OSM_CLIENT_ID=${{ secrets.OSM_CLIENT_ID }}/' .env + sed -i 's/OSM_CLIENT_SECRET = .*/OSM_CLIENT_SECRET=${{ secrets.OSM_CLIENT_SECRET }}/' .env + sed -i 's/OSM_SECRET_KEY = .*/OSM_SECRET_KEY=${{ secrets.OSM_SECRET_KEY }}/' .env + export DATABASE_URL=postgis://admin:password@localhost:5432/ai + export RAMP_HOME="/home/runner/work/fAIr/fAIr" + export TRAINING_WORKSPACE="/home/runner/work/fAIr/fAIr/backend/training" - name: Run celery worker run: | From a1ab09c6dd7e84c975ec3dd63e1c664d20f7c607 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:02:38 +0100 Subject: [PATCH 16/32] test(github-action): updates env variables --- .github/workflows/backend_build.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index a27c7fd7..02f2bb9b 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -87,17 +87,17 @@ jobs: cd backend/ pip install -r requirements.txt - - name: Creating env + - name: Setting env vaariables + env: + DATABASE_URL: postgis://admin:password@localhost:5432/ai + RAMP_HOME: "/home/runner/work/fAIr/fAIr" + TRAINING_WORKSPACE: "/home/runner/work/fAIr/fAIr/backend/training" run: | cd backend/ - mv sample_env .env - sed -i 's/TESTING_TOKEN = .*/TESTING_TOKEN=${{ secrets.TESTING_TOKEN }}/' .env - sed -i 's/OSM_CLIENT_ID = .*/OSM_CLIENT_ID=${{ secrets.OSM_CLIENT_ID }}/' .env - sed -i 's/OSM_CLIENT_SECRET = .*/OSM_CLIENT_SECRET=${{ secrets.OSM_CLIENT_SECRET }}/' .env - sed -i 's/OSM_SECRET_KEY = .*/OSM_SECRET_KEY=${{ secrets.OSM_SECRET_KEY }}/' .env - export DATABASE_URL=postgis://admin:password@localhost:5432/ai - export RAMP_HOME="/home/runner/work/fAIr/fAIr" - export TRAINING_WORKSPACE="/home/runner/work/fAIr/fAIr/backend/training" + + export DATABASE_URL=$DATABASE_URL + export RAMP_HOME=$RAMP_HOME + export TRAINING_WORKSPACE=$TRAINING_WORKSPACE - name: Run celery worker run: | From c5f939823479a9655bbfa777892cbe3dc7cadfaf Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:37:21 +0100 Subject: [PATCH 17/32] test(exclude-coverage): omit everything in /usr and excludes callback function from coverage --- backend/login/views.py | 2 +- backend/pyproject.toml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/login/views.py b/backend/login/views.py index bb75c9d2..321af752 100644 --- a/backend/login/views.py +++ b/backend/login/views.py @@ -38,7 +38,7 @@ def get(self, request, format=None): class callback(APIView): - def get(self, request, format=None): + def get(self, request, format=None): # pragma: no cover """Callback method redirected from osm callback method Args: diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 3c813601..e1eb0f5b 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -46,6 +46,7 @@ distribution = true dev = [ "commitizen>=3.27.0", "ruff>=0.4.9", + "coverage>=7.6.0", ] [tool.commitizen] @@ -54,3 +55,8 @@ tag_format = "\"v$version\"" version_scheme = "semver2" version = "1.0.1" update_changelog_on_bump = true + +[tool.coverage.run] +omit = [ + "/usr/*" +] From a9a987bad7e2642df1d4500916a3e1b49a936d33 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:39:33 +0100 Subject: [PATCH 18/32] test(model-factories): adds model factories to isolate test data --- backend/tests/factories.py | 137 +++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 backend/tests/factories.py diff --git a/backend/tests/factories.py b/backend/tests/factories.py new file mode 100644 index 00000000..829c1b4e --- /dev/null +++ b/backend/tests/factories.py @@ -0,0 +1,137 @@ +import factory +from login.models import OsmUser +from django.contrib.gis.geos import Polygon +from core.models import ( + Dataset, + AOI, + Label, + Model, + Training, + Feedback, + FeedbackAOI, + FeedbackLabel, +) + + +class OsmUserFactory(factory.django.DjangoModelFactory): + class Meta: + model = OsmUser + + osm_id = 123456 + + +class DatasetFactory(factory.django.DjangoModelFactory): + class Meta: + model = Dataset + + name = "My test dataset" + source_imagery = "https://tiles.openaerialmap.org/5ac4fc6f26964b0010033112/0/5ac4fc6f26964b0010033113/{z}/{x}/{y}" + created_by = factory.SubFactory(OsmUserFactory) + + +class AoiFactory(factory.django.DjangoModelFactory): + class Meta: + model = AOI + + geom = Polygon( + ( + (32.588507094820351, 0.348666499011499), + (32.588517512656978, 0.348184682976698), + (32.588869114643053, 0.348171660921362), + (32.588840465592334, 0.348679521066151), + (32.588507094820351, 0.348666499011499), + ) + ) + dataset = factory.SubFactory(DatasetFactory) + + +class LabelFactory(factory.django.DjangoModelFactory): + class Meta: + model = Label + + aoi = factory.SubFactory(AoiFactory) + geom = Polygon( + ( + (32.588507094820351, 0.348666499011499), + (32.588517512656978, 0.348184682976698), + (32.588869114643053, 0.348171660921362), + (32.588840465592334, 0.348679521066151), + (32.588507094820351, 0.348666499011499), + ) + ) + + +class ModelFactory(factory.django.DjangoModelFactory): + class Meta: + model = Model + + dataset = factory.SubFactory(DatasetFactory) + name = "My test model" + created_by = factory.SubFactory(OsmUserFactory) + + +class TrainingFactory(factory.django.DjangoModelFactory): + class Meta: + model = Training + + model = factory.SubFactory(ModelFactory) + description = "My very first training" + created_by = factory.SubFactory(OsmUserFactory) + epochs = 1 + zoom_level = [20, 21] + batch_size = 1 + + +class FeedbackFactory(factory.django.DjangoModelFactory): + class Meta: + model = Feedback + + geom = Polygon( + ( + (32.588507094820351, 0.348666499011499), + (32.588517512656978, 0.348184682976698), + (32.588869114643053, 0.348171660921362), + (32.588840465592334, 0.348679521066151), + (32.588507094820351, 0.348666499011499), + ) + ) + training = factory.SubFactory(TrainingFactory) + zoom_level = 19 + feedback_type = "TP" + user = factory.SubFactory(OsmUserFactory) + source_imagery = "https://tiles.openaerialmap.org/5ac4fc6f26964b0010033112/0/5ac4fc6f26964b0010033113/{z}/{x}/{y}" + + +class FeedbackAoiFactory(factory.django.DjangoModelFactory): + class Meta: + model = FeedbackAOI + + training = factory.SubFactory(TrainingFactory) + geom = Polygon( + ( + (32.588507094820351, 0.348666499011499), + (32.588517512656978, 0.348184682976698), + (32.588869114643053, 0.348171660921362), + (32.588840465592334, 0.348679521066151), + (32.588507094820351, 0.348666499011499), + ) + ) + label_status = -1 + source_imagery = "https://tiles.openaerialmap.org/5ac4fc6f26964b0010033112/0/5ac4fc6f26964b0010033113/{z}/{x}/{y}" + user = factory.SubFactory(OsmUserFactory) + + +class FeedbackLabelFactory(factory.django.DjangoModelFactory): + class Meta: + model = FeedbackLabel + + feedback_aoi = factory.SubFactory(FeedbackAoiFactory) + geom = Polygon( + ( + (32.588507094820351, 0.348666499011499), + (32.588517512656978, 0.348184682976698), + (32.588869114643053, 0.348171660921362), + (32.588840465592334, 0.348679521066151), + (32.588507094820351, 0.348666499011499), + ) + ) From 970b06c49ace3ca3183d4ca62ad96084b97eadd8 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:43:18 +0100 Subject: [PATCH 19/32] ci(backend_build): updates requirements for tests --- .github/workflows/backend_build.yml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 7983b9a6..09f3a993 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -1,4 +1,4 @@ -name: Backend Build +name: Backend Build and Tests on: push: branches: @@ -86,8 +86,10 @@ jobs: run: | cd backend/ pip install -r requirements.txt + pip install coverage + pip install factory-boy - - name: Creating env + - name: Create env run: | cd backend/ mv sample_env .env @@ -115,17 +117,25 @@ jobs: pip freeze | grep opencv pip install opencv-python-headless==4.7.0.68 - - name: Run tests + - name: Run migrations env: TESTING_TOKEN: ${{ secrets.TESTING_TOKEN }} run: | cd backend/ - - - export TESTING_TOKEN=$TESTING_TOKEN python manage.py makemigrations python manage.py makemigrations core python manage.py makemigrations login python manage.py migrate python manage.py migrate login python manage.py migrate core + + - name : Run tests + env : + TESTING_TOKEN: ${{ secrets.TESTING_TOKEN }} + OSM_CLIENT_ID: ${{ secrets.OSM_CLIENT_ID }} + OSM_CLIENT_SECRET: ${{ secrets.OSM_CLIENT_SECRET }} + OSM_SECRET_KEY: "" + run : | + cd backend/ + coverage run manage.py test tests + coverage report From fa4aa61bde3aa43bff5494bf972502120d1979df Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:52:13 +0100 Subject: [PATCH 20/32] test(endpoints-&-view): adds tests for the endpoints and home view - redirect --- backend/tests/test_endpoints.py | 307 ++++++++++++++++++++++++++------ backend/tests/test_views.py | 17 ++ 2 files changed, 272 insertions(+), 52 deletions(-) create mode 100644 backend/tests/test_views.py diff --git a/backend/tests/test_endpoints.py b/backend/tests/test_endpoints.py index a0447590..a20a3f84 100644 --- a/backend/tests/test_endpoints.py +++ b/backend/tests/test_endpoints.py @@ -1,10 +1,20 @@ import json import os +import shutil -import validators from django.conf import settings +import validators from rest_framework import status from rest_framework.test import APILiveServerTestCase, RequestsClient +from .factories import ( + OsmUserFactory, + TrainingFactory, + DatasetFactory, + AoiFactory, + LabelFactory, + ModelFactory, + FeedbackAoiFactory, +) API_BASE = "http://testserver/api/v1" @@ -19,6 +29,12 @@ class TaskApiTest(APILiveServerTestCase): def setUp(self): # Create a request factory instance self.client = RequestsClient() + self.user = OsmUserFactory(osm_id=123) + self.dataset = DatasetFactory(created_by=self.user) + self.aoi = AoiFactory(dataset=self.dataset) + self.model = ModelFactory(dataset=self.dataset, created_by=self.user) + self.json_type_header = headersList.copy() + self.json_type_header["content-type"] = "application/json" def test_auth_me(self): res = self.client.get(f"{API_BASE}/auth/me/", headers=headersList) @@ -32,9 +48,11 @@ def test_auth_login(self): self.assertEqual(validators.url(res_body["login_url"]), True) def test_create_dataset(self): + # create dataset + payload = { - "name": "My test dataset", - "source_imagery": "https://tiles.openaerialmap.org/5ac4fc6f26964b0010033112/0/5ac4fc6f26964b0010033113/{z}/{x}/{y}", + "name": self.dataset.name, + "source_imagery": self.dataset.source_imagery, } # test without authentication should be forbidden res = self.client.post(f"{API_BASE}/dataset/", payload) @@ -43,22 +61,11 @@ def test_create_dataset(self): res = self.client.post(f"{API_BASE}/dataset/", payload, headers=headersList) self.assertEqual(res.status_code, status.HTTP_201_CREATED) - # now dataset is created , create first aoi inside it - payload_second = { - "geom": { - "type": "Polygon", - "coordinates": [ - [ - [32.588507094820351, 0.348666499011499], - [32.588517512656978, 0.348184682976698], - [32.588869114643053, 0.348171660921362], - [32.588840465592334, 0.348679521066151], - [32.588507094820351, 0.348666499011499], - ] - ], - }, - "dataset": 1, - } + def test_create_training(self): + # now dataset is created, create first aoi inside it + + payload_second = {"geom": self.aoi.geom.json, "dataset": self.dataset.id} + json_type_header = headersList json_type_header["content-type"] = "application/json" res = self.client.post( @@ -66,81 +73,277 @@ def test_create_dataset(self): ) self.assertEqual(res.status_code, status.HTTP_201_CREATED) - # create second aoi too , to test multiple aois + # create second aoi too, to test multiple aois + payload_third = { - "geom": { - "type": "Polygon", - "coordinates": [ - [ - [32.588046105549715, 0.349843692679227], - [32.588225813231475, 0.349484284008701], - [32.588624295482369, 0.349734307433132], - [32.588371662944233, 0.350088507273009], - [32.588046105549715, 0.349843692679227], - ] - ], - }, - "dataset": 1, + "geom": self.aoi.geom.json, + "dataset": self.dataset.id, } res = self.client.post( f"{API_BASE}/aoi/", json.dumps(payload_third), headers=json_type_header ) self.assertEqual(res.status_code, status.HTTP_201_CREATED) + # create model + + model_payload = {"name": self.model.name, "dataset": self.dataset.id} + res = self.client.post( + f"{API_BASE}/model/", json.dumps(model_payload), headers=json_type_header + ) + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + + # create training without label + + training_payload = { + "description": "My very first training", + "epochs": 1, + "zoom_level": [20, 21], + "batch_size": 1, + "model": self.model.id, + } + res = self.client.post( + f"{API_BASE}/training/", + json.dumps(training_payload), + headers=json_type_header, + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + # download labels from osm for 1 res = self.client.post( - f"{API_BASE}/label/osm/fetch/1/", "", headers=headersList + f"{API_BASE}/label/osm/fetch/{self.aoi.id}/", "", headers=headersList ) self.assertEqual(res.status_code, status.HTTP_201_CREATED) # download labels from osm for 2 res = self.client.post( - f"{API_BASE}/label/osm/fetch/2/", "", headers=headersList + f"{API_BASE}/label/osm/fetch/{self.aoi.id}/", "", headers=headersList ) self.assertEqual(res.status_code, status.HTTP_201_CREATED) - # build the dataset + # create training with epochs greater than the limit - build_dt_payload = {"dataset_id": 1, "zoom_level": ["19"]} + training_payload = { + "description": "My very first training", + "epochs": 31, + "zoom_level": [20, 21], + "batch_size": 1, + "model": self.model.id, + } res = self.client.post( - f"{API_BASE}/dataset/image/build/", - json.dumps(build_dt_payload), + f"{API_BASE}/training/", + json.dumps(training_payload), headers=json_type_header, ) - self.assertEqual(res.status_code, status.HTTP_201_CREATED) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) - # build dataset on multiple zoom levels + # create training with batch size greater than the limit - build_dt_payload = {"dataset_id": 1, "zoom_level": ["19", "20"]} + training_payload = { + "description": "My very first training", + "epochs": 1, + "zoom_level": [20, 21], + "batch_size": 9, + "model": self.model.id, + } res = self.client.post( - f"{API_BASE}/dataset/image/build/", - json.dumps(build_dt_payload), + f"{API_BASE}/training/", + json.dumps(training_payload), headers=json_type_header, ) - self.assertEqual(res.status_code, status.HTTP_201_CREATED) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) - # create model + # create training inside model - model_payload = {"name": "My test model", "dataset": 1} + training_payload = { + "description": "My very first training", + "epochs": 1, + "zoom_level": [20, 21], + "batch_size": 1, + "model": self.model.id, + } res = self.client.post( - f"{API_BASE}/model/", json.dumps(model_payload), headers=json_type_header + f"{API_BASE}/training/", + json.dumps(training_payload), + headers=json_type_header, ) self.assertEqual(res.status_code, status.HTTP_201_CREATED) - # create training inside model + # create another training for the same model + training_payload = { "description": "My very first training", "epochs": 1, + "zoom_level": [20, 21], "batch_size": 1, - "model": 1, + "model": self.model.id, } res = self.client.post( f"{API_BASE}/training/", json.dumps(training_payload), headers=json_type_header, ) - print(res.json()) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + self.training = TrainingFactory(model=self.model, created_by=self.user) + + def test_create_label(self): + self.label = LabelFactory(aoi=self.aoi) + self.training = TrainingFactory(model=self.model, created_by=self.user) + + # create label + + label_payload = { + "geom": self.label.geom.json, + "aoi": self.aoi.id, + } + + res = self.client.post( + f"{API_BASE}/label/", + json.dumps(label_payload), + headers=self.json_type_header, + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) # 201- for create + + # create another label with the same geom and aoi + + label_payload2 = { + "geom": self.label.geom.json, + "aoi": self.aoi.id, + } + + res = self.client.post( + f"{API_BASE}/label/", + json.dumps(label_payload2), + headers=self.json_type_header, + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) # 200- for update + + # create another label with error + + label_payload3 = { + "geom": self.label.geom.json, + "aoi": 40, # non-existent aoi + } + res = self.client.post( + f"{API_BASE}/label/", + json.dumps(label_payload3), + headers=self.json_type_header, + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + def test_fetch_feedbackAoi_osm_label(self): + # create feedback aoi + training = TrainingFactory(model=self.model, created_by=self.user) + feedbackAoi = FeedbackAoiFactory(training=training, user=self.user) + + # download available osm data as labels for the feedback aoi + + res = self.client.post( + f"{API_BASE}/label/feedback/osm/fetch/{feedbackAoi.id}/", + "", + headers=headersList, + ) self.assertEqual(res.status_code, status.HTTP_201_CREATED) - # test + + def test_get_runStatus(self): + training = TrainingFactory(model=self.model, created_by=self.user) + + # get running training status + + res = self.client.get( + f"{API_BASE}/training/status/{training.id}/", headers=headersList + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) + + def test_submit_training_feedback(self): + training = TrainingFactory(model=self.model, created_by=self.user) + + # apply feedback to training published checkpoints + + training_feedback_payload = { + "training_id": training.id, + "epochs": 20, + "batch_size": 8, + "zoom_level": [19, 20], + } + res = self.client.post( + f"{API_BASE}/feedback/training/submit/", + json.dumps(training_feedback_payload), + headers=self.json_type_header, + ) + # submit unfinished/unpublished training feedback should not pass + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + def test_publish_training(self): + training = TrainingFactory(model=self.model, created_by=self.user) + + # publish an unfinished training should not pass + + res = self.client.post( + f"{API_BASE}/training/publish/{training.id}/", headers=headersList + ) + self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND) + + def test_get_GpxView(self): + training = TrainingFactory(model=self.model, created_by=self.user) + feedbackAoi = FeedbackAoiFactory(training=training, user=self.user) + + # generate aoi GPX view - aoi_id + + res = self.client.get(f"{API_BASE}/aoi/gpx/{self.aoi.id}/", headers=headersList) + self.assertEqual(res.status_code, status.HTTP_200_OK) + + # generate feedback aoi GPX view - feedback aoi_id + + res = self.client.get( + f"{API_BASE}/feedback-aoi/gpx/{feedbackAoi.id}/", headers=headersList + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) + + def test_get_workspace(self): + # get training workspace + + res = self.client.get(f"{API_BASE}/workspace/", headers=headersList) + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + + def test_download_workspace(self): + try: + lookup_dir = "test_dir" + + # download non-existent dir should fail + res = self.client.get( + f"{API_BASE}/workspace/download/{lookup_dir}", headers=headersList + ) + self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND) + + # test download workspace + base_dir = os.path.join(settings.TRAINING_WORKSPACE, lookup_dir) + os.makedirs(base_dir) + + with open(os.path.join(base_dir, "file.txt"), "wb") as f: + f.write(b"Test file") + + res = self.client.get( + f"{API_BASE}/workspace/download/{lookup_dir}", + headers=headersList, + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) + + # test download file greater than the 200 mb limit + + with open(os.path.join(base_dir, "large_file.txt"), "wb") as f: + f.seek(201 * 1024**2) + f.write(b"\0") + + # download file size greater than limit should fail + res = self.client.get( + f"{API_BASE}/workspace/download/{lookup_dir}", + headers=headersList, + ) + self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN) + + finally: + # clean up + shutil.rmtree(base_dir) diff --git a/backend/tests/test_views.py b/backend/tests/test_views.py new file mode 100644 index 00000000..991ae1bd --- /dev/null +++ b/backend/tests/test_views.py @@ -0,0 +1,17 @@ +from django.test import TestCase +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APIClient + +BASE_URL = "http://testserver/api" + + +class CoreViewsTest(TestCase): + def setUp(self): + self.client = APIClient() + self.home_url = f"{BASE_URL}/" + + def test_home_redirect(self): + res = self.client.get(self.home_url) + self.assertEqual(res.status_code, status.HTTP_302_FOUND) + self.assertRedirects(res, reverse("schema-swagger-ui")) From 8050dcc5d2f237f7fa02714ec697b50d88ed0e34 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:46:23 +0100 Subject: [PATCH 21/32] ci(backend-build): updates requirement to run tests --- .github/workflows/backend_build.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index bb697925..09f3a993 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -92,10 +92,10 @@ jobs: - name: Create env run: | cd backend/ - - export DATABASE_URL=$DATABASE_URL - export RAMP_HOME=$RAMP_HOME - export TRAINING_WORKSPACE=$TRAINING_WORKSPACE + mv sample_env .env + export DATABASE_URL=postgis://admin:password@localhost:5432/ai + export RAMP_HOME="/home/runner/work/fAIr/fAIr" + export TRAINING_WORKSPACE="/home/runner/work/fAIr/fAIr/backend/training" - name: Run celery worker run: | @@ -120,9 +120,6 @@ jobs: - name: Run migrations env: TESTING_TOKEN: ${{ secrets.TESTING_TOKEN }} - OSM_CLIENT_ID: ${{ secrets.OSM_CLIENT_ID}} - OSM_CLIENT_SECRET: ${{ secrets.OSM_CLIENT_SECRET}} - OSM_SECRET_KEY: ${{ secrets.OSM_SECRET_KEY}} run: | cd backend/ python manage.py makemigrations From 79848db05504c4e590f65d33b18b2813e6ce3766 Mon Sep 17 00:00:00 2001 From: kshitijrajsharma Date: Wed, 31 Jul 2024 07:35:45 +0545 Subject: [PATCH 22/32] Adds testing to backend build ci --- .github/workflows/backend_build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 09f3a993..7e879cd0 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -135,6 +135,7 @@ jobs: OSM_CLIENT_ID: ${{ secrets.OSM_CLIENT_ID }} OSM_CLIENT_SECRET: ${{ secrets.OSM_CLIENT_SECRET }} OSM_SECRET_KEY: "" + run : | cd backend/ coverage run manage.py test tests From db620de62cc275011a34ab169632d12862600e1d Mon Sep 17 00:00:00 2001 From: kshitijrajsharma Date: Wed, 31 Jul 2024 19:54:07 +0545 Subject: [PATCH 23/32] refactor(backend_build): removes the run from test branch --- backend/.DS_Store | Bin 0 -> 8196 bytes backend/core/.DS_Store | Bin 0 -> 6148 bytes backend/login/.DS_Store | Bin 0 -> 6148 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 backend/.DS_Store create mode 100644 backend/core/.DS_Store create mode 100644 backend/login/.DS_Store diff --git a/backend/.DS_Store b/backend/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7b316faa4cfd5335544b8f2d5b8f67383bc22082 GIT binary patch literal 8196 zcmeHMJ#W-N5S_g<CUAu^; zp)k(1RyzL9Rak>~qMRPln1*yXoz%SbhAN;6r~;~hDxeDd3ku+!&864!-gl_Bs(>nR zCKcfQAw^}Id0RSbw+=M+2mqVlwk^2EIl#uG-e%sG&e~A)=}r&EhZ=vxFfJT;%I>h4 zx23Z#oQw-6=p%}}KxYW9n$(+?z6;K803h>$e0uAUP6||7&_pRMwJ}X>6|9P|5 zEbsUCr};imUVhK6U;cQb&2w}@&*Fh(K0%^#dc;`2C=Igq*bNn#QI*G)GT zRcypv!zHiZHM@B3IX#uE=GK*8VD(7IS-$k+U!hX*7?phHRflAuVBp=ZZZ}c7s z`DpnJ6)O2+RPqtMX1;Td4|9JWa^Btx(TI0B&qaKsZi7d_@7_wQSaUfY&>>BDcH?RT zBR#<11DQ|qZSOtuImSE6@2ww)VG^0XhOTXXTlUS77Qkg2Y zMY#%>yk?D;JjRWAS+{US(CW_+jkuTdMZ`z!4IYx;|F-FYd13d=InSMN#&$lxJ3L>H+d|_F%r~;=_pzFNX`TW0wxvk#;P9>i@s{;Rp0w(G2_P6mU z{%l=)DE&M3W_i8}Pqocd zfZen!wauKq&RP{v1y(2^`-6cC1|AcKcI!Z6uK>U@x~<_^?h=w?JO&;UhZupeO9i@A z_E!vc>F75;F7TK*bm_$Y^1=Sg>~AP+zmEFGh7$)4wN(XFfvf^0_ga_p|ET!>pH0#? zRX`Q^R|;@BY=(!Jl0RE>lasU7z)#>}5|=o%DQNt0Y#-z(-h*4in2`ry;4yKC9+>_J MSQ)fY1%6e5FIP5$)&Kwi literal 0 HcmV?d00001 diff --git a/backend/login/.DS_Store b/backend/login/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ae16fafc9c71591713f722c7a42d731b4740447e GIT binary patch literal 6148 zcmeHKOHRW;4E2;Q0$tD*f(25p5Qzf}RX9Np5NLv0q=ZC#7VPG3uwucIJHR0j&*M?s zC`}O?R3TgPyotx2IB%qJOhjh#SR_PaB8s4lgMADegx6U+G6)u4G=7hqt|+GkT~pTa zR>Lkbz-KqYX=T*V6V7gRepxwBrg>d8lJ-f_?^)GpIxFiLJfdfllt-U0=i9uCUwAi* z^(-8=r30$yhURpKY#%VI>6Y%>*`B`j`K{;rdUp8!_2F~fxMx+bGX2T7ou}nIRg?i` zKpEIo22itEqLHAE%78MU3@jPo`yqxh+JH{bUmX~H3jiF#>;-e~B{(JyXahPytU!!} z0wvUh7Q;w5>~Zk30iB?PlL^g-32!!`LoxpC*gy8}WUQc$%78M^W?)Yq$6Ws}`uqQO zkUl8`%D}&3zzoxAI>9G}+S>UzuC+1r63W7UPH-85i7&b OkASB^2W8+-8Tbac>RN&T literal 0 HcmV?d00001 From b24353d4b642843c2c1826459bad9364bb2b1aaf Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:43:18 +0100 Subject: [PATCH 24/32] ci(backend_build): updates requirements for tests --- .github/workflows/backend_build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 7e879cd0..09f3a993 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -135,7 +135,6 @@ jobs: OSM_CLIENT_ID: ${{ secrets.OSM_CLIENT_ID }} OSM_CLIENT_SECRET: ${{ secrets.OSM_CLIENT_SECRET }} OSM_SECRET_KEY: "" - run : | cd backend/ coverage run manage.py test tests From ac7fcc8d53d447b7be6a9d0e6e9c1397146531e7 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:52:13 +0100 Subject: [PATCH 25/32] test(endpoints-&-view): adds tests for the endpoints and home view - redirect --- backend/tests/test_endpoints.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backend/tests/test_endpoints.py b/backend/tests/test_endpoints.py index af60fa1a..a20a3f84 100644 --- a/backend/tests/test_endpoints.py +++ b/backend/tests/test_endpoints.py @@ -132,8 +132,6 @@ def test_create_training(self): "model": self.model.id, } res = self.client.post( - f"{API_BASE}/training/", - json.dumps(training_payload), f"{API_BASE}/training/", json.dumps(training_payload), headers=json_type_header, @@ -150,8 +148,6 @@ def test_create_training(self): "model": self.model.id, } res = self.client.post( - f"{API_BASE}/training/", - json.dumps(training_payload), f"{API_BASE}/training/", json.dumps(training_payload), headers=json_type_header, @@ -172,7 +168,6 @@ def test_create_training(self): json.dumps(training_payload), headers=json_type_header, ) - print(res.json()) self.assertEqual(res.status_code, status.HTTP_201_CREATED) # create another training for the same model From 3716bc67b5074f1bd4fb0bd4873b1dff47fa453e Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Mon, 15 Jul 2024 09:07:27 +0100 Subject: [PATCH 26/32] test(endpoints-&-view): adds tests for the endpoints also adds test for view home - redirect --- backend/tests/test_endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tests/test_endpoints.py b/backend/tests/test_endpoints.py index a20a3f84..c0c4ce5d 100644 --- a/backend/tests/test_endpoints.py +++ b/backend/tests/test_endpoints.py @@ -2,7 +2,6 @@ import os import shutil -from django.conf import settings import validators from rest_framework import status from rest_framework.test import APILiveServerTestCase, RequestsClient @@ -168,6 +167,7 @@ def test_create_training(self): json.dumps(training_payload), headers=json_type_header, ) + print(res.json()) self.assertEqual(res.status_code, status.HTTP_201_CREATED) # create another training for the same model From 56097a891058e22c893ce447e1e090350ea7b730 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:50:56 +0100 Subject: [PATCH 27/32] test(test-backend-build): adds run project tests in Github action workflow to run automatic tests on PR --- .github/workflows/backend_build.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 09f3a993..a1d2453a 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -55,10 +55,15 @@ jobs: - name: Unzip and Move Basemodel run: unzip checkpoint.tf.zip -d ramp-code/ramp + - name: Install numpy + run: | + pip install numpy + - name: Install gdal run: | sudo apt-get update && sudo apt-get -y install gdal-bin libgdal-dev python3-gdal && sudo apt-get -y autoremove && sudo apt-get clean pip install GDAL==$(gdal-config --version) + - name: Install ramp dependecies run: | cd ramp-code && cd colab && make install @@ -106,12 +111,6 @@ jobs: cd backend/ celery -A aiproject --broker=redis://localhost:6379/ flower & - - name: Fix gdal array - run: | - pip uninstall -y gdal - pip install numpy - pip install GDAL==$(gdal-config --version) --global-option=build_ext --global-option="-I/usr/include/gdal" - - name: Check Opencv version run: | pip freeze | grep opencv From 200273bd6d0b0c86f5a3f425c670213ca3eda677 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:06:37 +0100 Subject: [PATCH 28/32] test(fix-backend-build): fixes install dependecies failing --- .github/workflows/backend_build.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index a1d2453a..09f3a993 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -55,15 +55,10 @@ jobs: - name: Unzip and Move Basemodel run: unzip checkpoint.tf.zip -d ramp-code/ramp - - name: Install numpy - run: | - pip install numpy - - name: Install gdal run: | sudo apt-get update && sudo apt-get -y install gdal-bin libgdal-dev python3-gdal && sudo apt-get -y autoremove && sudo apt-get clean pip install GDAL==$(gdal-config --version) - - name: Install ramp dependecies run: | cd ramp-code && cd colab && make install @@ -111,6 +106,12 @@ jobs: cd backend/ celery -A aiproject --broker=redis://localhost:6379/ flower & + - name: Fix gdal array + run: | + pip uninstall -y gdal + pip install numpy + pip install GDAL==$(gdal-config --version) --global-option=build_ext --global-option="-I/usr/include/gdal" + - name: Check Opencv version run: | pip freeze | grep opencv From edd487e84b5fcb2f4376a32cbe41a34c99607221 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 26 Jul 2024 14:31:23 +0100 Subject: [PATCH 29/32] test(github-action): add OSM client id, secret, secret key --- .github/workflows/backend_build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 09f3a993..b47ffc75 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -120,6 +120,9 @@ jobs: - name: Run migrations env: TESTING_TOKEN: ${{ secrets.TESTING_TOKEN }} + OSM_CLIENT_ID: ${{ secrets.OSM_CLIENT_ID}} + OSM_CLIENT_SECRET: ${{ secrets.OSM_CLIENT_SECRET}} + OSM_SECRET_KEY: ${{ secrets.OSM_SECRET_KEY}} run: | cd backend/ python manage.py makemigrations From b2d59f0742ff7df392434447311a9f79b7ae3385 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:29:37 +0100 Subject: [PATCH 30/32] test(github-action): updates env variables --- .github/workflows/backend_build.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index b47ffc75..766d8ee0 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -38,11 +38,6 @@ jobs: - name: Get my current working dir run: pwd - - name: Test env vars for python - env: - TESTING_TOKEN: ${{ secrets.TESTING_TOKEN }} - run: python -c "import os; print(os.environ['TESTING_TOKEN'])" - - name: Clone Ramp run: git clone https://github.com/kshitijrajsharma/ramp-code-fAIr.git ramp-code From 735bcc12c210cddd169797d9e37b5c6812044b46 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:38:42 +0100 Subject: [PATCH 31/32] test(github-action): updates env setting --- .github/workflows/backend_build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index 766d8ee0..b47ffc75 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -38,6 +38,11 @@ jobs: - name: Get my current working dir run: pwd + - name: Test env vars for python + env: + TESTING_TOKEN: ${{ secrets.TESTING_TOKEN }} + run: python -c "import os; print(os.environ['TESTING_TOKEN'])" + - name: Clone Ramp run: git clone https://github.com/kshitijrajsharma/ramp-code-fAIr.git ramp-code From 7aaccd607ea7508bd8f834f895caddfdef267c68 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:46:23 +0100 Subject: [PATCH 32/32] ci(backend-build): updates requirement to run tests --- .github/workflows/backend_build.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/backend_build.yml b/.github/workflows/backend_build.yml index b47ffc75..09f3a993 100644 --- a/.github/workflows/backend_build.yml +++ b/.github/workflows/backend_build.yml @@ -120,9 +120,6 @@ jobs: - name: Run migrations env: TESTING_TOKEN: ${{ secrets.TESTING_TOKEN }} - OSM_CLIENT_ID: ${{ secrets.OSM_CLIENT_ID}} - OSM_CLIENT_SECRET: ${{ secrets.OSM_CLIENT_SECRET}} - OSM_SECRET_KEY: ${{ secrets.OSM_SECRET_KEY}} run: | cd backend/ python manage.py makemigrations