diff --git a/.bandit b/.bandit index e880d92..7438283 100644 --- a/.bandit +++ b/.bandit @@ -1,2 +1,2 @@ [bandit] -exclude = tests,app/utils/testing.py +exclude = tests diff --git a/app/access/tests/__init__.py b/app/access/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/access/tests/test_api.py b/app/access/tests/test_api.py deleted file mode 100644 index 9d35c04..0000000 --- a/app/access/tests/test_api.py +++ /dev/null @@ -1,552 +0,0 @@ -from unittest.mock import patch - -from access.api import user_to_response -from access.models import User -from access.schemas import UserSchema -from botocore.exceptions import EndpointConnectionError -from provider.models import Provider -from utils.testing import create_user_with_permissions - -from django.test import TestCase - - -# pylint: disable=too-many-public-methods -class ApiTestCase(TestCase): - - def setUp(self): - provider = Provider.objects.create() - model_fields = { - "username": "dude", - "first_name": "Jeffrey", - "last_name": "Lebowski", - "email": "dude@bowling.com", - "provider": provider, - } - User.objects.create(**model_fields) - - def test_user_to_response_maps_fields_correctly(self): - - model = User.objects.last() - - actual = user_to_response(model) - - expected = UserSchema( - username="dude", - first_name="Jeffrey", - last_name="Lebowski", - email="dude@bowling.com", - provider_id=Provider.objects.last().id, - ) - - assert actual == expected - - def test_get_user_returns_existing_user(self): - create_user_with_permissions('test', 'test', [('access', 'user', 'view_user')]) - self.client.login(username='test', password='test') - - response = self.client.get("/api/users/dude") - - assert response.status_code == 200 - assert response.json() == { - "username": "dude", - "first_name": "Jeffrey", - "last_name": "Lebowski", - "email": "dude@bowling.com", - "provider_id": Provider.objects.last().id, - } - - def test_get_user_returns_404_if_nonexisting(self): - create_user_with_permissions('test', 'test', [('access', 'user', 'view_user')]) - self.client.login(username='test', password='test') - - response = self.client.get("/api/users/nihilist") - - assert response.status_code == 404 - assert response.json() == {"code": 404, "description": "Resource not found"} - - def test_get_user_returns_401_if_not_logged_in(self): - response = self.client.get("/api/users/dude") - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - - def test_get_user_returns_403_if_no_permission(self): - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - response = self.client.get("/api/users/dude") - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} - - def test_get_users_returns_single_user(self): - create_user_with_permissions('test', 'test', [('access', 'user', 'view_user')]) - self.client.login(username='test', password='test') - - response = self.client.get("/api/users") - - assert response.status_code == 200 - assert response.json() == { - "items": [{ - "username": "dude", - "first_name": "Jeffrey", - "last_name": "Lebowski", - "email": "dude@bowling.com", - "provider_id": Provider.objects.last().id, - }] - } - - def test_get_users_returns_users_ordered_by_id(self): - create_user_with_permissions('test', 'test', [('access', 'user', 'view_user')]) - self.client.login(username='test', password='test') - - model_fields = { - "username": "veteran", - "first_name": "Walter", - "last_name": "Sobchak", - "email": "veteran@bowling.com", - "provider": Provider.objects.last(), - } - User.objects.create(**model_fields) - - response = self.client.get("/api/users") - - assert response.status_code == 200 - assert response.json() == { - "items": [ - { - "username": "dude", - "first_name": "Jeffrey", - "last_name": "Lebowski", - "email": "dude@bowling.com", - "provider_id": Provider.objects.last().id, - }, - { - "username": "veteran", - "first_name": "Walter", - "last_name": "Sobchak", - "email": "veteran@bowling.com", - "provider_id": Provider.objects.last().id, - }, - ] - } - - def test_get_users_returns_401_if_not_logged_in(self): - response = self.client.get("/api/users") - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - - def test_get_users_returns_403_if_no_permission(self): - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - response = self.client.get("/api/users") - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} - - @patch('access.api.create_cognito_user') - def test_post_users_creates_new_user_in_db_and_returns_it(self, create_cognito_user): - create_cognito_user.return_value = True - - create_user_with_permissions('test', 'test', [('access', 'user', 'add_user')]) - self.client.login(username='test', password='test') - - payload = { - "username": "donny", - "first_name": "Theodore Donald", - "last_name": "Kerabatsos", - "email": "donny@bowling.com", - "provider_id": Provider.objects.last().id, - } - - response = self.client.post("/api/users", data=payload, content_type='application/json') - - assert response.status_code == 201 - assert response.json() == payload - assert User.objects.count() == 2 - assert create_cognito_user.called - - @patch('access.api.create_cognito_user') - def test_post_users_returns_404_if_provider_id_does_not_exist(self, create_cognito_user): - create_cognito_user.return_value = False - - create_user_with_permissions('test', 'test', [('access', 'user', 'add_user')]) - self.client.login(username='test', password='test') - - non_existing_provider_id = Provider.objects.last().id + 1 - payload = { - "username": "donny", - "first_name": "Theodore Donald", - "last_name": "Kerabatsos", - "email": "donny@bowling.com", - "provider_id": non_existing_provider_id, - } - - response = self.client.post("/api/users", data=payload, content_type='application/json') - - assert response.status_code == 404 - assert response.json() == {"code": 404, "description": "Resource not found"} - assert not create_cognito_user.called - - @patch('access.api.create_cognito_user') - def test_post_users_returns_422_if_email_format_invalid(self, create_cognito_user): - create_cognito_user.return_value = False - - create_user_with_permissions('test', 'test', [('access', 'user', 'add_user')]) - self.client.login(username='test', password='test') - - invalid_email = "donny_at_bowling_dot_com" - payload = { - "username": "donny", - "first_name": "Theodore Donald", - "last_name": "Kerabatsos", - "email": invalid_email, - "provider_id": Provider.objects.last().id, - } - - response = self.client.post("/api/users", data=payload, content_type='application/json') - - assert response.status_code == 422 - assert response.json() == {'code': 422, 'description': ["Enter a valid email address."]} - assert not create_cognito_user.called - - @patch('access.api.create_cognito_user') - def test_post_users_returns_409_if_user_exists_already(self, create_cognito_user): - create_cognito_user.return_value = False - - create_user_with_permissions('test', 'test', [('access', 'user', 'add_user')]) - self.client.login(username='test', password='test') - - payload = { - "username": "dude", - "first_name": "Theodore Donald", - "last_name": "Kerabatsos", - "email": "donny@bowling.com", - "provider_id": Provider.objects.last().id, - } - - response = self.client.post("/api/users", data=payload, content_type='application/json') - - assert response.status_code == 409 - assert response.json() == { - 'code': 409, 'description': ["User with this User name already exists."] - } - assert not create_cognito_user.called - - @patch('access.api.create_cognito_user') - def test_post_users_returns_409_and_reports_all_errors_if_multiple_things_amiss( - self, create_cognito_user - ): - create_cognito_user.return_value = False - - create_user_with_permissions('test', 'test', [('access', 'user', 'add_user')]) - self.client.login(username='test', password='test') - - invalid_email = "donny_at_bowling_dot_com" - payload = { - "username": "dude", - "first_name": "Theodore Donald", - "last_name": "Kerabatsos", - "email": invalid_email, - "provider_id": Provider.objects.last().id, - } - - response = self.client.post("/api/users", data=payload, content_type='application/json') - - assert response.status_code == 409 - assert response.json() == { - 'code': 409, - 'description': [ - "Enter a valid email address.", "User with this User name already exists." - ] - } - assert not create_cognito_user.called - - @patch('access.api.create_cognito_user') - def test_post_users_returns_500_if_cognito_inconsistent(self, create_cognito_user): - create_cognito_user.return_value = False - - create_user_with_permissions('test', 'test', [('access', 'user', 'add_user')]) - self.client.login(username='test', password='test') - - payload = { - "username": "donny", - "first_name": "Theodore Donald", - "last_name": "Kerabatsos", - "email": "donny@bowling.com", - "provider_id": Provider.objects.last().id, - } - - response = self.client.post("/api/users", data=payload, content_type='application/json') - - assert response.status_code == 500 - assert response.json() == {'code': 500, 'description': 'Internal Server Error'} - assert User.objects.count() == 1 - assert create_cognito_user.called - - @patch('access.api.create_cognito_user') - def test_post_users_returns_503_if_cognito_down(self, create_cognito_user): - create_cognito_user.side_effect = EndpointConnectionError(endpoint_url='http://localhost') - - create_user_with_permissions('test', 'test', [('access', 'user', 'add_user')]) - self.client.login(username='test', password='test') - - payload = { - "username": "donny", - "first_name": "Theodore Donald", - "last_name": "Kerabatsos", - "email": "donny@bowling.com", - "provider_id": Provider.objects.last().id, - } - - response = self.client.post("/api/users", data=payload, content_type='application/json') - - assert response.status_code == 503 - assert response.json() == {'code': 503, 'description': 'Service Unavailable'} - assert User.objects.count() == 1 - assert create_cognito_user.called - - @patch('access.api.create_cognito_user') - def test_post_user_returns_401_if_not_logged_in(self, create_cognito_user): - create_cognito_user.return_value = True - - response = self.client.post("/api/users", data={}, content_type='application/json') - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - assert not create_cognito_user.called - assert User.objects.count() == 1 - - @patch('access.api.create_cognito_user') - def test_post_user_returns_403_if_no_permission(self, create_cognito_user): - create_cognito_user.return_value = True - - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - response = self.client.post("/api/users", data={}, content_type='application/json') - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} - assert not create_cognito_user.called - assert User.objects.count() == 1 - - @patch('access.api.disable_cognito_user') - def test_delete_user_deletes_user(self, disable_cognito_user): - disable_cognito_user.return_value = True - - create_user_with_permissions('test', 'test', [('access', 'user', 'delete_user')]) - self.client.login(username='test', password='test') - - response = self.client.delete("/api/users/dude") - - assert response.status_code == 204 - assert response.content == b'' - assert User.objects.count() == 0 - assert disable_cognito_user.called - - @patch('access.api.disable_cognito_user') - def test_delete_user_returns_404_if_nonexisting(self, disable_cognito_user): - disable_cognito_user.return_value = False - - create_user_with_permissions('test', 'test', [('access', 'user', 'delete_user')]) - self.client.login(username='test', password='test') - - response = self.client.delete("/api/users/lebowski") - - assert response.status_code == 404 - assert response.json() == {"code": 404, "description": "Resource not found"} - assert User.objects.count() == 1 - assert not disable_cognito_user.called - - @patch('access.api.disable_cognito_user') - def test_delete_user_returns_500_if_cognito_inconsistent(self, disable_cognito_user): - disable_cognito_user.return_value = False - - create_user_with_permissions('test', 'test', [('access', 'user', 'delete_user')]) - self.client.login(username='test', password='test') - - response = self.client.delete("/api/users/dude") - - assert response.status_code == 500 - assert response.json() == {"code": 500, "description": "Internal Server Error"} - assert User.objects.count() == 1 - assert disable_cognito_user.called - - @patch('access.api.disable_cognito_user') - def test_delete_user_returns_503_if_cognito_down(self, disable_cognito_user): - disable_cognito_user.side_effect = EndpointConnectionError(endpoint_url='http://localhost') - - create_user_with_permissions('test', 'test', [('access', 'user', 'delete_user')]) - self.client.login(username='test', password='test') - - response = self.client.delete("/api/users/dude") - - assert response.status_code == 503 - assert response.json() == {"code": 503, "description": "Service Unavailable"} - assert User.objects.count() == 1 - assert disable_cognito_user.called - - @patch('access.api.disable_cognito_user') - def test_delete_user_returns_401_if_not_logged_in(self, disable_cognito_user): - disable_cognito_user.return_value = True - - response = self.client.delete("/api/users/dude", data={}, content_type='application/json') - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - assert User.objects.count() == 1 - assert not disable_cognito_user.called - - @patch('access.api.disable_cognito_user') - def test_delete_user_returns_403_if_no_permission(self, disable_cognito_user): - disable_cognito_user.return_value = True - - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - response = self.client.delete("/api/users/dude", data={}, content_type='application/json') - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} - assert User.objects.count() == 1 - assert not disable_cognito_user.called - - def test_update_user_returns_401_if_not_logged_in(self): - response = self.client.put("/api/users/dude") - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - - def test_update_user_returns_403_if_no_permission(self): - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - response = self.client.put("/api/users/dude") - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} - - @patch('access.api.update_cognito_user') - def test_update_user_updates_existing_user_as_expected(self, update_cognito_user): - update_cognito_user.return_value = True - - create_user_with_permissions('test', 'test', [('access', 'user', 'change_user')]) - self.client.login(username='test', password='test') - - payload = { - "username": "dude", - "first_name": "Jeff", - "last_name": "Bridges", - "email": "tron@hollywood.com", - "provider_id": Provider.objects.last().id, - } - - response = self.client.put("/api/users/dude", data=payload, content_type='application/json') - - assert response.status_code == 200 - assert response.json() == payload - user = User.objects.filter(username="dude").first() - for key, value in payload.items(): - assert getattr(user, key) == value - assert update_cognito_user.called - - def test_update_user_returns_404_and_leaves_user_as_is_if_user_nonexistent(self): - - create_user_with_permissions('test', 'test', [('access', 'user', 'change_user')]) - self.client.login(username='test', password='test') - - user_before = User.objects.filter(username="dude").first() - payload = { - "username": "dude", - "first_name": "Jeff", - "last_name": "Bridges", - "email": "tron@hollywood.com", - "provider_id": Provider.objects.last().id, - } - - nonexistent_username = "maude" - response = self.client.put( - f"/api/users/{nonexistent_username}", data=payload, content_type='application/json' - ) - - assert response.status_code == 404 - assert response.json() == {"code": 404, "description": "Resource not found"} - user_after = User.objects.filter(username="dude").first() - assert user_after == user_before - - def test_update_user_returns_400_and_leaves_user_as_is_if_provider_nonexistent(self): - - create_user_with_permissions('test', 'test', [('access', 'user', 'change_user')]) - self.client.login(username='test', password='test') - - user_before = User.objects.filter(username="dude").first() - nonexistent_id = Provider.objects.last().id + 1234 - payload = { - "username": "dude", - "first_name": "Jeff", - "last_name": "Bridges", - "email": "tron@hollywood.com", - "provider_id": nonexistent_id, - } - - response = self.client.put("/api/users/dude", data=payload, content_type="application/json") - - assert response.status_code == 400 - assert response.json() == {"code": 400, "description": "Provider does not exist"} - user_after = User.objects.filter(username="dude").first() - assert user_after == user_before - - @patch('access.api.update_cognito_user') - def test_update_user_returns_500_and_leaves_user_as_is_if_cognito_inconsistent( - self, update_cognito_user - ): - update_cognito_user.return_value = False - - create_user_with_permissions('test', 'test', [('access', 'user', 'change_user')]) - self.client.login(username='test', password='test') - - user_before = User.objects.filter(username="dude").first() - payload = { - "username": "dude", - "first_name": "Jeff", - "last_name": "Bridges", - "email": "tron@hollywood.com", - "provider_id": Provider.objects.last().id, - } - - response = self.client.put("/api/users/dude", data=payload, content_type="application/json") - - assert response.status_code == 500 - assert response.json() == {"code": 500, "description": "Internal Server Error"} - user_after = User.objects.filter(username="dude").first() - assert user_after == user_before - assert update_cognito_user.called - - @patch('access.api.update_cognito_user') - def test_update_user_returns_503_and_leaves_user_as_is_if_cognito_down( - self, update_cognito_user - ): - update_cognito_user.side_effect = EndpointConnectionError(endpoint_url='http://localhost') - - create_user_with_permissions('test', 'test', [('access', 'user', 'change_user')]) - self.client.login(username='test', password='test') - - user_before = User.objects.filter(username="dude").first() - payload = { - "username": "dude", - "first_name": "Jeff", - "last_name": "Bridges", - "email": "tron@hollywood.com", - "provider_id": Provider.objects.last().id, - } - - response = self.client.put("/api/users/dude", data=payload, content_type="application/json") - - assert response.status_code == 503 - assert response.json() == {"code": 503, "description": "Service Unavailable"} - user_after = User.objects.filter(username="dude").first() - assert user_after == user_before - assert update_cognito_user.called diff --git a/app/access/tests/test_models.py b/app/access/tests/test_models.py deleted file mode 100644 index 5f7c7f2..0000000 --- a/app/access/tests/test_models.py +++ /dev/null @@ -1,78 +0,0 @@ -import pytest -from access.models import User -from provider.models import Provider - -from django.core.exceptions import ValidationError -from django.forms import ModelForm - - -@pytest.mark.django_db -class TestUser: - - def test_user_stored_as_expected_for_valid_input(self): - provider = Provider.objects.create() - model_fields = { - "username": "dude", - "first_name": "Jeffrey", - "last_name": "Lebowski", - "email": "dude@bowling.com", - "provider": provider, - } - - actual = User.objects.create(**model_fields) - - assert actual.username == "dude" - assert actual.first_name == "Jeffrey" - assert actual.last_name == "Lebowski" - assert actual.email == "dude@bowling.com" - assert actual.provider == provider - - def test_user_raises_exception_for_user_with_existing_user_name(self): - User.objects.create( - username="dude", - first_name="Jeffrey", - last_name="Lebowski", - email="dude@bowling.com", - provider=Provider.objects.create() - ) - with pytest.raises(ValidationError): - User.objects.create( - username="dude", - first_name="XXX", - last_name="YYY", - email="xxx@yyy.com", - provider=Provider.objects.create() - ) - - def test_user_with_invalid_email_raises_exception_when_creating_db_record(self): - provider = Provider.objects.create() - model_fields = { - "username": "dude", - "first_name": "Jeffrey", - "last_name": "Lebowski", - "email": "dude_at_bowling_dot_com", - "provider": provider, - } - - with pytest.raises(ValidationError): - User.objects.create(**model_fields) - - def test_form_invalid_for_user_with_invalid_email(self): - - class UserForm(ModelForm): - - class Meta: - model = User - fields = "__all__" - - provider = Provider.objects.create() - data = { - "username": "dude", - "first_name": "Jeffrey", - "last_name": "Lebowski", - "email": "dude_at_bowling_dot_com", - "provider": provider, - } - form = UserForm(data) - - assert not form.is_valid() diff --git a/app/bod/tests/__init__.py b/app/bod/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/bod/tests/test_bod_migrate_command.py b/app/bod/tests/test_bod_migrate_command.py deleted file mode 100644 index 093cffd..0000000 --- a/app/bod/tests/test_bod_migrate_command.py +++ /dev/null @@ -1,255 +0,0 @@ -from io import StringIO - -from bod.models import BodContactOrganisation -from bod.models import BodDataset -from bod.models import BodTranslations -from distributions.models import Attribution -from distributions.models import Dataset -from provider.models import Provider - -from django.core.management import call_command -from django.test import TestCase - - -class BodMigrateCommandTest(TestCase): - - def setUp(self): - BodContactOrganisation.objects.create( - pk_contactorganisation_id=17, - name_de="Bundesamt für Umwelt", - name_fr="Office fédéral de l'environnement", - name_en="Federal Office for the Environment", - name_it="Ufficio federale dell'ambiente", - name_rm="Uffizi federal per l'ambient", - abkuerzung_de="BAFU", - abkuerzung_fr="OFEV", - abkuerzung_en="FOEN", - abkuerzung_it="UFAM", - abkuerzung_rm="UFAM", - attribution="ch.bafu" - ) - BodTranslations.objects.create( - msg_id="ch.bafu", de="BAFU", fr="OFEV", en="FOEN", it="UFAM", rm="UFAM" - ) - BodDataset.objects.create( - id=170, id_dataset="ch.bafu.auen-vegetationskarten", fk_contactorganisation_id=17 - ) - - def test_command_imports(self): - out = StringIO() - call_command("bod_migrate", verbosity=2, stdout=out) - self.assertIn("Added provider 'Federal Office for the Environment'", out.getvalue()) - self.assertIn("1 provider(s) added", out.getvalue()) - self.assertIn("1 attribution(s) added", out.getvalue()) - self.assertIn("1 dataset(s) added", out.getvalue()) - self.assertEqual(Provider.objects.count(), 1) - self.assertEqual(Attribution.objects.count(), 1) - self.assertEqual(Dataset.objects.count(), 1) - - provider = Provider.objects.first() - self.assertEqual(provider.name_de, "Bundesamt für Umwelt") - self.assertEqual(provider.name_fr, "Office fédéral de l'environnement") - self.assertEqual(provider.name_en, "Federal Office for the Environment") - self.assertEqual(provider.name_it, "Ufficio federale dell'ambiente") - self.assertEqual(provider.name_rm, "Uffizi federal per l'ambient") - self.assertEqual(provider.acronym_de, "BAFU") - self.assertEqual(provider.acronym_fr, "OFEV") - self.assertEqual(provider.acronym_en, "FOEN") - self.assertEqual(provider.acronym_it, "UFAM") - self.assertEqual(provider.acronym_rm, "UFAM") - - attribution = provider.attribution_set.first() - self.assertEqual(attribution.name_de, "BAFU") - self.assertEqual(attribution.name_fr, "OFEV") - self.assertEqual(attribution.name_en, "FOEN") - self.assertEqual(attribution.name_it, "UFAM") - self.assertEqual(attribution.name_rm, "UFAM") - self.assertEqual(attribution.description_de, "BAFU") - self.assertEqual(attribution.description_fr, "OFEV") - self.assertEqual(attribution.description_en, "FOEN") - self.assertEqual(attribution.description_it, "UFAM") - self.assertEqual(attribution.description_rm, "UFAM") - - dataset = provider.dataset_set.first() - self.assertEqual(dataset.attribution, attribution) - self.assertEqual(dataset.slug, "ch.bafu.auen-vegetationskarten") - - def test_command_updates(self): - provider = Provider.objects.create( - name_de="XXX", - name_fr="XXX", - name_en="XXX", - acronym_de="BAFU", - acronym_fr="", - acronym_en="", - _legacy_id=17 - ) - attribution = Attribution.objects.create( - name_de="XXX", - name_fr="XXX", - name_en="XXX", - description_de="BAFU", - description_fr="", - description_en="", - provider=provider, - _legacy_id=17 - ) - dataset = Dataset.objects.create( - slug="XXX", provider=provider, attribution=attribution, _legacy_id=170 - ) - - out = StringIO() - call_command("bod_migrate", verbosity=2, stdout=out) - self.assertIn(f"Changed Provider {provider.id} name_de", out.getvalue()) - self.assertNotIn(f"Changed Provider {provider.id} acronym_de", out.getvalue()) - self.assertIn("1 provider(s) updated", out.getvalue()) - self.assertIn(f"Changed Attribution {attribution.id} name_de", out.getvalue()) - self.assertNotIn(f"Changed Attribution {attribution.id} description_de", out.getvalue()) - self.assertIn("1 attribution(s) updated", out.getvalue()) - self.assertIn(f"Changed Dataset {dataset.id} slug", out.getvalue()) - self.assertIn("1 dataset(s) updated", out.getvalue()) - self.assertEqual(Provider.objects.count(), 1) - self.assertEqual(Attribution.objects.count(), 1) - self.assertEqual(Dataset.objects.count(), 1) - - provider = Provider.objects.first() - self.assertEqual(provider.name_de, "Bundesamt für Umwelt") - self.assertEqual(provider.name_fr, "Office fédéral de l'environnement") - self.assertEqual(provider.name_en, "Federal Office for the Environment") - self.assertEqual(provider.name_it, "Ufficio federale dell'ambiente") - self.assertEqual(provider.name_rm, "Uffizi federal per l'ambient") - self.assertEqual(provider.acronym_de, "BAFU") - self.assertEqual(provider.acronym_fr, "OFEV") - self.assertEqual(provider.acronym_en, "FOEN") - self.assertEqual(provider.acronym_it, "UFAM") - self.assertEqual(provider.acronym_rm, "UFAM") - - attribution = provider.attribution_set.first() - self.assertEqual(attribution.name_de, "BAFU") - self.assertEqual(attribution.name_fr, "OFEV") - self.assertEqual(attribution.name_en, "FOEN") - self.assertEqual(attribution.name_it, "UFAM") - self.assertEqual(attribution.name_rm, "UFAM") - self.assertEqual(attribution.description_de, "BAFU") - self.assertEqual(attribution.description_fr, "OFEV") - self.assertEqual(attribution.description_en, "FOEN") - self.assertEqual(attribution.description_it, "UFAM") - self.assertEqual(attribution.description_rm, "UFAM") - - dataset = provider.dataset_set.first() - self.assertEqual(dataset.slug, "ch.bafu.auen-vegetationskarten") - - def test_command_removes_orphaned(self): - # Add objects which will be removed - provider = Provider.objects.create( - name_de="XXX", - name_fr="XXX", - name_en="XXX", - acronym_de="XXX", - acronym_fr="XXX", - acronym_en="XXX", - _legacy_id=16 - ) - attribution = Attribution.objects.create( - name_de="XXX", - name_fr="XXX", - name_en="XXX", - description_de="XXX", - description_fr="XXX", - description_en="XXX", - provider=provider, - _legacy_id=16 - ) - Dataset.objects.create( - slug="XXX", provider=provider, attribution=attribution, _legacy_id=160 - ) - - # Add objects which will not be removed - provider = Provider.objects.create( - name_de="YYY", - name_fr="YYY", - name_en="YYY", - acronym_de="YYYY", - acronym_fr="YYYY", - acronym_en="YYYY", - ) - attribution = Attribution.objects.create( - name_de="YYYY", - name_fr="YYYY", - name_en="YYYY", - description_de="YYY", - description_fr="YYY", - description_en="YYY", - provider=provider - ) - Dataset.objects.create(slug="YYYY", provider=provider, attribution=attribution) - - out = StringIO() - call_command("bod_migrate", verbosity=2, stdout=out) - self.assertIn("1 provider(s) removed", out.getvalue()) - self.assertIn("1 attribution(s) removed", out.getvalue()) - self.assertIn("1 dataset(s) removed", out.getvalue()) - self.assertIn("1 provider(s) added", out.getvalue()) - self.assertIn("1 attribution(s) added", out.getvalue()) - self.assertIn("1 dataset(s) added", out.getvalue()) - self.assertEqual(Provider.objects.count(), 2) - self.assertEqual(Attribution.objects.count(), 2) - self.assertEqual(Dataset.objects.count(), 2) - self.assertEqual({'BAFU', 'YYYY'}, - set(Provider.objects.values_list('acronym_de', flat=True))) - self.assertEqual({'BAFU', 'YYYY'}, - set(Attribution.objects.values_list('name_de', flat=True))) - self.assertEqual({'ch.bafu.auen-vegetationskarten', 'YYYY'}, - set(Dataset.objects.values_list('slug', flat=True))) - - def test_command_does_not_import_if_dry_run(self): - out = StringIO() - call_command("bod_migrate", dry_run=True, stdout=out) - self.assertIn("1 provider(s) added", out.getvalue()) - self.assertIn("1 attribution(s) added", out.getvalue()) - self.assertIn("1 dataset(s) added", out.getvalue()) - self.assertIn("dry run, aborting", out.getvalue()) - self.assertEqual(Provider.objects.count(), 0) - self.assertEqual(Attribution.objects.count(), 0) - self.assertEqual(Dataset.objects.count(), 0) - - def test_command_clears_existing_data(self): - provider = Provider.objects.create( - name_de="XXX", - name_fr="XXX", - name_en="XXX", - acronym_de="XXX", - acronym_fr="XXX", - acronym_en="XXX", - _legacy_id=150 - ) - attribution = Attribution.objects.create( - name_de="XXX", - name_fr="XXX", - name_en="XXX", - description_de="XXX", - description_fr="XXX", - description_en="XXX", - provider=provider - ) - Dataset.objects.create(slug="YYYY", provider=provider, attribution=attribution) - - out = StringIO() - call_command("bod_migrate", clear=True, stdout=out) - self.assertIn("1 provider(s) cleared", out.getvalue()) - self.assertIn("1 attribution(s) cleared", out.getvalue()) - self.assertIn("1 dataset(s) cleared", out.getvalue()) - self.assertIn("1 provider(s) added", out.getvalue()) - self.assertIn("1 attribution(s) added", out.getvalue()) - self.assertIn("1 dataset(s) added", out.getvalue()) - self.assertEqual(Provider.objects.count(), 1) - self.assertEqual(Dataset.objects.count(), 1) - - provider = Provider.objects.first() - self.assertEqual(provider.name_de, "Bundesamt für Umwelt") - - attribution = provider.attribution_set.first() - self.assertEqual(attribution.name_de, "BAFU") - - dataset = provider.dataset_set.first() - self.assertEqual(dataset.slug, "ch.bafu.auen-vegetationskarten") diff --git a/app/cognito/tests/__init__.py b/app/cognito/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/cognito/tests/test_client.py b/app/cognito/tests/test_client.py deleted file mode 100644 index 23e72bd..0000000 --- a/app/cognito/tests/test_client.py +++ /dev/null @@ -1,272 +0,0 @@ -from unittest.mock import call -from unittest.mock import patch - -from cognito.utils.client import Client -from cognito.utils.client import user_attributes_to_dict - -from django.conf import settings -from django.test import TestCase - - -def cognito_user(username, managed, for_): - attributes_key = 'Attributes' if for_ == 'list_users' else 'UserAttributes' - attributes = [{'Name': 'email', 'Value': 'test@example.org'}] - if managed: - attributes.append({'Name': settings.COGNITO_MANAGED_FLAG_NAME, 'Value': 'true'}) - return {'Username': username, attributes_key: attributes} - - -class ClientTestCase(TestCase): - - def test_user_attributes_to_dict(self): - attributes = [{ - 'Name': 'email', 'Value': 'test@example.org' - }, { - 'Name': 'flag', 'Value': 'true' - }] - attributes = user_attributes_to_dict(attributes) - self.assertEqual(attributes, {'email': 'test@example.org', 'flag': 'true'}) - - @patch('cognito.utils.client.client') - def test_list_users_returns_only_managed(self, boto3): - managed = cognito_user('1234', True, 'list_users') - unmanaged = cognito_user('1234', False, 'list_users') - boto3.return_value.list_users.return_value = {'Users': [managed, unmanaged]} - - client = Client() - users = client.list_users() - self.assertEqual(users, [managed]) - self.assertIn(call().list_users(UserPoolId=client.user_pool_id, Limit=60), boto3.mock_calls) - - @patch('cognito.utils.client.client') - def test_list_users_pagination(self, boto3): - users = [cognito_user(str(count), True, 'list_users') for count in range(1, 131)] - response_1 = {'Users': users[0:60], 'PaginationToken': '1'} - response_2 = {'Users': users[60:120], 'PaginationToken': '2'} - response_3 = {'Users': users[120:130]} - boto3.return_value.list_users.side_effect = [response_1, response_2, response_3] - - client = Client() - users = client.list_users() - - self.assertEqual([user['Username'] for user in users], [str(x) for x in range(1, 131)]) - self.assertIn(call().list_users(UserPoolId=client.user_pool_id, Limit=60), boto3.mock_calls) - self.assertIn( - call().list_users(UserPoolId=client.user_pool_id, Limit=60, PaginationToken='2'), - boto3.mock_calls - ) - self.assertIn( - call().list_users(UserPoolId=client.user_pool_id, Limit=60, PaginationToken='2'), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_get_user_returns_managed(self, boto3): - response = cognito_user('1234', True, 'get_user') - boto3.return_value.admin_get_user.return_value = response - - client = Client() - user = client.get_user('1234') - self.assertEqual(user, response) - self.assertIn( - call().admin_get_user(UserPoolId=client.user_pool_id, Username='1234'), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_get_user_does_not_return_unmanaged(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', False, 'get_user') - - client = Client() - user = client.get_user('1234') - self.assertEqual(user, None) - self.assertIn( - call().admin_get_user(UserPoolId=client.user_pool_id, Username='1234'), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_get_user_returns_unmanaged(self, boto3): - response = cognito_user('1234', False, 'get_user') - boto3.return_value.admin_get_user.return_value = response - - client = Client() - user = client.get_user('1234', return_unmanaged=True) - self.assertEqual(user, response) - self.assertIn( - call().admin_get_user(UserPoolId=client.user_pool_id, Username='1234'), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_create_user_creates_managed(self, boto3): - boto3.return_value.admin_get_user.return_value = None - - client = Client() - created = client.create_user('1234', 'test@example.org') - self.assertEqual(created, True) - self.assertIn( - call().admin_create_user( - UserPoolId=client.user_pool_id, - Username='1234', - UserAttributes=[{ - 'Name': 'email', 'Value': 'test@example.org' - }, { - 'Name': client.managed_flag_name, 'Value': 'true' - }], - DesiredDeliveryMediums=['EMAIL'] - ), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_create_user_does_not_create_if_managed_exists(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', True, 'get_user') - - client = Client() - created = client.create_user('1234', 'test@example.org') - self.assertEqual(created, False) - self.assertNotIn( - call().admin_create_user( - UserPoolId=client.user_pool_id, - Username='1234', - UserAttributes=[{ - 'Name': 'email', 'Value': 'test@example.org' - }, { - 'Name': client.managed_flag_name, 'Value': 'true' - }], - DesiredDeliveryMediums=['EMAIL'] - ), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_create_user_does_not_create_if_unmanaged_exists(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', False, 'get_user') - - client = Client() - created = client.create_user('1234', 'test@example.org') - self.assertEqual(created, False) - self.assertNotIn( - call().admin_create_user( - UserPoolId=client.user_pool_id, - Username='1234', - UserAttributes=[{ - 'Name': 'email', 'Value': 'test@example.org' - }, { - 'Name': client.managed_flag_name, 'Value': 'true' - }], - DesiredDeliveryMediums=['EMAIL'] - ), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_delete_user_deletes_managed(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', True, 'get_user') - - client = Client() - deleted = client.delete_user('1234') - self.assertEqual(deleted, True) - self.assertIn( - call().admin_delete_user(UserPoolId=client.user_pool_id, Username='1234'), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_delete_user_does_not_delete_unmanaged(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', False, 'get_user') - - client = Client() - deleted = client.delete_user('1234') - self.assertEqual(deleted, False) - self.assertNotIn( - call().admin_delete_user(UserPoolId=client.user_pool_id, Username='1234'), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_update_user_updates_managed(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', True, 'get_user') - - client = Client() - updated = client.update_user('1234', 'test@example.org') - self.assertEqual(updated, True) - self.assertIn( - call().admin_update_user_attributes( - UserPoolId=client.user_pool_id, - Username='1234', - UserAttributes=[{ - 'Name': 'email', 'Value': 'test@example.org' - }] - ), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_update_user_does_not_update_unmanaged(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', False, 'get_user') - - client = Client() - updated = client.update_user('1234', 'test@example.org') - self.assertEqual(updated, False) - self.assertNotIn( - call().admin_update_user_attributes( - UserPoolId=client.user_pool_id, - Username='1234', - UserAttributes=[{ - 'Name': 'email', 'Value': 'test@example.org' - }, { - 'Name': client.managed_flag_name, 'Value': 'true' - }] - ), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_disable_user_disables_managed(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', True, 'get_user') - - client = Client() - disabled = client.disable_user('1234') - self.assertEqual(disabled, True) - self.assertIn( - call().admin_disable_user(UserPoolId=client.user_pool_id, Username='1234'), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_disable_user_does_not_disable_unmanaged(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', False, 'get_user') - - client = Client() - disabled = client.disable_user('1234') - self.assertEqual(disabled, False) - self.assertNotIn( - call().admin_disable_user(UserPoolId=client.user_pool_id, Username='1234'), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_enable_user_enables_managed(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', True, 'get_user') - - client = Client() - enabled = client.enable_user('1234') - self.assertEqual(enabled, True) - self.assertIn( - call().admin_enable_user(UserPoolId=client.user_pool_id, Username='1234'), - boto3.mock_calls - ) - - @patch('cognito.utils.client.client') - def test_enable_user_does_not_enable_unmanaged(self, boto3): - boto3.return_value.admin_get_user.return_value = cognito_user('1234', False, 'get_user') - - client = Client() - enabled = client.enable_user('1234') - self.assertEqual(enabled, False) - self.assertNotIn( - call().admin_enable_user(UserPoolId=client.user_pool_id, Username='1234'), - boto3.mock_calls - ) diff --git a/app/cognito/tests/test_sync_command.py b/app/cognito/tests/test_sync_command.py deleted file mode 100644 index d63ce85..0000000 --- a/app/cognito/tests/test_sync_command.py +++ /dev/null @@ -1,167 +0,0 @@ -from io import StringIO -from unittest.mock import call -from unittest.mock import patch - -from access.models import User -from provider.models import Provider - -from django.core.management import call_command -from django.test import TestCase -from django.utils import timezone - - -def cognito_user(username, email, enabled=True): - return { - 'Username': username, 'Attributes': [{ - 'Name': 'email', 'Value': email - }], 'Enabled': enabled - } - - -class CognitoSyncCommandTest(TestCase): - - def setUp(self): - self.provider = Provider.objects.create( - acronym_de="BAFU", - acronym_fr="OFEV", - acronym_en="FOEN", - acronym_it="UFAM", - acronym_rm="UFAM", - name_de="Bundesamt für Umwelt", - name_fr="Office fédéral de l'environnement", - name_en="Federal Office for the Environment", - name_it="Ufficio federale dell'ambiente", - name_rm="Uffizi federal per l'ambient", - ) - - def add_user(self, username, email, deleted_at=None): - User.objects.create( - username=username, - first_name=username, - last_name=username, - email=email, - provider=self.provider, - deleted_at=deleted_at - ) - - @patch('cognito.management.commands.cognito_sync.Client') - def test_command_adds(self, client): - self.add_user('1', '1@example.org') - client.return_value.list_users.return_value = [] - - out = StringIO() - call_command('cognito_sync', verbosity=2, stdout=out) - - self.assertIn('adding user 1', out.getvalue()) - self.assertIn('1 user(s) added', out.getvalue()) - self.assertIn(call().create_user('1', '1@example.org'), client.mock_calls) - - @patch('cognito.management.commands.cognito_sync.Client') - def test_command_deletes(self, client): - client.return_value.list_users.return_value = [cognito_user('1', '1@example.org')] - - out = StringIO() - call_command('cognito_sync', verbosity=2, stdout=out) - - self.assertIn('deleting user 1', out.getvalue()) - self.assertIn('1 user(s) deleted', out.getvalue()) - self.assertIn(call().delete_user('1'), client.mock_calls) - - @patch('cognito.management.commands.cognito_sync.Client') - def test_command_updates(self, client): - self.add_user('1', '1@example.org') - client.return_value.list_users.return_value = [cognito_user('1', '2@example.org')] - - out = StringIO() - call_command('cognito_sync', verbosity=2, stdout=out) - - self.assertIn('updating user 1', out.getvalue()) - self.assertIn('1 user(s) updated', out.getvalue()) - self.assertIn(call().update_user('1', '1@example.org'), client.mock_calls) - - @patch('cognito.management.commands.cognito_sync.Client') - def test_command_updates_disabled(self, client): - self.add_user('1', '1@example.org', timezone.now()) - client.return_value.list_users.return_value = [cognito_user('1', '1@example.org')] - - out = StringIO() - call_command('cognito_sync', verbosity=2, stdout=out) - - self.assertIn('disabling user 1', out.getvalue()) - self.assertIn('1 user(s) disabled', out.getvalue()) - self.assertIn(call().disable_user('1'), client.mock_calls) - - @patch('cognito.management.commands.cognito_sync.Client') - def test_command_updates_enabled(self, client): - self.add_user('1', '1@example.org') - client.return_value.list_users.return_value = [cognito_user('1', '1@example.org', False)] - - out = StringIO() - call_command('cognito_sync', verbosity=2, stdout=out) - - self.assertIn('enabling user 1', out.getvalue()) - self.assertIn('1 user(s) enabled', out.getvalue()) - self.assertIn(call().enable_user('1'), client.mock_calls) - - @patch('cognito.management.commands.cognito_sync.Client') - def test_command_does_not_updates_if_unchanged(self, client): - self.add_user('1', '1@example.org') - client.return_value.list_users.return_value = [cognito_user('1', '1@example.org')] - - out = StringIO() - call_command('cognito_sync', verbosity=2, stdout=out) - - self.assertIn('nothing to be done', out.getvalue()) - - @patch('builtins.input') - @patch('cognito.management.commands.cognito_sync.Client') - def test_command_clears_if_confirmed(self, client, input_): - input_.side_effect = ['yes'] - self.add_user('1', '1@example.org') - client.return_value.list_users.side_effect = [[cognito_user('1', '1@example.org')], []] - - out = StringIO() - call_command('cognito_sync', clear=True, verbosity=2, stdout=out) - - self.assertIn('This action will delete all managed users from cognito', out.getvalue()) - self.assertIn('deleting user 1', out.getvalue()) - self.assertIn('1 user(s) deleted', out.getvalue()) - self.assertIn('adding user 1', out.getvalue()) - self.assertIn('1 user(s) added', out.getvalue()) - self.assertIn(call().delete_user('1'), client.mock_calls) - self.assertIn(call().create_user('1', '1@example.org'), client.mock_calls) - - @patch('builtins.input') - @patch('cognito.management.commands.cognito_sync.Client') - def test_command_does_not_clears_if_not_confirmed(self, client, input_): - self.add_user('1', '1@example.org') - input_.side_effect = ['no'] - client.return_value.list_users.side_effect = [[cognito_user('1', '1@example.org')], []] - - out = StringIO() - call_command('cognito_sync', clear=True, verbosity=2, stdout=out) - - self.assertIn('This action will delete all managed users from cognito', out.getvalue()) - self.assertIn('operation cancelled', out.getvalue()) - self.assertNotIn(call().delete_user('1'), client.mock_calls) - self.assertNotIn(call().create_user('1', '1@example.org'), client.mock_calls) - - @patch('cognito.management.commands.cognito_sync.Client') - def test_command_runs_dry(self, client): - self.add_user('1', '1@example.org') - self.add_user('2', '2@example.org') - client.return_value.list_users.return_value = [ - cognito_user('1', '10@example.org'), cognito_user('3', '3@example.org') - ] - - out = StringIO() - call_command('cognito_sync', dry_run=True, verbosity=2, stdout=out) - - self.assertIn('adding user 2', out.getvalue()) - self.assertIn('deleting user 3', out.getvalue()) - self.assertIn('updating user 1', out.getvalue()) - self.assertIn('dry run', out.getvalue()) - self.assertTrue(client().list_users.called) - self.assertFalse(client().create_user.called) - self.assertFalse(client().delete_user.called) - self.assertFalse(client().update_user.called) diff --git a/app/cognito/tests/test_user_utils.py b/app/cognito/tests/test_user_utils.py deleted file mode 100644 index aafa6c4..0000000 --- a/app/cognito/tests/test_user_utils.py +++ /dev/null @@ -1,107 +0,0 @@ -from unittest.mock import call -from unittest.mock import patch - -from cognito.utils.user import create_cognito_user -from cognito.utils.user import delete_cognito_user -from cognito.utils.user import disable_cognito_user -from cognito.utils.user import update_cognito_user - -from django.test import TestCase - - -class DummyUser: - - def __init__(self, username, email): - self.username = username - self.email = email - - -class ClientTestCase(TestCase): - - @patch('cognito.utils.user.Client') - @patch('cognito.utils.user.logger') - def test_create_cognito_user_creates_user(self, logger, client): - client.return_value.create_user.return_value = True - - created = create_cognito_user(DummyUser('123', 'test@example.org')) - - self.assertEqual(created, True) - self.assertIn(call.info('User %s created', '123'), logger.mock_calls) - - @patch('cognito.utils.user.Client') - @patch('cognito.utils.user.logger') - def test_create_cognito_user_does_not_create_existing_user(self, logger, client): - client.return_value.create_user.return_value = False - - created = create_cognito_user(DummyUser('123', 'test@example.org')) - - self.assertEqual(created, False) - self.assertIn( - call.critical('User %s already exists, not created', '123'), logger.mock_calls - ) - - @patch('cognito.utils.user.Client') - @patch('cognito.utils.user.logger') - def test_delete_cognito_user_deletes_user(self, logger, client): - client.return_value.delete_user.return_value = True - - deleted = delete_cognito_user(DummyUser('123', 'test@example.org')) - - self.assertEqual(deleted, True) - self.assertIn(call.info('User %s deleted', '123'), logger.mock_calls) - - @patch('cognito.utils.user.Client') - @patch('cognito.utils.user.logger') - def test_delete_cognito_user_does_not_delete_nonexisting_user(self, logger, client): - client.return_value.delete_user.return_value = False - - deleted = delete_cognito_user(DummyUser('123', 'test@example.org')) - - self.assertEqual(deleted, False) - self.assertIn( - call.critical('User %s does not exist, not deleted', '123'), logger.mock_calls - ) - - @patch('cognito.utils.user.Client') - @patch('cognito.utils.user.logger') - def test_update_cognito_user_updates_user(self, logger, client): - client.return_value.update_user.return_value = True - - updated = update_cognito_user(DummyUser('123', 'test@example.org')) - - self.assertEqual(updated, True) - self.assertIn(call.info('User %s updated', '123'), logger.mock_calls) - - @patch('cognito.utils.user.Client') - @patch('cognito.utils.user.logger') - def test_update_cognito_user_does_not_update_nonexisting_user(self, logger, client): - client.return_value.update_user.return_value = False - - updated = update_cognito_user(DummyUser('123', 'test@example.org')) - - self.assertEqual(updated, False) - self.assertIn( - call.critical('User %s does not exist, not updated', '123'), logger.mock_calls - ) - - @patch('cognito.utils.user.Client') - @patch('cognito.utils.user.logger') - def test_disable_user_disables_user(self, logger, client): - client.return_value.disable_user.return_value = True - - disabled = disable_cognito_user(DummyUser('123', 'test@example.org')) - - self.assertEqual(disabled, True) - self.assertIn(call.info('User %s disabled', '123'), logger.mock_calls) - - @patch('cognito.utils.user.Client') - @patch('cognito.utils.user.logger') - def test_disable_user_does_not_disable_nonexisting_user(self, logger, client): - client.return_value.disable_user.return_value = False - - disabled = disable_cognito_user(DummyUser('123', 'test@example.org')) - - self.assertEqual(disabled, False) - self.assertIn( - call.critical('User %s does not exist, not disabled', '123'), logger.mock_calls - ) diff --git a/app/config/tests/test_checker.py b/app/config/tests/test_checker.py deleted file mode 100644 index 8e3d698..0000000 --- a/app/config/tests/test_checker.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.test import TestCase - - -class CheckerUrlTestCase(TestCase): - - def test_checker_url(self): - - # intentionally not using reverse here as we want to - # make sure the URL really is /checker - url = '/checker' - - response = self.client.get(url) - assert response.status_code == 200 - content = response.json() - - assert 'success' in content - assert 'message' in content - assert content['success'] is True - assert content['message'] == "OK" diff --git a/app/config/tests/test_exception_handler.py b/app/config/tests/test_exception_handler.py deleted file mode 100644 index df57e75..0000000 --- a/app/config/tests/test_exception_handler.py +++ /dev/null @@ -1,87 +0,0 @@ -from botocore.exceptions import EndpointConnectionError -from config.api import api -from ninja import Router -from ninja.errors import AuthenticationError -from ninja.errors import HttpError -from ninja.errors import ValidationError as NinjaValidationError - -from django.core.exceptions import ValidationError as DjangoValidationError -from django.http import Http404 -from django.http import HttpRequest -from django.test import TestCase - -router = Router() -api.add_router("", router) - - -@router.get("trigger-not-found") -def trigger_not_found(request: HttpRequest) -> dict[str, bool | str]: - raise Http404() - - -@router.get("trigger-http-error") -def trigger_http_error(request: HttpRequest) -> dict[str, bool | str]: - raise HttpError(303, "See other") - - -@router.get("/trigger-ninja-validation-error") -def trigger_ninja_validation_error(request: HttpRequest) -> dict[str, bool | str]: - raise NinjaValidationError(errors=[{"email": "Not a valid email."}]) - - -@router.get("/trigger-authentication-error") -def trigger_authentication_error(request: HttpRequest) -> dict[str, bool | str]: - raise AuthenticationError() - - -@router.get("/trigger-internal-server-error") -def trigger_internal_server_error(request: HttpRequest) -> dict[str, bool | str]: - raise RuntimeError() - - -@router.get("/trigger-django-validation-error") -def trigger_django_validation_error(request: HttpRequest) -> dict[str, bool | str]: - raise DjangoValidationError(message=[{"email": "Not a valid email."}]) - - -@router.get("/trigger-cognito-connection-error") -def trigger_cognito_connection_error(request: HttpRequest) -> dict[str, bool | str]: - raise EndpointConnectionError(endpoint_url='localhost') - - -class ErrorHandlerTestCase(TestCase): - - def test_handle_404_not_found(self): - response = self.client.get('/api/trigger-not-found') - assert response.status_code == 404 - assert response.json() == {'code': 404, 'description': 'Resource not found'} - - def test_handle_http_error(self): - response = self.client.get('/api/trigger-http-error') - assert response.status_code == 303 - assert response.json() == {'code': 303, 'description': 'See other'} - - def test_handle_ninja_validation_error(self): - response = self.client.get('/api/trigger-ninja-validation-error') - assert response.status_code == 422 - assert response.json() == {'code': 422, 'description': ['Not a valid email.']} - - def test_handle_unauthorized(self): - response = self.client.get('/api/trigger-authentication-error') - assert response.status_code == 401 - assert response.json() == {'code': 401, 'description': 'Unauthorized'} - - def test_handle_exception(self): - response = self.client.get('/api/trigger-internal-server-error') - assert response.status_code == 500 - assert response.json() == {'code': 500, 'description': 'Internal Server Error'} - - def test_handle_django_validation_error(self): - response = self.client.get('/api/trigger-django-validation-error') - assert response.status_code == 422 - assert response.json() == {'code': 422, 'description': ['Not a valid email.']} - - def test_handle_cognito_connection_error(self): - response = self.client.get('/api/trigger-cognito-connection-error') - assert response.status_code == 503 - assert response.json() == {'code': 503, 'description': 'Service Unavailable'} diff --git a/app/distributions/tests/__init__.py b/app/distributions/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/distributions/tests/test_api.py b/app/distributions/tests/test_api.py deleted file mode 100644 index 722a188..0000000 --- a/app/distributions/tests/test_api.py +++ /dev/null @@ -1,702 +0,0 @@ -import datetime -from unittest import mock - -from distributions.api import attribution_to_response -from distributions.models import Attribution -from distributions.models import Dataset -from distributions.schemas import AttributionSchema -from provider.models import Provider -from schemas import TranslationsSchema -from utils.testing import create_user_with_permissions - -from django.test import TestCase - - -# pylint: disable=too-many-public-methods -class ApiTestCase(TestCase): - - def setUp(self): - provider = Provider.objects.create() - attribution_fields = { - "name_de": "BAFU", - "name_fr": "OFEV", - "name_en": "FOEN", - "name_it": "UFAM", - "name_rm": "UFAM", - "description_de": "Bundesamt für Umwelt", - "description_fr": "Office fédéral de l'environnement", - "description_en": "Federal Office for the Environment", - "description_it": "Ufficio federale dell'ambiente", - "description_rm": "Uffizi federal per l'ambient", - "provider": provider, - } - attribution = Attribution.objects.create(**attribution_fields) - - dataset_fields = { - "slug": "ch.bafu.neophyten-haargurke", - "provider": provider, - "attribution": attribution, - } - self.time_created = datetime.datetime(2024, 9, 12, 15, 28, 0, tzinfo=datetime.UTC) - with mock.patch('django.utils.timezone.now', mock.Mock(return_value=self.time_created)): - Dataset.objects.create(**dataset_fields) - - def test_attribution_to_response_returns_response_with_language_as_defined(self): - - model = Attribution.objects.last() - actual = attribution_to_response(model, lang="de") - - expected = AttributionSchema( - id=model.id, - name="BAFU", - name_translations=TranslationsSchema( - de="BAFU", - fr="OFEV", - en="FOEN", - it="UFAM", - rm="UFAM", - ), - description="Bundesamt für Umwelt", - description_translations=TranslationsSchema( - de="Bundesamt für Umwelt", - fr="Office fédéral de l'environnement", - en="Federal Office for the Environment", - it="Ufficio federale dell'ambiente", - rm="Uffizi federal per l'ambient", - ), - provider_id=Provider.objects.last().id - ) - - assert actual == expected - - def test_attribution_to_response_returns_response_with_default_language_if_undefined(self): - - model = Attribution.objects.last() - model.name_it = None - model.name_rm = None - model.description_it = None - model.description_rm = None - - actual = attribution_to_response(model, lang="it") - - expected = AttributionSchema( - id=model.id, - name="FOEN", - name_translations=TranslationsSchema( - de="BAFU", - fr="OFEV", - en="FOEN", - it=None, - rm=None, - ), - description="Federal Office for the Environment", - description_translations=TranslationsSchema( - de="Bundesamt für Umwelt", - fr="Office fédéral de l'environnement", - en="Federal Office for the Environment", - it=None, - rm=None, - ), - provider_id=Provider.objects.last().id, - ) - - assert actual == expected - - def test_get_attribution_returns_existing_attribution_with_default_language(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - attribution_id = Attribution.objects.last().id - - response = self.client.get(f"/api/attributions/{attribution_id}") - - assert response.status_code == 200 - assert response.json() == { - "id": attribution_id, - "name": "FOEN", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - }, - "description": "Federal Office for the Environment", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "provider_id": Provider.objects.last().id, - } - - def test_get_attribution_returns_attribution_with_language_from_query(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - attribution_id = Attribution.objects.last().id - - response = self.client.get(f"/api/attributions/{attribution_id}?lang=de") - - assert response.status_code == 200 - assert response.json() == { - "id": attribution_id, - "name": "BAFU", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - }, - "description": "Bundesamt für Umwelt", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "provider_id": Provider.objects.last().id, - } - - def test_get_attribution_returns_404_for_nonexisting_attribution(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - response = self.client.get("/api/attributions/9999") - - assert response.status_code == 404 - assert response.json() == {"code": 404, "description": "Resource not found"} - - def test_get_attribution_skips_translations_that_are_not_available(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - model = Attribution.objects.last() - model.name_it = None - model.name_rm = None - model.description_it = None - model.description_rm = None - model.save() - - response = self.client.get(f"/api/attributions/{model.id}") - - assert response.status_code == 200 - assert response.json() == { - "id": model.id, - "name": "FOEN", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - }, - "description": "Federal Office for the Environment", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - }, - "provider_id": Provider.objects.last().id, - } - - def test_get_attribution_returns_attribution_with_language_from_header(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - attribution_id = Attribution.objects.last().id - - response = self.client.get( - f"/api/attributions/{attribution_id}", headers={"Accept-Language": "de"} - ) - - assert response.status_code == 200 - assert response.json() == { - "id": attribution_id, - "name": "BAFU", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - }, - "description": "Bundesamt für Umwelt", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "provider_id": Provider.objects.last().id, - } - - def test_get_attribution_returns_attribution_with_language_from_query_param_even_if_header_set( - self - ): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - attribution_id = Attribution.objects.last().id - - response = self.client.get( - f"/api/attributions/{attribution_id}?lang=fr", headers={"Accept-Language": "de"} - ) - - assert response.status_code == 200 - assert response.json() == { - "id": attribution_id, - "name": "OFEV", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - }, - "description": "Office fédéral de l'environnement", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "provider_id": Provider.objects.last().id, - } - - def test_get_attribution_returns_attribution_with_default_language_if_header_empty(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - attribution_id = Attribution.objects.last().id - - response = self.client.get( - f"/api/attributions/{attribution_id}", headers={"Accept-Language": ""} - ) - - assert response.status_code == 200 - assert response.json() == { - "id": attribution_id, - "name": "FOEN", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - }, - "description": "Federal Office for the Environment", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "provider_id": Provider.objects.last().id, - } - - def test_get_attribution_returns_attribution_with_first_known_language_from_header(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - attribution_id = Attribution.objects.last().id - - response = self.client.get( - f"/api/attributions/{attribution_id}", headers={"Accept-Language": "cn, *, de-DE, en"} - ) - - assert response.status_code == 200 - assert response.json() == { - "id": attribution_id, - "name": "BAFU", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - }, - "description": "Bundesamt für Umwelt", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "provider_id": Provider.objects.last().id, - } - - def test_get_attribution_returns_attribution_with_first_language_from_header_ignoring_qfactor( - self - ): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - attribution_id = Attribution.objects.last().id - - response = self.client.get( - f"/api/attributions/{attribution_id}", - headers={"Accept-Language": "fr;q=0.9, de;q=0.8"} - ) - - assert response.status_code == 200 - assert response.json() == { - "id": attribution_id, - "name": "OFEV", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - }, - "description": "Office fédéral de l'environnement", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "provider_id": Provider.objects.last().id, - } - - def test_get_attribution_returns_401_if_not_logged_in(self): - attribution_id = Attribution.objects.last().id - response = self.client.get(f"/api/attributions/{attribution_id}") - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - - def test_get_attribution_returns_403_if_no_permission(self): - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - attribution_id = Attribution.objects.last().id - response = self.client.get(f"/api/attributions/{attribution_id}") - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} - - def test_get_attributions_returns_single_attribution_with_given_language(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - response = self.client.get("/api/attributions?lang=fr") - - assert response.status_code == 200 - assert response.json() == { - "items": [{ - "id": Attribution.objects.last().id, - "name": "OFEV", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - }, - "description": "Office fédéral de l'environnement", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "provider_id": Provider.objects.last().id, - }] - } - - def test_get_attributions_skips_translations_that_are_not_available(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - model = Attribution.objects.last() - model.name_it = None - model.name_rm = None - model.description_it = None - model.description_rm = None - model.save() - - response = self.client.get("/api/attributions") - - assert response.status_code == 200 - assert response.json() == { - "items": [{ - "id": Attribution.objects.last().id, - "name": "FOEN", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - }, - "description": "Federal Office for the Environment", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - }, - "provider_id": Provider.objects.last().id, - }] - } - - def test_get_attributions_returns_attribution_with_language_from_header(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - response = self.client.get("/api/attributions", headers={"Accept-Language": "de"}) - - assert response.status_code == 200 - assert response.json() == { - "items": [{ - "id": Attribution.objects.last().id, - "name": "BAFU", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - }, - "description": "Bundesamt für Umwelt", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "provider_id": Provider.objects.last().id, - }] - } - - def test_get_attributions_returns_all_attributions_ordered_by_id_with_given_language(self): - create_user_with_permissions( - 'test', 'test', [('distributions', 'attribution', 'view_attribution')] - ) - self.client.login(username='test', password='test') - - provider1 = Provider.objects.last() - attribution_id_1 = Attribution.objects.last().id - - provider2 = Provider.objects.create() - model_fields = { - "name_de": "BAV", - "name_fr": "OFT", - "name_en": "FOT", - "name_it": "UFT", - "name_rm": "UFT", - "description_de": "Bundesamt für Verkehr", - "description_fr": "Office fédéral des transports", - "description_en": "Federal Office of Transport", - "description_it": "Ufficio federale dei trasporti", - "description_rm": "Uffizi federal da traffic", - "provider": provider2, - } - attribution_id_2 = Attribution.objects.create(**model_fields).id - - response = self.client.get("/api/attributions?lang=fr") - - assert response.status_code == 200 - assert response.json() == { - "items": [ - { - "id": attribution_id_1, - "name": "OFEV", - "name_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - }, - "description": "Office fédéral de l'environnement", - "description_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "provider_id": provider1.id, - }, - { - "id": attribution_id_2, - "name": "OFT", - "name_translations": { - "de": "BAV", - "fr": "OFT", - "en": "FOT", - "it": "UFT", - "rm": "UFT", - }, - "description": "Office fédéral des transports", - "description_translations": { - "de": "Bundesamt für Verkehr", - "fr": "Office fédéral des transports", - "en": "Federal Office of Transport", - "it": "Ufficio federale dei trasporti", - "rm": "Uffizi federal da traffic", - }, - "provider_id": provider2.id, - }, - ] - } - - def test_get_attributions_returns_401_if_not_logged_in(self): - response = self.client.get("/api/providers") - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - - def test_get_attributions_returns_403_if_no_permission(self): - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - response = self.client.get("/api/providers") - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} - - def test_get_dataset_returns_specified_dataset(self): - create_user_with_permissions('test', 'test', [('distributions', 'dataset', 'view_dataset')]) - self.client.login(username='test', password='test') - - dataset_id = Dataset.objects.last().id - - response = self.client.get(f"/api/datasets/{dataset_id}") - - assert response.status_code == 200 - assert response.json() == { - "id": dataset_id, - "slug": "ch.bafu.neophyten-haargurke", - "created": self.time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), - "updated": self.time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), - "provider_id": Provider.objects.last().id, - "attribution_id": Attribution.objects.last().id, - } - - def test_get_dataset_returns_401_if_not_logged_in(self): - dataset_id = Dataset.objects.last().id - response = self.client.get(f"/api/datasets/{dataset_id}") - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - - def test_get_dataset_returns_403_if_no_permission(self): - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - dataset_id = Dataset.objects.last().id - response = self.client.get(f"/api/datasets/{dataset_id}") - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} - - def test_get_datasets_returns_single_dataset_as_expected(self): - create_user_with_permissions('test', 'test', [('distributions', 'dataset', 'view_dataset')]) - self.client.login(username='test', password='test') - - response = self.client.get("/api/datasets") - - dataset = Dataset.objects.last() - assert response.status_code == 200 - assert response.json() == { - "items": [{ - "id": dataset.id, - "slug": "ch.bafu.neophyten-haargurke", - "created": self.time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), - "updated": self.time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), - "provider_id": Provider.objects.last().id, - "attribution_id": Attribution.objects.last().id, - }] - } - - def test_get_datasets_returns_all_datasets_ordered_by_id(self): - create_user_with_permissions('test', 'test', [('distributions', 'dataset', 'view_dataset')]) - self.client.login(username='test', password='test') - - provider2 = Provider.objects.create(acronym_de="Provider2") - attribution2 = Attribution.objects.create( - name_de="Attribution2", - provider=provider2, - ) - model_fields2 = { - "slug": "slug2", - "provider": provider2, - "attribution": attribution2, - } - time_created2 = datetime.datetime(2024, 9, 12, 16, 28, 0, tzinfo=datetime.UTC) - with mock.patch('django.utils.timezone.now', mock.Mock(return_value=time_created2)): - dataset2 = Dataset.objects.create(**model_fields2) - - response = self.client.get("/api/datasets") - - dataset1 = Dataset.objects.first() - assert response.status_code == 200 - assert response.json() == { - "items": [ - { - "id": dataset1.id, - "slug": dataset1.slug, - "created": self.time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), - "updated": self.time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), - "provider_id": Provider.objects.first().id, - "attribution_id": Attribution.objects.first().id, - }, - { - "id": dataset2.id, - "slug": "slug2", - "created": dataset2.created.strftime("%Y-%m-%dT%H:%M:%SZ"), - "updated": dataset2.updated.strftime("%Y-%m-%dT%H:%M:%SZ"), - "provider_id": provider2.id, - "attribution_id": attribution2.id, - }, - ] - } - - def test_get_datasets_returns_401_if_not_logged_in(self): - response = self.client.get("/api/datasets") - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - - def test_get_datasets_returns_403_if_no_permission(self): - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - response = self.client.get("/api/datasets") - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} diff --git a/app/distributions/tests/test_attribution_model.py b/app/distributions/tests/test_attribution_model.py deleted file mode 100644 index fe61151..0000000 --- a/app/distributions/tests/test_attribution_model.py +++ /dev/null @@ -1,129 +0,0 @@ -from distributions.models import Attribution -from provider.models import Provider - -from django.db import IntegrityError -from django.forms import ModelForm -from django.test import TestCase - - -class AttributionTestCase(TestCase): - - def test_object_created_in_db_with_all_fields_defined(self): - attribution = { - "name_de": "BAFU", - "name_fr": "OFEV", - "name_en": "FOEN", - "name_it": "UFAM", - "name_rm": "UFAM", - "description_de": "Bundesamt für Umwelt", - "description_fr": "Office fédéral de l'environnement", - "description_en": "Federal Office for the Environment", - "description_it": "Ufficio federale dell'ambiente", - "description_rm": "Uffizi federal per l'ambient", - "provider": Provider.objects.create(acronym_de="ENSI") - } - Attribution.objects.create(**attribution) - - attributions = Attribution.objects.all() - - self.assertEqual(len(attributions), 1) - - actual = Attribution.objects.last() - self.assertEqual(actual.name_de, attribution["name_de"]) - self.assertEqual(actual.name_fr, attribution["name_fr"]) - self.assertEqual(actual.name_en, attribution["name_en"]) - self.assertEqual(actual.name_it, attribution["name_it"]) - self.assertEqual(actual.name_rm, attribution["name_rm"]) - - self.assertEqual(actual.description_de, attribution["description_de"]) - self.assertEqual(actual.description_fr, attribution["description_fr"]) - self.assertEqual(actual.description_en, attribution["description_en"]) - self.assertEqual(actual.description_it, attribution["description_it"]) - self.assertEqual(actual.description_rm, attribution["description_rm"]) - - self.assertEqual(actual.provider.acronym_de, "ENSI") - - def test_object_created_in_db_with_optional_fields_null(self): - attribution = { - "name_de": "BAFU", - "name_fr": "OFEV", - "name_en": "FOEN", - "name_it": None, - "name_rm": None, - "description_de": "Bundesamt für Umwelt", - "description_fr": "Office fédéral de l'environnement", - "description_en": "Federal Office for the Environment", - "description_it": None, - "description_rm": None, - "provider": Provider.objects.create(acronym_de="ENSI") - } - Attribution.objects.create(**attribution) - - attributions = Attribution.objects.all() - - self.assertEqual(len(attributions), 1) - - actual = Attribution.objects.last() - self.assertEqual(actual.name_de, attribution["name_de"]) - self.assertEqual(actual.name_fr, attribution["name_fr"]) - self.assertEqual(actual.name_en, attribution["name_en"]) - self.assertEqual(actual.name_it, attribution["name_it"]) - self.assertEqual(actual.name_rm, attribution["name_rm"]) - - self.assertEqual(actual.description_de, attribution["description_de"]) - self.assertEqual(actual.description_fr, attribution["description_fr"]) - self.assertEqual(actual.description_en, attribution["description_en"]) - self.assertEqual(actual.description_it, attribution["description_it"]) - self.assertEqual(actual.description_rm, attribution["description_rm"]) - - def test_raises_exception_when_creating_db_object_with_mandatory_field_null(self): - provider = Provider.objects.create() - self.assertRaises( - IntegrityError, Attribution.objects.create, name_de=None, provider=provider - ) - - def test_form_valid_for_blank_optional_field(self): - - class AttributionForm(ModelForm): - - class Meta: - model = Attribution - fields = "__all__" - - provider = Provider.objects.create() - - data = { - "name_de": "BAFU", - "name_fr": "OFEV", - "name_en": "FOEN", - "description_de": "Bundesamt für Umwelt", - "description_fr": "Office fédéral de l'environnement", - "description_en": "Federal Office for the Environment", - "provider": provider.id, - } - form = AttributionForm(data) - - self.assertTrue(form.is_valid()) - - def test_form_invalid_for_blank_mandatory_field(self): - - class AttributionForm(ModelForm): - - class Meta: - model = Attribution - fields = "__all__" - - provider = Provider.objects.create() - - data = { - "name_de": "BAFU", - "name_fr": "OFEV", - "name_en": "FOEN", - "description_de": "Bundesamt für Umwelt", - "description_fr": "Office fédéral de l'environnement", - "description_en": "", # empty but mandatory field - "provider": provider.id, - } - form = AttributionForm(data) - - self.assertFalse(form.is_valid()) diff --git a/app/distributions/tests/test_dataset_model.py b/app/distributions/tests/test_dataset_model.py deleted file mode 100644 index ee3a028..0000000 --- a/app/distributions/tests/test_dataset_model.py +++ /dev/null @@ -1,62 +0,0 @@ -import datetime -from unittest import mock - -from distributions.models import Attribution -from distributions.models import Dataset -from provider.models import Provider - -from django.test import TestCase - - -class DatasetTestCase(TestCase): - - def test_object_created_in_db_with_all_fields_defined(self): - slug = "ch.bafu.neophyten-haargurke" - provider = Provider.objects.create(acronym_de="BAFU") - attribution = Attribution.objects.create( - name_de="Kantone", - provider=provider, - ) - time_created = datetime.datetime(2024, 9, 12, 15, 28, 0, tzinfo=datetime.UTC) - with mock.patch('django.utils.timezone.now', mock.Mock(return_value=time_created)): - Dataset.objects.create( - slug=slug, - provider=provider, - attribution=attribution, - ) - datasets = Dataset.objects.all() - - self.assertEqual(len(datasets), 1) - - dataset = Dataset.objects.last() - - self.assertEqual(dataset.slug, slug) - self.assertEqual(dataset.provider.acronym_de, "BAFU") - self.assertEqual(dataset.attribution.name_de, "Kantone") - - self.assertEqual(dataset.created, time_created) - self.assertEqual(dataset.updated, time_created) - - def test_field_created_matches_creation_time(self): - provider = Provider.objects.create() - attribution = Attribution.objects.create(provider=provider) - - time_created = datetime.datetime(2024, 9, 12, 15, 28, 0, tzinfo=datetime.UTC) - with mock.patch('django.utils.timezone.now', mock.Mock(return_value=time_created)): - dataset = Dataset.objects.create( - provider=provider, - attribution=attribution, - ) - self.assertEqual(dataset.created, time_created) - - def test_field_updated_matches_update_time(self): - provider = Provider.objects.create() - attribution = Attribution.objects.create(provider=provider) - dataset = Dataset.objects.create(provider=provider, attribution=attribution) - - time_updated = datetime.datetime(2024, 9, 12, 15, 42, 0, tzinfo=datetime.UTC) - with mock.patch('django.utils.timezone.now', mock.Mock(return_value=time_updated)): - dataset.slug = "ch.bafu.neophyten-goetterbaum" - dataset.save() - - self.assertEqual(dataset.updated, time_updated) diff --git a/app/provider/tests/__init__.py b/app/provider/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/provider/tests/test_api.py b/app/provider/tests/test_api.py deleted file mode 100644 index 9623702..0000000 --- a/app/provider/tests/test_api.py +++ /dev/null @@ -1,523 +0,0 @@ -from provider.api import provider_to_response -from provider.models import Provider -from provider.schemas import ProviderSchema -from schemas import TranslationsSchema -from utils.testing import create_user_with_permissions - -from django.test import TestCase - - -class ApiTestCase(TestCase): - - def setUp(self): - model_fields = { - "name_de": "Bundesamt für Umwelt", - "name_fr": "Office fédéral de l'environnement", - "name_en": "Federal Office for the Environment", - "name_it": "Ufficio federale dell'ambiente", - "name_rm": "Uffizi federal per l'ambient", - "acronym_de": "BAFU", - "acronym_fr": "OFEV", - "acronym_en": "FOEN", - "acronym_it": "UFAM", - "acronym_rm": "UFAM", - } - Provider.objects.create(**model_fields) - - def test_provider_to_response_returns_response_with_language_as_defined(self): - - model = Provider.objects.last() - - actual = provider_to_response(model, lang="de") - - expected = ProviderSchema( - id=model.id, - name="Bundesamt für Umwelt", - name_translations=TranslationsSchema( - de="Bundesamt für Umwelt", - fr="Office fédéral de l'environnement", - en="Federal Office for the Environment", - it="Ufficio federale dell'ambiente", - rm="Uffizi federal per l'ambient", - ), - acronym="BAFU", - acronym_translations=TranslationsSchema( - de="BAFU", - fr="OFEV", - en="FOEN", - it="UFAM", - rm="UFAM", - ) - ) - - assert actual == expected - - def test_provider_to_response_returns_response_with_default_language_if_undefined(self): - - provider = Provider.objects.last() - provider.name_it = None - provider.name_rm = None - provider.acronym_it = None - provider.acronym_rm = None - - actual = provider_to_response(provider, lang="it") - - expected = ProviderSchema( - id=str(provider.id), - name="Federal Office for the Environment", - name_translations=TranslationsSchema( - de="Bundesamt für Umwelt", - fr="Office fédéral de l'environnement", - en="Federal Office for the Environment", - it=None, - rm=None, - ), - acronym="FOEN", - acronym_translations=TranslationsSchema( - de="BAFU", - fr="OFEV", - en="FOEN", - it=None, - rm=None, - ) - ) - - assert actual == expected - - def test_get_provider_returns_existing_provider_with_default_language(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - provider_id = Provider.objects.last().id - - response = self.client.get(f"/api/providers/{provider_id}") - - assert response.status_code == 200 - assert response.json() == { - "id": provider_id, - "name": "Federal Office for the Environment", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "acronym": "FOEN", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - } - } - - def test_get_provider_returns_provider_with_language_from_query(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - provider_id = Provider.objects.last().id - - response = self.client.get(f"/api/providers/{provider_id}?lang=de") - - assert response.status_code == 200 - assert response.json() == { - "id": provider_id, - "name": "Bundesamt für Umwelt", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "acronym": "BAFU", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - } - } - - def test_get_provider_returns_404_for_nonexisting_provider(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - response = self.client.get("/api/providers/2") - - assert response.status_code == 404 - assert response.json() == {"code": 404, "description": "Resource not found"} - - def test_get_provider_skips_translations_that_are_not_available(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - provider = Provider.objects.last() - provider.name_it = None - provider.name_rm = None - provider.acronym_it = None - provider.acronym_rm = None - provider.save() - - response = self.client.get(f"/api/providers/{provider.id}") - - assert response.status_code == 200 - assert response.json() == { - "id": provider.id, - "name": "Federal Office for the Environment", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - }, - "acronym": "FOEN", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - } - } - - def test_get_provider_returns_provider_with_language_from_header(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - provider_id = Provider.objects.last().id - - response = self.client.get( - f"/api/providers/{provider_id}", headers={"Accept-Language": "de"} - ) - - assert response.status_code == 200 - assert response.json() == { - "id": provider_id, - "name": "Bundesamt für Umwelt", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "acronym": "BAFU", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - } - } - - def test_get_provider_returns_provider_with_language_from_query_param_even_if_header_set(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - provider_id = Provider.objects.last().id - - response = self.client.get( - f"/api/providers/{provider_id}?lang=fr", headers={"Accept-Language": "de"} - ) - - assert response.status_code == 200 - assert response.json() == { - "id": provider_id, - "name": "Office fédéral de l'environnement", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "acronym": "OFEV", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - } - } - - def test_get_provider_returns_provider_with_default_language_if_header_empty(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - provider_id = Provider.objects.last().id - - response = self.client.get(f"/api/providers/{provider_id}", headers={"Accept-Language": ""}) - - assert response.status_code == 200 - assert response.json() == { - "id": provider_id, - "name": "Federal Office for the Environment", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "acronym": "FOEN", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - } - } - - def test_get_provider_returns_provider_with_first_known_language_from_header(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - provider_id = Provider.objects.last().id - - response = self.client.get( - f"/api/providers/{provider_id}", headers={"Accept-Language": "cn, *, de-DE, en"} - ) - - assert response.status_code == 200 - assert response.json() == { - "id": provider_id, - "name": "Bundesamt für Umwelt", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "acronym": "BAFU", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - } - } - - def test_get_provider_returns_provider_with_first_known_language_from_header_ignoring_qfactor( - self - ): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - provider_id = Provider.objects.last().id - - response = self.client.get( - f"/api/providers/{provider_id}", headers={"Accept-Language": "fr;q=0.9, de;q=0.8"} - ) - - assert response.status_code == 200 - assert response.json() == { - "id": provider_id, - "name": "Office fédéral de l'environnement", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "acronym": "OFEV", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - } - } - - def test_get_provider_returns_401_if_not_logged_in(self): - provider_id = Provider.objects.last().id - response = self.client.get(f"/api/providers/{provider_id}") - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - - def test_get_provider_returns_403_if_no_permission(self): - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - provider_id = Provider.objects.last().id - response = self.client.get(f"/api/providers/{provider_id}") - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} - - def test_get_providers_returns_single_provider_with_given_language(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - response = self.client.get("/api/providers?lang=fr") - - assert response.status_code == 200 - assert response.json() == { - "items": [{ - "id": Provider.objects.last().id, - "name": "Office fédéral de l'environnement", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "acronym": "OFEV", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - } - }] - } - - def test_get_providers_skips_translations_that_are_not_available(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - provider = Provider.objects.last() - provider.name_it = None - provider.name_rm = None - provider.acronym_it = None - provider.acronym_rm = None - provider.save() - - response = self.client.get("/api/providers") - - assert response.status_code == 200 - assert response.json() == { - "items": [{ - "id": provider.id, - "name": "Federal Office for the Environment", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - }, - "acronym": "FOEN", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - } - }] - } - - def test_get_providers_returns_provider_with_language_from_header(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - response = self.client.get("/api/providers", headers={"Accept-Language": "de"}) - - assert response.status_code == 200 - assert response.json() == { - "items": [{ - "id": Provider.objects.last().id, - "name": "Bundesamt für Umwelt", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "acronym": "BAFU", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - } - }] - } - - def test_get_providers_returns_all_providers_ordered_by_id_with_given_language(self): - create_user_with_permissions('test', 'test', [('provider', 'provider', 'view_provider')]) - self.client.login(username='test', password='test') - - provider = { - "name_de": "Bundesamt für Verkehr", - "name_fr": "Office fédéral des transports", - "name_en": "Federal Office of Transport", - "name_it": "Ufficio federale dei trasporti", - "name_rm": "Uffizi federal da traffic", - "acronym_de": "BAV", - "acronym_fr": "OFT", - "acronym_en": "FOT", - "acronym_it": "UFT", - "acronym_rm": "UFT", - } - Provider.objects.create(**provider) - - response = self.client.get("/api/providers?lang=fr") - - assert response.status_code == 200 - assert response.json() == { - "items": [ - { - "id": Provider.objects.first().id, - "name": "Office fédéral de l'environnement", - "name_translations": { - "de": "Bundesamt für Umwelt", - "fr": "Office fédéral de l'environnement", - "en": "Federal Office for the Environment", - "it": "Ufficio federale dell'ambiente", - "rm": "Uffizi federal per l'ambient", - }, - "acronym": "OFEV", - "acronym_translations": { - "de": "BAFU", - "fr": "OFEV", - "en": "FOEN", - "it": "UFAM", - "rm": "UFAM", - } - }, - { - "id": Provider.objects.last().id, - "name": "Office fédéral des transports", - "name_translations": { - "de": "Bundesamt für Verkehr", - "fr": "Office fédéral des transports", - "en": "Federal Office of Transport", - "it": "Ufficio federale dei trasporti", - "rm": "Uffizi federal da traffic", - }, - "acronym": "OFT", - "acronym_translations": { - "de": "BAV", - "fr": "OFT", - "en": "FOT", - "it": "UFT", - "rm": "UFT", - } - }, - ] - } - - def test_get_providers_returns_401_if_not_logged_in(self): - response = self.client.get("/api/providers") - - assert response.status_code == 401 - assert response.json() == {"code": 401, "description": "Unauthorized"} - - def test_get_providers_returns_403_if_no_permission(self): - create_user_with_permissions('test', 'test', []) - self.client.login(username='test', password='test') - - response = self.client.get("/api/providers") - - assert response.status_code == 403 - assert response.json() == {"code": 403, "description": "Forbidden"} diff --git a/app/provider/tests/test_provider_model.py b/app/provider/tests/test_provider_model.py deleted file mode 100644 index f4f440b..0000000 --- a/app/provider/tests/test_provider_model.py +++ /dev/null @@ -1,115 +0,0 @@ -from provider.models import Provider - -from django.db import IntegrityError -from django.forms import ModelForm -from django.test import TestCase - - -class ProviderTestCase(TestCase): - - def test_object_created_in_db_with_all_fields_defined(self): - provider_in = { - "name_de": "Bundesamt für Umwelt", - "name_fr": "Office fédéral de l'environnement", - "name_en": "Federal Office for the Environment", - "name_it": "Ufficio federale dell'ambiente", - "name_rm": "Uffizi federal per l'ambient", - "acronym_de": "BAFU", - "acronym_fr": "OFEV", - "acronym_en": "FOEN", - "acronym_it": "UFAM", - "acronym_rm": "UFAM", - } - Provider.objects.create(**provider_in) - - providers = Provider.objects.all() - - self.assertEqual(len(providers), 1) - - actual = Provider.objects.last() - self.assertEqual(provider_in["name_de"], actual.name_de) - self.assertEqual(provider_in["name_fr"], actual.name_fr) - self.assertEqual(provider_in["name_en"], actual.name_en) - self.assertEqual(provider_in["name_it"], actual.name_it) - self.assertEqual(provider_in["name_rm"], actual.name_rm) - - self.assertEqual(provider_in["acronym_de"], actual.acronym_de) - self.assertEqual(provider_in["acronym_fr"], actual.acronym_fr) - self.assertEqual(provider_in["acronym_en"], actual.acronym_en) - self.assertEqual(provider_in["acronym_it"], actual.acronym_it) - self.assertEqual(provider_in["acronym_rm"], actual.acronym_rm) - - def test_object_created_in_db_with_optional_fields_null(self): - provider_in = { - "name_de": "Bundesamt für Umwelt", - "name_fr": "Office fédéral de l'environnement", - "name_en": "Federal Office for the Environment", - "name_it": None, - "name_rm": None, - "acronym_de": "BAFU", - "acronym_fr": "OFEV", - "acronym_en": "FOEN", - "acronym_it": None, - "acronym_rm": None, - } - Provider.objects.create(**provider_in) - - providers = Provider.objects.all() - - self.assertEqual(len(providers), 1) - - actual = Provider.objects.last() - self.assertEqual(actual.name_de, provider_in["name_de"]) - self.assertEqual(actual.name_fr, provider_in["name_fr"]) - self.assertEqual(actual.name_en, provider_in["name_en"]) - self.assertEqual(actual.name_it, provider_in["name_it"]) - self.assertEqual(actual.name_rm, provider_in["name_rm"]) - - self.assertEqual(actual.acronym_de, provider_in["acronym_de"]) - self.assertEqual(actual.acronym_fr, provider_in["acronym_fr"]) - self.assertEqual(actual.acronym_en, provider_in["acronym_en"]) - self.assertEqual(actual.acronym_it, provider_in["acronym_it"]) - self.assertEqual(actual.acronym_rm, provider_in["acronym_rm"]) - - def test_raises_exception_when_creating_db_object_with_mandatory_field_null(self): - self.assertRaises(IntegrityError, Provider.objects.create, name_de=None) - - def test_form_valid_for_blank_optional_field(self): - - class ProviderForm(ModelForm): - - class Meta: - model = Provider - fields = "__all__" - - data = { - "name_de": "Bundesamt für Umwelt", - "name_fr": "Office fédéral de l'environnement", - "name_en": "Federal Office for the Environment", - "acronym_de": "BAFU", - "acronym_fr": "OFEV", - "acronym_en": "FOEN", - } - form = ProviderForm(data) - - self.assertTrue(form.is_valid()) - - def test_form_invalid_for_blank_mandatory_field(self): - - class ProviderForm(ModelForm): - - class Meta: - model = Provider - fields = "__all__" - - data = { - "name_de": "Bundesamt für Umwelt", - "name_fr": "Office fédéral de l'environnement", - "name_en": "Federal Office for the Environment", - "acronym_de": "BAFU", - "acronym_fr": "OFEV", - "acronym_en": "", # empty but mandatory field - } - form = ProviderForm(data) - - self.assertFalse(form.is_valid()) diff --git a/app/tests/access/test_access_api.py b/app/tests/access/test_access_api.py new file mode 100644 index 0000000..1a5c9b6 --- /dev/null +++ b/app/tests/access/test_access_api.py @@ -0,0 +1,612 @@ +from unittest.mock import patch + +from access.api import user_to_response +from access.models import User +from access.schemas import UserSchema +from botocore.exceptions import EndpointConnectionError +from provider.models import Provider +from pytest import fixture + + +@fixture(name='provider') +def fixture_provider(db): + yield Provider.objects.create() + + +@fixture(name='user') +def fixture_user(provider): + yield User.objects.create( + username="dude", + first_name="Jeffrey", + last_name="Lebowski", + email="dude@bowling.com", + provider=provider + ) + + +def test_user_to_response_maps_fields_correctly(user): + + model = User.objects.last() + + actual = user_to_response(model) + + expected = UserSchema( + username="dude", + first_name="Jeffrey", + last_name="Lebowski", + email="dude@bowling.com", + provider_id=Provider.objects.last().id, + ) + + assert actual == expected + + +def test_get_user_returns_existing_user(user, django_user_factory, client): + django_user_factory('test', 'test', [('access', 'user', 'view_user')]) + client.login(username='test', password='test') + + response = client.get("/api/users/dude") + + assert response.status_code == 200 + assert response.json() == { + "username": "dude", + "first_name": "Jeffrey", + "last_name": "Lebowski", + "email": "dude@bowling.com", + "provider_id": Provider.objects.last().id, + } + + +def test_get_user_returns_404_if_nonexisting(user, django_user_factory, client): + django_user_factory('test', 'test', [('access', 'user', 'view_user')]) + client.login(username='test', password='test') + + response = client.get("/api/users/nihilist") + + assert response.status_code == 404 + assert response.json() == {"code": 404, "description": "Resource not found"} + + +def test_get_user_returns_401_if_not_logged_in(user, django_user_factory, client): + response = client.get("/api/users/dude") + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + + +def test_get_user_returns_403_if_no_permission(user, django_user_factory, client): + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + response = client.get("/api/users/dude") + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} + + +def test_get_users_returns_single_user(user, django_user_factory, client): + django_user_factory('test', 'test', [('access', 'user', 'view_user')]) + client.login(username='test', password='test') + + response = client.get("/api/users") + + assert response.status_code == 200 + assert response.json() == { + "items": [{ + "username": "dude", + "first_name": "Jeffrey", + "last_name": "Lebowski", + "email": "dude@bowling.com", + "provider_id": Provider.objects.last().id, + }] + } + + +def test_get_users_returns_users_ordered_by_id(user, django_user_factory, client): + django_user_factory('test', 'test', [('access', 'user', 'view_user')]) + client.login(username='test', password='test') + + model_fields = { + "username": "veteran", + "first_name": "Walter", + "last_name": "Sobchak", + "email": "veteran@bowling.com", + "provider": Provider.objects.last(), + } + User.objects.create(**model_fields) + + response = client.get("/api/users") + + assert response.status_code == 200 + assert response.json() == { + "items": [ + { + "username": "dude", + "first_name": "Jeffrey", + "last_name": "Lebowski", + "email": "dude@bowling.com", + "provider_id": Provider.objects.last().id, + }, + { + "username": "veteran", + "first_name": "Walter", + "last_name": "Sobchak", + "email": "veteran@bowling.com", + "provider_id": Provider.objects.last().id, + }, + ] + } + + +def test_get_users_returns_401_if_not_logged_in(user, django_user_factory, client): + response = client.get("/api/users") + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + + +def test_get_users_returns_403_if_no_permission(user, django_user_factory, client): + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + response = client.get("/api/users") + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} + + +@patch('access.api.create_cognito_user') +def test_post_users_creates_new_user_in_db_and_returns_it( + create_cognito_user, user, django_user_factory, client +): + create_cognito_user.return_value = True + + django_user_factory('test', 'test', [('access', 'user', 'add_user')]) + client.login(username='test', password='test') + + payload = { + "username": "donny", + "first_name": "Theodore Donald", + "last_name": "Kerabatsos", + "email": "donny@bowling.com", + "provider_id": Provider.objects.last().id, + } + + response = client.post("/api/users", data=payload, content_type='application/json') + + assert response.status_code == 201 + assert response.json() == payload + assert User.objects.count() == 2 + assert create_cognito_user.called + + +@patch('access.api.create_cognito_user') +def test_post_users_returns_404_if_provider_id_does_not_exist( + create_cognito_user, user, django_user_factory, client +): + create_cognito_user.return_value = False + + django_user_factory('test', 'test', [('access', 'user', 'add_user')]) + client.login(username='test', password='test') + + non_existing_provider_id = Provider.objects.last().id + 1 + payload = { + "username": "donny", + "first_name": "Theodore Donald", + "last_name": "Kerabatsos", + "email": "donny@bowling.com", + "provider_id": non_existing_provider_id, + } + + response = client.post("/api/users", data=payload, content_type='application/json') + + assert response.status_code == 404 + assert response.json() == {"code": 404, "description": "Resource not found"} + assert not create_cognito_user.called + + +@patch('access.api.create_cognito_user') +def test_post_users_returns_422_if_email_format_invalid( + create_cognito_user, user, django_user_factory, client +): + create_cognito_user.return_value = False + + django_user_factory('test', 'test', [('access', 'user', 'add_user')]) + client.login(username='test', password='test') + + invalid_email = "donny_at_bowling_dot_com" + payload = { + "username": "donny", + "first_name": "Theodore Donald", + "last_name": "Kerabatsos", + "email": invalid_email, + "provider_id": Provider.objects.last().id, + } + + response = client.post("/api/users", data=payload, content_type='application/json') + + assert response.status_code == 422 + assert response.json() == {'code': 422, 'description': ["Enter a valid email address."]} + assert not create_cognito_user.called + + +@patch('access.api.create_cognito_user') +def test_post_users_returns_409_if_user_exists_already( + create_cognito_user, user, django_user_factory, client +): + create_cognito_user.return_value = False + + django_user_factory('test', 'test', [('access', 'user', 'add_user')]) + client.login(username='test', password='test') + + payload = { + "username": "dude", + "first_name": "Theodore Donald", + "last_name": "Kerabatsos", + "email": "donny@bowling.com", + "provider_id": Provider.objects.last().id, + } + + response = client.post("/api/users", data=payload, content_type='application/json') + + assert response.status_code == 409 + assert response.json() == { + 'code': 409, 'description': ["User with this User name already exists."] + } + assert not create_cognito_user.called + + +@patch('access.api.create_cognito_user') +def test_post_users_returns_409_and_reports_all_errors_if_multiple_things_amiss( + create_cognito_user, user, django_user_factory, client +): + create_cognito_user.return_value = False + + django_user_factory('test', 'test', [('access', 'user', 'add_user')]) + client.login(username='test', password='test') + + invalid_email = "donny_at_bowling_dot_com" + payload = { + "username": "dude", + "first_name": "Theodore Donald", + "last_name": "Kerabatsos", + "email": invalid_email, + "provider_id": Provider.objects.last().id, + } + + response = client.post("/api/users", data=payload, content_type='application/json') + + assert response.status_code == 409 + assert response.json() == { + 'code': 409, + 'description': ["Enter a valid email address.", "User with this User name already exists."] + } + assert not create_cognito_user.called + + +@patch('access.api.create_cognito_user') +def test_post_users_returns_500_if_cognito_inconsistent( + create_cognito_user, user, django_user_factory, client +): + create_cognito_user.return_value = False + + django_user_factory('test', 'test', [('access', 'user', 'add_user')]) + client.login(username='test', password='test') + + payload = { + "username": "donny", + "first_name": "Theodore Donald", + "last_name": "Kerabatsos", + "email": "donny@bowling.com", + "provider_id": Provider.objects.last().id, + } + + response = client.post("/api/users", data=payload, content_type='application/json') + + assert response.status_code == 500 + assert response.json() == {'code': 500, 'description': 'Internal Server Error'} + assert User.objects.count() == 1 + assert create_cognito_user.called + + +@patch('access.api.create_cognito_user') +def test_post_users_returns_503_if_cognito_down( + create_cognito_user, user, django_user_factory, client +): + create_cognito_user.side_effect = EndpointConnectionError(endpoint_url='http://localhost') + + django_user_factory('test', 'test', [('access', 'user', 'add_user')]) + client.login(username='test', password='test') + + payload = { + "username": "donny", + "first_name": "Theodore Donald", + "last_name": "Kerabatsos", + "email": "donny@bowling.com", + "provider_id": Provider.objects.last().id, + } + + response = client.post("/api/users", data=payload, content_type='application/json') + + assert response.status_code == 503 + assert response.json() == {'code': 503, 'description': 'Service Unavailable'} + assert User.objects.count() == 1 + assert create_cognito_user.called + + +@patch('access.api.create_cognito_user') +def test_post_user_returns_401_if_not_logged_in( + create_cognito_user, user, django_user_factory, client +): + create_cognito_user.return_value = True + + response = client.post("/api/users", data={}, content_type='application/json') + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + assert not create_cognito_user.called + assert User.objects.count() == 1 + + +@patch('access.api.create_cognito_user') +def test_post_user_returns_403_if_no_permission( + create_cognito_user, user, django_user_factory, client +): + create_cognito_user.return_value = True + + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + response = client.post("/api/users", data={}, content_type='application/json') + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} + assert not create_cognito_user.called + assert User.objects.count() == 1 + + +@patch('access.api.disable_cognito_user') +def test_delete_user_deletes_user(disable_cognito_user, user, django_user_factory, client): + disable_cognito_user.return_value = True + + django_user_factory('test', 'test', [('access', 'user', 'delete_user')]) + client.login(username='test', password='test') + + response = client.delete("/api/users/dude") + + assert response.status_code == 204 + assert response.content == b'' + assert User.objects.count() == 0 + assert disable_cognito_user.called + + +@patch('access.api.disable_cognito_user') +def test_delete_user_returns_404_if_nonexisting( + disable_cognito_user, user, django_user_factory, client +): + disable_cognito_user.return_value = False + + django_user_factory('test', 'test', [('access', 'user', 'delete_user')]) + client.login(username='test', password='test') + + response = client.delete("/api/users/lebowski") + + assert response.status_code == 404 + assert response.json() == {"code": 404, "description": "Resource not found"} + assert User.objects.count() == 1 + assert not disable_cognito_user.called + + +@patch('access.api.disable_cognito_user') +def test_delete_user_returns_500_if_cognito_inconsistent( + disable_cognito_user, user, django_user_factory, client +): + disable_cognito_user.return_value = False + + django_user_factory('test', 'test', [('access', 'user', 'delete_user')]) + client.login(username='test', password='test') + + response = client.delete("/api/users/dude") + + assert response.status_code == 500 + assert response.json() == {"code": 500, "description": "Internal Server Error"} + assert User.objects.count() == 1 + assert disable_cognito_user.called + + +@patch('access.api.disable_cognito_user') +def test_delete_user_returns_503_if_cognito_down( + disable_cognito_user, user, django_user_factory, client +): + disable_cognito_user.side_effect = EndpointConnectionError(endpoint_url='http://localhost') + + django_user_factory('test', 'test', [('access', 'user', 'delete_user')]) + client.login(username='test', password='test') + + response = client.delete("/api/users/dude") + + assert response.status_code == 503 + assert response.json() == {"code": 503, "description": "Service Unavailable"} + assert User.objects.count() == 1 + assert disable_cognito_user.called + + +@patch('access.api.disable_cognito_user') +def test_delete_user_returns_401_if_not_logged_in( + disable_cognito_user, user, django_user_factory, client +): + disable_cognito_user.return_value = True + + response = client.delete("/api/users/dude", data={}, content_type='application/json') + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + assert User.objects.count() == 1 + assert not disable_cognito_user.called + + +@patch('access.api.disable_cognito_user') +def test_delete_user_returns_403_if_no_permission( + disable_cognito_user, user, django_user_factory, client +): + disable_cognito_user.return_value = True + + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + response = client.delete("/api/users/dude", data={}, content_type='application/json') + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} + assert User.objects.count() == 1 + assert not disable_cognito_user.called + + +def test_update_user_returns_401_if_not_logged_in(user, django_user_factory, client): + response = client.put("/api/users/dude") + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + + +def test_update_user_returns_403_if_no_permission(user, django_user_factory, client): + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + response = client.put("/api/users/dude") + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} + + +@patch('access.api.update_cognito_user') +def test_update_user_updates_existing_user_as_expected( + update_cognito_user, user, django_user_factory, client +): + update_cognito_user.return_value = True + + django_user_factory('test', 'test', [('access', 'user', 'change_user')]) + client.login(username='test', password='test') + + payload = { + "username": "dude", + "first_name": "Jeff", + "last_name": "Bridges", + "email": "tron@hollywood.com", + "provider_id": Provider.objects.last().id, + } + + response = client.put("/api/users/dude", data=payload, content_type='application/json') + + assert response.status_code == 200 + assert response.json() == payload + user = User.objects.filter(username="dude").first() + for key, value in payload.items(): + assert getattr(user, key) == value + assert update_cognito_user.called + + +def test_update_user_returns_404_and_leaves_user_as_is_if_user_nonexistent( + user, django_user_factory, client +): + + django_user_factory('test', 'test', [('access', 'user', 'change_user')]) + client.login(username='test', password='test') + + user_before = User.objects.filter(username="dude").first() + payload = { + "username": "dude", + "first_name": "Jeff", + "last_name": "Bridges", + "email": "tron@hollywood.com", + "provider_id": Provider.objects.last().id, + } + + nonexistent_username = "maude" + response = client.put( + f"/api/users/{nonexistent_username}", data=payload, content_type='application/json' + ) + + assert response.status_code == 404 + assert response.json() == {"code": 404, "description": "Resource not found"} + user_after = User.objects.filter(username="dude").first() + assert user_after == user_before + + +def test_update_user_returns_400_and_leaves_user_as_is_if_provider_nonexistent( + user, django_user_factory, client +): + + django_user_factory('test', 'test', [('access', 'user', 'change_user')]) + client.login(username='test', password='test') + + user_before = User.objects.filter(username="dude").first() + nonexistent_id = Provider.objects.last().id + 1234 + payload = { + "username": "dude", + "first_name": "Jeff", + "last_name": "Bridges", + "email": "tron@hollywood.com", + "provider_id": nonexistent_id, + } + + response = client.put("/api/users/dude", data=payload, content_type="application/json") + + assert response.status_code == 400 + assert response.json() == {"code": 400, "description": "Provider does not exist"} + user_after = User.objects.filter(username="dude").first() + assert user_after == user_before + + +@patch('access.api.update_cognito_user') +def test_update_user_returns_500_and_leaves_user_as_is_if_cognito_inconsistent( + update_cognito_user, user, django_user_factory, client +): + update_cognito_user.return_value = False + + django_user_factory('test', 'test', [('access', 'user', 'change_user')]) + client.login(username='test', password='test') + + user_before = User.objects.filter(username="dude").first() + payload = { + "username": "dude", + "first_name": "Jeff", + "last_name": "Bridges", + "email": "tron@hollywood.com", + "provider_id": Provider.objects.last().id, + } + + response = client.put("/api/users/dude", data=payload, content_type="application/json") + + assert response.status_code == 500 + assert response.json() == {"code": 500, "description": "Internal Server Error"} + user_after = User.objects.filter(username="dude").first() + assert user_after == user_before + assert update_cognito_user.called + + +@patch('access.api.update_cognito_user') +def test_update_user_returns_503_and_leaves_user_as_is_if_cognito_down( + update_cognito_user, user, django_user_factory, client +): + update_cognito_user.side_effect = EndpointConnectionError(endpoint_url='http://localhost') + + django_user_factory('test', 'test', [('access', 'user', 'change_user')]) + client.login(username='test', password='test') + + user_before = User.objects.filter(username="dude").first() + payload = { + "username": "dude", + "first_name": "Jeff", + "last_name": "Bridges", + "email": "tron@hollywood.com", + "provider_id": Provider.objects.last().id, + } + + response = client.put("/api/users/dude", data=payload, content_type="application/json") + + assert response.status_code == 503 + assert response.json() == {"code": 503, "description": "Service Unavailable"} + user_after = User.objects.filter(username="dude").first() + assert user_after == user_before + assert update_cognito_user.called diff --git a/app/tests/access/test_access_models.py b/app/tests/access/test_access_models.py new file mode 100644 index 0000000..ddf96f4 --- /dev/null +++ b/app/tests/access/test_access_models.py @@ -0,0 +1,81 @@ +import pytest +from access.models import User +from provider.models import Provider +from pytest import fixture + +from django.core.exceptions import ValidationError +from django.forms import ModelForm + + +@fixture(name='provider') +def fixture_provider(db): + yield Provider.objects.create() + + +def test_user_stored_as_expected_for_valid_input(provider): + model_fields = { + "username": "dude", + "first_name": "Jeffrey", + "last_name": "Lebowski", + "email": "dude@bowling.com", + "provider": provider, + } + + actual = User.objects.create(**model_fields) + + assert actual.username == "dude" + assert actual.first_name == "Jeffrey" + assert actual.last_name == "Lebowski" + assert actual.email == "dude@bowling.com" + assert actual.provider == provider + + +def test_user_raises_exception_for_user_with_existing_user_name(provider): + User.objects.create( + username="dude", + first_name="Jeffrey", + last_name="Lebowski", + email="dude@bowling.com", + provider=provider + ) + with pytest.raises(ValidationError): + User.objects.create( + username="dude", + first_name="XXX", + last_name="YYY", + email="xxx@yyy.com", + provider=provider + ) + + +def test_user_with_invalid_email_raises_exception_when_creating_db_record(provider): + model_fields = { + "username": "dude", + "first_name": "Jeffrey", + "last_name": "Lebowski", + "email": "dude_at_bowling_dot_com", + "provider": provider, + } + + with pytest.raises(ValidationError): + User.objects.create(**model_fields) + + +def test_form_invalid_for_user_with_invalid_email(provider): + + class UserForm(ModelForm): + + class Meta: + model = User + fields = "__all__" + + data = { + "username": "dude", + "first_name": "Jeffrey", + "last_name": "Lebowski", + "email": "dude_at_bowling_dot_com", + "provider": provider, + } + form = UserForm(data) + + assert not form.is_valid() diff --git a/app/tests/bod/test_bod_migrate_command.py b/app/tests/bod/test_bod_migrate_command.py new file mode 100644 index 0000000..567b05d --- /dev/null +++ b/app/tests/bod/test_bod_migrate_command.py @@ -0,0 +1,266 @@ +from io import StringIO + +from bod.models import BodContactOrganisation +from bod.models import BodDataset +from bod.models import BodTranslations +from distributions.models import Attribution +from distributions.models import Dataset +from provider.models import Provider +from pytest import fixture + +from django.core.management import call_command + + +@fixture(name='bod_translation') +def fixture_bod_translation(db): + yield BodTranslations.objects.create( + msg_id="ch.bafu", de="BAFU", fr="OFEV", en="FOEN", it="UFAM", rm="UFAM" + ) + + +@fixture(name='bod_contact_organisation') +def fixture_bod_contact_organisation(bod_translation): + yield BodContactOrganisation.objects.create( + pk_contactorganisation_id=17, + name_de="Bundesamt für Umwelt", + name_fr="Office fédéral de l'environnement", + name_en="Federal Office for the Environment", + name_it="Ufficio federale dell'ambiente", + name_rm="Uffizi federal per l'ambient", + abkuerzung_de="BAFU", + abkuerzung_fr="OFEV", + abkuerzung_en="FOEN", + abkuerzung_it="UFAM", + abkuerzung_rm="UFAM", + attribution="ch.bafu" + ) + + +@fixture(name='bod_dataset') +def fixture_bod_dataset(bod_contact_organisation): + yield BodDataset.objects.create( + id=170, + id_dataset="ch.bafu.auen-vegetationskarten", + fk_contactorganisation_id=bod_contact_organisation.pk_contactorganisation_id + ) + + +def test_command_imports(bod_dataset): + out = StringIO() + call_command("bod_migrate", verbosity=2, stdout=out) + assert "Added provider 'Federal Office for the Environment'" in out.getvalue() + assert "1 provider(s) added" in out.getvalue() + assert "1 attribution(s) added" in out.getvalue() + assert "1 dataset(s) added" in out.getvalue() + assert Provider.objects.count() == 1 + assert Attribution.objects.count() == 1 + assert Dataset.objects.count() == 1 + + provider = Provider.objects.first() + assert provider.name_de == "Bundesamt für Umwelt" + assert provider.name_fr == "Office fédéral de l'environnement" + assert provider.name_en == "Federal Office for the Environment" + assert provider.name_it == "Ufficio federale dell'ambiente" + assert provider.name_rm == "Uffizi federal per l'ambient" + assert provider.acronym_de == "BAFU" + assert provider.acronym_fr == "OFEV" + assert provider.acronym_en == "FOEN" + assert provider.acronym_it == "UFAM" + assert provider.acronym_rm == "UFAM" + + attribution = provider.attribution_set.first() + assert attribution.name_de == "BAFU" + assert attribution.name_fr == "OFEV" + assert attribution.name_en == "FOEN" + assert attribution.name_it == "UFAM" + assert attribution.name_rm == "UFAM" + assert attribution.description_de == "BAFU" + assert attribution.description_fr == "OFEV" + assert attribution.description_en == "FOEN" + assert attribution.description_it == "UFAM" + assert attribution.description_rm == "UFAM" + + dataset = provider.dataset_set.first() + assert dataset.attribution == attribution + assert dataset.slug == "ch.bafu.auen-vegetationskarten" + + +def test_command_updates(bod_contact_organisation, bod_dataset): + # Add objects that will be updated + provider = Provider.objects.create( + name_de="XXX", + name_fr="XXX", + name_en="XXX", + acronym_de="BAFU", + acronym_fr="", + acronym_en="", + _legacy_id=bod_contact_organisation.pk_contactorganisation_id + ) + attribution = Attribution.objects.create( + name_de="XXX", + name_fr="XXX", + name_en="XXX", + description_de="BAFU", + description_fr="", + description_en="", + provider=provider, + _legacy_id=bod_contact_organisation.pk_contactorganisation_id + ) + dataset = Dataset.objects.create( + slug="XXX", provider=provider, attribution=attribution, _legacy_id=bod_dataset.id + ) + + out = StringIO() + call_command("bod_migrate", verbosity=2, stdout=out) + assert f"Changed Provider {provider.id} name_de" in out.getvalue() + assert f"Changed Provider {provider.id} acronym_de" not in out.getvalue() + assert "1 provider(s) updated" in out.getvalue() + assert f"Changed Attribution {attribution.id} name_de" in out.getvalue() + assert f"Changed Attribution {attribution.id} description_de" not in out.getvalue() + assert "1 attribution(s) updated" in out.getvalue() + assert f"Changed Dataset {dataset.id} slug" in out.getvalue() + assert "1 dataset(s) updated" in out.getvalue() + assert Provider.objects.count() == 1 + assert Attribution.objects.count() == 1 + assert Dataset.objects.count() == 1 + + provider = Provider.objects.first() + assert provider.name_de == "Bundesamt für Umwelt" + assert provider.name_fr == "Office fédéral de l'environnement" + assert provider.name_en == "Federal Office for the Environment" + assert provider.name_it == "Ufficio federale dell'ambiente" + assert provider.name_rm == "Uffizi federal per l'ambient" + assert provider.acronym_de == "BAFU" + assert provider.acronym_fr == "OFEV" + assert provider.acronym_en == "FOEN" + assert provider.acronym_it == "UFAM" + assert provider.acronym_rm == "UFAM" + + attribution = provider.attribution_set.first() + assert attribution.name_de == "BAFU" + assert attribution.name_fr == "OFEV" + assert attribution.name_en == "FOEN" + assert attribution.name_it == "UFAM" + assert attribution.name_rm == "UFAM" + assert attribution.description_de == "BAFU" + assert attribution.description_fr == "OFEV" + assert attribution.description_en == "FOEN" + assert attribution.description_it == "UFAM" + assert attribution.description_rm == "UFAM" + + dataset = provider.dataset_set.first() + assert dataset.slug == "ch.bafu.auen-vegetationskarten" + + +def test_command_removes_orphaned(bod_dataset): + # Add objects which will be removed + provider = Provider.objects.create( + name_de="XXX", + name_fr="XXX", + name_en="XXX", + acronym_de="XXX", + acronym_fr="XXX", + acronym_en="XXX", + _legacy_id=16 + ) + attribution = Attribution.objects.create( + name_de="XXX", + name_fr="XXX", + name_en="XXX", + description_de="XXX", + description_fr="XXX", + description_en="XXX", + provider=provider, + _legacy_id=16 + ) + Dataset.objects.create(slug="XXX", provider=provider, attribution=attribution, _legacy_id=160) + + # Add objects which will not be removed + provider = Provider.objects.create( + name_de="YYY", + name_fr="YYY", + name_en="YYY", + acronym_de="YYYY", + acronym_fr="YYYY", + acronym_en="YYYY", + ) + attribution = Attribution.objects.create( + name_de="YYYY", + name_fr="YYYY", + name_en="YYYY", + description_de="YYY", + description_fr="YYY", + description_en="YYY", + provider=provider + ) + Dataset.objects.create(slug="YYYY", provider=provider, attribution=attribution) + + out = StringIO() + call_command("bod_migrate", verbosity=2, stdout=out) + assert "1 provider(s) removed" in out.getvalue() + assert "1 attribution(s) removed" in out.getvalue() + assert "1 dataset(s) removed" in out.getvalue() + assert "1 provider(s) added" in out.getvalue() + assert "1 attribution(s) added" in out.getvalue() + assert "1 dataset(s) added" in out.getvalue() + assert Provider.objects.count() == 2 + assert Attribution.objects.count() == 2 + assert Dataset.objects.count() == 2 + assert {'BAFU', 'YYYY'} == set(Provider.objects.values_list('acronym_de', flat=True)) + assert {'BAFU', 'YYYY'} == set(Attribution.objects.values_list('name_de', flat=True)) + assert {'ch.bafu.auen-vegetationskarten', + 'YYYY'} == set(Dataset.objects.values_list('slug', flat=True)) + + +def test_command_does_not_import_if_dry_run(bod_dataset): + out = StringIO() + call_command("bod_migrate", dry_run=True, stdout=out) + assert "1 provider(s) added" in out.getvalue() + assert "1 attribution(s) added" in out.getvalue() + assert "1 dataset(s) added" in out.getvalue() + assert "dry run, aborting" in out.getvalue() + assert Provider.objects.count() == 0 + assert Attribution.objects.count() == 0 + assert Dataset.objects.count() == 0 + + +def test_command_clears_existing_data(bod_dataset): + provider = Provider.objects.create( + name_de="XXX", + name_fr="XXX", + name_en="XXX", + acronym_de="XXX", + acronym_fr="XXX", + acronym_en="XXX", + _legacy_id=150 + ) + attribution = Attribution.objects.create( + name_de="XXX", + name_fr="XXX", + name_en="XXX", + description_de="XXX", + description_fr="XXX", + description_en="XXX", + provider=provider + ) + Dataset.objects.create(slug="YYYY", provider=provider, attribution=attribution) + + out = StringIO() + call_command("bod_migrate", clear=True, stdout=out) + assert "1 provider(s) cleared" in out.getvalue() + assert "1 attribution(s) cleared" in out.getvalue() + assert "1 dataset(s) cleared" in out.getvalue() + assert "1 provider(s) added" in out.getvalue() + assert "1 attribution(s) added" in out.getvalue() + assert "1 dataset(s) added" in out.getvalue() + assert Provider.objects.count() == 1 + assert Dataset.objects.count() == 1 + + provider = Provider.objects.first() + assert provider.name_de == "Bundesamt für Umwelt" + + attribution = provider.attribution_set.first() + assert attribution.name_de == "BAFU" + + dataset = provider.dataset_set.first() + assert dataset.slug == "ch.bafu.auen-vegetationskarten" diff --git a/app/tests/cognito/test_cognito_client.py b/app/tests/cognito/test_cognito_client.py new file mode 100644 index 0000000..d948243 --- /dev/null +++ b/app/tests/cognito/test_cognito_client.py @@ -0,0 +1,267 @@ +from unittest.mock import call +from unittest.mock import patch + +from cognito.utils.client import Client +from cognito.utils.client import user_attributes_to_dict + + +def test_user_attributes_to_dict(): + attributes = [{'Name': 'email', 'Value': 'test@example.org'}, {'Name': 'flag', 'Value': 'true'}] + attributes = user_attributes_to_dict(attributes) + assert attributes == {'email': 'test@example.org', 'flag': 'true'} + + +@patch('cognito.utils.client.client') +def test_list_users_returns_only_managed(boto3, cognito_user_response_factory): + managed = cognito_user_response_factory('1234', managed=True) + unmanaged = cognito_user_response_factory('1234', managed=False) + boto3.return_value.list_users.return_value = {'Users': [managed, unmanaged]} + + client = Client() + users = client.list_users() + assert users == [managed] + assert call().list_users(UserPoolId=client.user_pool_id, Limit=60) in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_list_users_pagination(boto3, cognito_user_response_factory): + users = [cognito_user_response_factory(str(count), True) for count in range(1, 131)] + response_1 = {'Users': users[0:60], 'PaginationToken': '1'} + response_2 = {'Users': users[60:120], 'PaginationToken': '2'} + response_3 = {'Users': users[120:130]} + boto3.return_value.list_users.side_effect = [response_1, response_2, response_3] + + client = Client() + users = client.list_users() + + assert [user['Username'] for user in users] == [str(x) for x in range(1, 131)] + assert call().list_users(UserPoolId=client.user_pool_id, Limit=60) in boto3.mock_calls + assert call().list_users( + UserPoolId=client.user_pool_id, Limit=60, PaginationToken='2' + ) in boto3.mock_calls + assert call().list_users( + UserPoolId=client.user_pool_id, Limit=60, PaginationToken='2' + ) in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_get_user_returns_managed(boto3, cognito_user_response_factory): + response = cognito_user_response_factory('1234', managed=True, attributes_key='UserAttributes') + boto3.return_value.admin_get_user.return_value = response + + client = Client() + user = client.get_user('1234') + assert user == response + assert call().admin_get_user( + UserPoolId=client.user_pool_id, Username='1234' + ) in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_get_user_does_not_return_unmanaged(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=False, attributes_key='UserAttributes' + ) + + client = Client() + user = client.get_user('1234') + assert user is None + assert call().admin_get_user( + UserPoolId=client.user_pool_id, Username='1234' + ) in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_get_user_returns_unmanaged(boto3, cognito_user_response_factory): + response = cognito_user_response_factory('1234', managed=False, attributes_key='UserAttributes') + boto3.return_value.admin_get_user.return_value = response + + client = Client() + user = client.get_user('1234', return_unmanaged=True) + assert user == response + assert call().admin_get_user( + UserPoolId=client.user_pool_id, Username='1234' + ) in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_create_user_creates_managed(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = None + + client = Client() + created = client.create_user('1234', 'test@example.org') + assert created is True + assert call().admin_create_user( + UserPoolId=client.user_pool_id, + Username='1234', + UserAttributes=[{ + 'Name': 'email', 'Value': 'test@example.org' + }, { + 'Name': client.managed_flag_name, 'Value': 'true' + }], + DesiredDeliveryMediums=['EMAIL'] + ) in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_create_user_does_not_create_if_managed_exists(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=True, attributes_key='UserAttributes' + ) + + client = Client() + created = client.create_user('1234', 'test@example.org') + assert created is False + assert call().admin_create_user( + UserPoolId=client.user_pool_id, + Username='1234', + UserAttributes=[{ + 'Name': 'email', 'Value': 'test@example.org' + }, { + 'Name': client.managed_flag_name, 'Value': 'true' + }], + DesiredDeliveryMediums=['EMAIL'] + ) not in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_create_user_does_not_create_if_unmanaged_exists(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=False, attributes_key='UserAttributes' + ) + + client = Client() + created = client.create_user('1234', 'test@example.org') + assert created is False + assert call().admin_create_user( + UserPoolId=client.user_pool_id, + Username='1234', + UserAttributes=[{ + 'Name': 'email', 'Value': 'test@example.org' + }, { + 'Name': client.managed_flag_name, 'Value': 'true' + }], + DesiredDeliveryMediums=['EMAIL'] + ) not in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_delete_user_deletes_managed(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=True, attributes_key='UserAttributes' + ) + + client = Client() + deleted = client.delete_user('1234') + assert deleted is True + assert call().admin_delete_user( + UserPoolId=client.user_pool_id, Username='1234' + ) in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_delete_user_does_not_delete_unmanaged(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=False, attributes_key='UserAttributes' + ) + + client = Client() + deleted = client.delete_user('1234') + assert deleted is False + assert call().admin_delete_user( + UserPoolId=client.user_pool_id, Username='1234' + ) not in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_update_user_updates_managed(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=True, attributes_key='UserAttributes' + ) + + client = Client() + updated = client.update_user('1234', 'test@example.org') + assert updated is True + assert call().admin_update_user_attributes( + UserPoolId=client.user_pool_id, + Username='1234', + UserAttributes=[{ + 'Name': 'email', 'Value': 'test@example.org' + }] + ) in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_update_user_does_not_update_unmanaged(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=False, attributes_key='UserAttributes' + ) + + client = Client() + updated = client.update_user('1234', 'test@example.org') + assert updated is False + assert call().admin_update_user_attributes( + UserPoolId=client.user_pool_id, + Username='1234', + UserAttributes=[{ + 'Name': 'email', 'Value': 'test@example.org' + }, { + 'Name': client.managed_flag_name, 'Value': 'true' + }] + ) not in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_disable_user_disables_managed(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=True, attributes_key='UserAttributes' + ) + + client = Client() + disabled = client.disable_user('1234') + assert disabled is True + assert call().admin_disable_user( + UserPoolId=client.user_pool_id, Username='1234' + ) in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_disable_user_does_not_disable_unmanaged(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=False, attributes_key='UserAttributes' + ) + + client = Client() + disabled = client.disable_user('1234') + assert disabled is False + assert call().admin_disable_user( + UserPoolId=client.user_pool_id, Username='1234' + ) not in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_enable_user_enables_managed(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=True, attributes_key='UserAttributes' + ) + + client = Client() + enabled = client.enable_user('1234') + assert enabled is True + assert call().admin_enable_user( + UserPoolId=client.user_pool_id, Username='1234' + ) in boto3.mock_calls + + +@patch('cognito.utils.client.client') +def test_enable_user_does_not_enable_unmanaged(boto3, cognito_user_response_factory): + boto3.return_value.admin_get_user.return_value = cognito_user_response_factory( + '1234', managed=False, attributes_key='UserAttributes' + ) + + client = Client() + enabled = client.enable_user('1234') + assert enabled is False + assert call().admin_enable_user( + UserPoolId=client.user_pool_id, Username='1234' + ) not in boto3.mock_calls diff --git a/app/tests/cognito/test_cognito_sync_command.py b/app/tests/cognito/test_cognito_sync_command.py new file mode 100644 index 0000000..d8d47d0 --- /dev/null +++ b/app/tests/cognito/test_cognito_sync_command.py @@ -0,0 +1,178 @@ +from io import StringIO +from unittest.mock import call +from unittest.mock import patch + +from access.models import User +from provider.models import Provider +from pytest import fixture + +from django.core.management import call_command +from django.utils import timezone + + +@fixture(name='provider') +def fixture_provider(db): + yield Provider.objects.create( + acronym_de="BAFU", + acronym_fr="OFEV", + acronym_en="FOEN", + acronym_it="UFAM", + acronym_rm="UFAM", + name_de="Bundesamt für Umwelt", + name_fr="Office fédéral de l'environnement", + name_en="Federal Office for the Environment", + name_it="Ufficio federale dell'ambiente", + name_rm="Uffizi federal per l'ambient", + ) + + +@fixture(name='user') +def fixture_user(provider): + yield User.objects.create( + username='1', first_name='1', last_name='1', email='1@example.org', provider=provider + ) + + +@patch('cognito.management.commands.cognito_sync.Client') +def test_command_adds(cognito_client, user): + cognito_client.return_value.list_users.return_value = [] + + out = StringIO() + call_command('cognito_sync', verbosity=2, stdout=out) + + assert 'adding user 1' in out.getvalue() + assert '1 user(s) added' in out.getvalue() + assert call().create_user('1', '1@example.org') in cognito_client.mock_calls + + +@patch('cognito.management.commands.cognito_sync.Client') +def test_command_deletes(cognito_client, db, cognito_user_response_factory): + cognito_client.return_value.list_users.return_value = [ + cognito_user_response_factory('1', '1@example.org') + ] + + out = StringIO() + call_command('cognito_sync', verbosity=2, stdout=out) + + assert 'deleting user 1' in out.getvalue() + assert '1 user(s) deleted' in out.getvalue() + assert call().delete_user('1') in cognito_client.mock_calls + + +@patch('cognito.management.commands.cognito_sync.Client') +def test_command_updates(cognito_client, user, cognito_user_response_factory): + cognito_client.return_value.list_users.return_value = [ + cognito_user_response_factory('1', '2@example.org') + ] + + out = StringIO() + call_command('cognito_sync', verbosity=2, stdout=out) + + assert 'updating user 1' in out.getvalue() + assert '1 user(s) updated' in out.getvalue() + assert call().update_user('1', '1@example.org') in cognito_client.mock_calls + + +@patch('cognito.management.commands.cognito_sync.Client') +def test_command_updates_disabled(cognito_client, user, cognito_user_response_factory): + user.deleted_at = timezone.now() + user.save() + cognito_client.return_value.list_users.return_value = [ + cognito_user_response_factory('1', '1@example.org') + ] + + out = StringIO() + call_command('cognito_sync', verbosity=2, stdout=out) + + assert 'disabling user 1' in out.getvalue() + assert '1 user(s) disabled' in out.getvalue() + assert call().disable_user('1') in cognito_client.mock_calls + + +@patch('cognito.management.commands.cognito_sync.Client') +def test_command_updates_enabled(cognito_client, user, cognito_user_response_factory): + cognito_client.return_value.list_users.return_value = [ + cognito_user_response_factory('1', '1@example.org', False) + ] + + out = StringIO() + call_command('cognito_sync', verbosity=2, stdout=out) + + assert 'enabling user 1' in out.getvalue() + assert '1 user(s) enabled' in out.getvalue() + assert call().enable_user('1') in cognito_client.mock_calls + + +@patch('cognito.management.commands.cognito_sync.Client') +def test_command_does_not_updates_if_unchanged(cognito_client, user, cognito_user_response_factory): + cognito_client.return_value.list_users.return_value = [ + cognito_user_response_factory('1', '1@example.org') + ] + + out = StringIO() + call_command('cognito_sync', verbosity=2, stdout=out) + + assert 'nothing to be done' in out.getvalue() + + +@patch('builtins.input') +@patch('cognito.management.commands.cognito_sync.Client') +def test_command_clears_if_confirmed(cognito_client, input_, user, cognito_user_response_factory): + input_.side_effect = ['yes'] + cognito_client.return_value.list_users.side_effect = [[ + cognito_user_response_factory('1', '1@example.org') + ], []] + + out = StringIO() + call_command('cognito_sync', clear=True, verbosity=2, stdout=out) + + assert 'This action will delete all managed users from cognito' in out.getvalue() + assert 'deleting user 1' in out.getvalue() + assert '1 user(s) deleted' in out.getvalue() + assert 'adding user 1' in out.getvalue() + assert '1 user(s) added' in out.getvalue() + assert call().delete_user('1') in cognito_client.mock_calls + assert call().create_user('1', '1@example.org') in cognito_client.mock_calls + + +@patch('builtins.input') +@patch('cognito.management.commands.cognito_sync.Client') +def test_command_does_not_clears_if_not_confirmed( + cognito_client, input_, user, cognito_user_response_factory +): + input_.side_effect = ['no'] + cognito_client.return_value.list_users.side_effect = [[ + cognito_user_response_factory('1', '1@example.org') + ], []] + + out = StringIO() + call_command('cognito_sync', clear=True, verbosity=2, stdout=out) + + assert 'This action will delete all managed users from cognito' in out.getvalue() + assert 'operation cancelled' in out.getvalue() + assert call().delete_user('1') not in cognito_client.mock_calls + assert call().create_user('1', '1@example.org') not in cognito_client.mock_calls + + +@patch('cognito.management.commands.cognito_sync.Client') +def test_command_runs_dry(cognito_client, provider, user, cognito_user_response_factory): + User.objects.create( + username='2', first_name='2', last_name='2', email='2@example.org', provider=provider + ) + + cognito_client.return_value.list_users.return_value = [ + cognito_user_response_factory('1', '10@example.org'), + cognito_user_response_factory('3', '3@example.org') + ] + + out = StringIO() + call_command('cognito_sync', dry_run=True, verbosity=2, stdout=out) + + assert 'adding user 2' in out.getvalue() + assert 'deleting user 3' in out.getvalue() + assert 'updating user 1' in out.getvalue() + assert 'dry run' in out.getvalue() + assert cognito_client.return_value.list_users.called + assert not cognito_client.return_value.create_user.called + assert not cognito_client.return_value.delete_user.called + assert not cognito_client.return_value.update_user.called diff --git a/app/tests/cognito/test_cognito_user_utils.py b/app/tests/cognito/test_cognito_user_utils.py new file mode 100644 index 0000000..d0bf294 --- /dev/null +++ b/app/tests/cognito/test_cognito_user_utils.py @@ -0,0 +1,102 @@ +from unittest.mock import call +from unittest.mock import patch + +from cognito.utils.user import create_cognito_user +from cognito.utils.user import delete_cognito_user +from cognito.utils.user import disable_cognito_user +from cognito.utils.user import update_cognito_user + + +class DummyUser: + + def __init__(self, username, email): + self.username = username + self.email = email + + +@patch('cognito.utils.user.Client') +@patch('cognito.utils.user.logger') +def test_create_cognito_user_creates_user(logger, client): + client.return_value.create_user.return_value = True + + created = create_cognito_user(DummyUser('123', 'test@example.org')) + + assert created is True + assert call.info('User %s created', '123') in logger.mock_calls + + +@patch('cognito.utils.user.Client') +@patch('cognito.utils.user.logger') +def test_create_cognito_user_does_not_create_existing_user(logger, client): + client.return_value.create_user.return_value = False + + created = create_cognito_user(DummyUser('123', 'test@example.org')) + + assert created is False + assert call.critical('User %s already exists, not created', '123') in logger.mock_calls + + +@patch('cognito.utils.user.Client') +@patch('cognito.utils.user.logger') +def test_delete_cognito_user_deletes_user(logger, client): + client.return_value.delete_user.return_value = True + + deleted = delete_cognito_user(DummyUser('123', 'test@example.org')) + + assert deleted is True + assert call.info('User %s deleted', '123') in logger.mock_calls + + +@patch('cognito.utils.user.Client') +@patch('cognito.utils.user.logger') +def test_delete_cognito_user_does_not_delete_nonexisting_user(logger, client): + client.return_value.delete_user.return_value = False + + deleted = delete_cognito_user(DummyUser('123', 'test@example.org')) + + assert deleted is False + assert call.critical('User %s does not exist, not deleted', '123') in logger.mock_calls + + +@patch('cognito.utils.user.Client') +@patch('cognito.utils.user.logger') +def test_update_cognito_user_updates_user(logger, client): + client.return_value.update_user.return_value = True + + updated = update_cognito_user(DummyUser('123', 'test@example.org')) + + assert updated is True + assert call.info('User %s updated', '123') in logger.mock_calls + + +@patch('cognito.utils.user.Client') +@patch('cognito.utils.user.logger') +def test_update_cognito_user_does_not_update_nonexisting_user(logger, client): + client.return_value.update_user.return_value = False + + updated = update_cognito_user(DummyUser('123', 'test@example.org')) + + assert updated is False + assert call.critical('User %s does not exist, not updated', '123') in logger.mock_calls + + +@patch('cognito.utils.user.Client') +@patch('cognito.utils.user.logger') +def test_disable_user_disables_user(logger, client): + client.return_value.disable_user.return_value = True + + disabled = disable_cognito_user(DummyUser('123', 'test@example.org')) + + assert disabled is True + assert call.info('User %s disabled', '123') in logger.mock_calls + + +@patch('cognito.utils.user.Client') +@patch('cognito.utils.user.logger') +def test_disable_user_does_not_disable_nonexisting_user(logger, client): + client.return_value.disable_user.return_value = False + + disabled = disable_cognito_user(DummyUser('123', 'test@example.org')) + + assert disabled is False + assert call.critical('User %s does not exist, not disabled', '123') in logger.mock_calls diff --git a/app/tests/config/test_checker.py b/app/tests/config/test_checker.py new file mode 100644 index 0000000..766f0f0 --- /dev/null +++ b/app/tests/config/test_checker.py @@ -0,0 +1,11 @@ +def test_checker_url(client): + # intentionally not using reverse here as we want to + # make sure the URL really is /checker + response = client.get('/checker') + assert response.status_code == 200 + content = response.json() + + assert 'success' in content + assert 'message' in content + assert content['success'] is True + assert content['message'] == "OK" diff --git a/app/tests/config/test_exception_handler.py b/app/tests/config/test_exception_handler.py new file mode 100644 index 0000000..8a71266 --- /dev/null +++ b/app/tests/config/test_exception_handler.py @@ -0,0 +1,90 @@ +from botocore.exceptions import EndpointConnectionError +from config.api import api +from ninja import Router +from ninja.errors import AuthenticationError +from ninja.errors import HttpError +from ninja.errors import ValidationError as NinjaValidationError + +from django.core.exceptions import ValidationError as DjangoValidationError +from django.http import Http404 +from django.http import HttpRequest + +router = Router() +api.add_router("", router) + + +@router.get("trigger-not-found") +def trigger_not_found(request: HttpRequest) -> dict[str, bool | str]: + raise Http404() + + +@router.get("trigger-http-error") +def trigger_http_error(request: HttpRequest) -> dict[str, bool | str]: + raise HttpError(303, "See other") + + +@router.get("/trigger-ninja-validation-error") +def trigger_ninja_validation_error(request: HttpRequest) -> dict[str, bool | str]: + raise NinjaValidationError(errors=[{"email": "Not a valid email."}]) + + +@router.get("/trigger-authentication-error") +def trigger_authentication_error(request: HttpRequest) -> dict[str, bool | str]: + raise AuthenticationError() + + +@router.get("/trigger-internal-server-error") +def trigger_internal_server_error(request: HttpRequest) -> dict[str, bool | str]: + raise RuntimeError() + + +@router.get("/trigger-django-validation-error") +def trigger_django_validation_error(request: HttpRequest) -> dict[str, bool | str]: + raise DjangoValidationError(message=[{"email": "Not a valid email."}]) + + +@router.get("/trigger-cognito-connection-error") +def trigger_cognito_connection_error(request: HttpRequest) -> dict[str, bool | str]: + raise EndpointConnectionError(endpoint_url='localhost') + + +def test_handle_404_not_found(client): + response = client.get('/api/trigger-not-found') + assert response.status_code == 404 + assert response.json() == {'code': 404, 'description': 'Resource not found'} + + +def test_handle_http_error(client): + response = client.get('/api/trigger-http-error') + assert response.status_code == 303 + assert response.json() == {'code': 303, 'description': 'See other'} + + +def test_handle_ninja_validation_error(client): + response = client.get('/api/trigger-ninja-validation-error') + assert response.status_code == 422 + assert response.json() == {'code': 422, 'description': ['Not a valid email.']} + + +def test_handle_unauthorized(client): + response = client.get('/api/trigger-authentication-error') + assert response.status_code == 401 + assert response.json() == {'code': 401, 'description': 'Unauthorized'} + + +def test_handle_exception(client): + response = client.get('/api/trigger-internal-server-error') + assert response.status_code == 500 + assert response.json() == {'code': 500, 'description': 'Internal Server Error'} + + +def test_handle_django_validation_error(client): + response = client.get('/api/trigger-django-validation-error') + assert response.status_code == 422 + assert response.json() == {'code': 422, 'description': ['Not a valid email.']} + + +def test_handle_cognito_connection_error(client): + response = client.get('/api/trigger-cognito-connection-error') + assert response.status_code == 503 + assert response.json() == {'code': 503, 'description': 'Service Unavailable'} diff --git a/app/tests/conftest.py b/app/tests/conftest.py new file mode 100644 index 0000000..9122e27 --- /dev/null +++ b/app/tests/conftest.py @@ -0,0 +1,72 @@ +from typing import Any + +from pytest import fixture + +from django.conf import settings +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType + + +@fixture +def django_user_factory(db): + """A fixture to create Django users, optionally with Django permissions. + + Returns a callable that accepts a username, password, and a list of permissions. Each + permission should be a tuple containing the application name, model name, and permission name. + + Example usage: + + def test_something(django_user_factory): + user = django_user_factory('admin', 'password', [('access', 'user', 'add_user')]) + + """ + + def create_user_with_permissions( + username: str, password: str, permissions: list[tuple[str, str, str]] + ) -> Any: + user = get_user_model().objects.create_user(username=username, password=password) + for app_label, model, codename in permissions: + content_type = ContentType.objects.get(app_label=app_label, model=model) + permission = Permission.objects.get(content_type=content_type, codename=codename) + user.user_permissions.add(permission) + return user + + return create_user_with_permissions + + +@fixture +def cognito_user_response_factory(): + """ A fixture to create Cognito responses containing user data. + + Returns a callable that accepts the following parameters: + - username: The username of the user. + - email: The email address of the user. + - enabled (bool): A flag indicating if the user is enabled. + - managed (bool): A flag indicating if the user is managed. + - attributes_key (str): The name of the attributes field, either 'Attributes' for operations + like list_users and admin_create_user, or 'UserAttributes' for operations like + admin_get_user. + + Example usage: + + def test_something(cognito_user_response_factory): + response = cognito_user_response_factory( + 'user', 'user@example.org', enabled=False, managed=True, attributes_key='Attributes' + ) + """ + + def create_cognito_user_response( + username, + email='test@example.org', + enabled=True, + managed=True, + attributes_key='Attributes' + ): + + attributes = [{'Name': 'email', 'Value': email}] + if managed: + attributes.append({'Name': settings.COGNITO_MANAGED_FLAG_NAME, 'Value': 'true'}) + return {'Username': username, attributes_key: attributes, 'Enabled': enabled} + + return create_cognito_user_response diff --git a/app/tests/distributions/test_attribution_model.py b/app/tests/distributions/test_attribution_model.py new file mode 100644 index 0000000..15a7693 --- /dev/null +++ b/app/tests/distributions/test_attribution_model.py @@ -0,0 +1,131 @@ +from distributions.models import Attribution +from provider.models import Provider +from pytest import fixture +from pytest import raises + +from django.db import IntegrityError +from django.forms import ModelForm + + +@fixture(name='provider') +def fixture_provider(db): + yield Provider.objects.create(acronym_de="ENSI") + + +def test_object_created_in_db_with_all_fields_defined(provider): + attribution = { + "name_de": "BAFU", + "name_fr": "OFEV", + "name_en": "FOEN", + "name_it": "UFAM", + "name_rm": "UFAM", + "description_de": "Bundesamt für Umwelt", + "description_fr": "Office fédéral de l'environnement", + "description_en": "Federal Office for the Environment", + "description_it": "Ufficio federale dell'ambiente", + "description_rm": "Uffizi federal per l'ambient", + "provider": provider + } + Attribution.objects.create(**attribution) + + attributions = Attribution.objects.all() + + assert len(attributions) == 1 + + actual = Attribution.objects.last() + assert actual.name_de == attribution["name_de"] + assert actual.name_fr == attribution["name_fr"] + assert actual.name_en == attribution["name_en"] + assert actual.name_it == attribution["name_it"] + assert actual.name_rm == attribution["name_rm"] + + assert actual.description_de == attribution["description_de"] + assert actual.description_fr == attribution["description_fr"] + assert actual.description_en == attribution["description_en"] + assert actual.description_it == attribution["description_it"] + assert actual.description_rm == attribution["description_rm"] + + assert actual.provider.acronym_de == "ENSI" + + +def test_object_created_in_db_with_optional_fields_null(provider): + attribution = { + "name_de": "BAFU", + "name_fr": "OFEV", + "name_en": "FOEN", + "name_it": None, + "name_rm": None, + "description_de": "Bundesamt für Umwelt", + "description_fr": "Office fédéral de l'environnement", + "description_en": "Federal Office for the Environment", + "description_it": None, + "description_rm": None, + "provider": provider + } + Attribution.objects.create(**attribution) + + attributions = Attribution.objects.all() + + assert len(attributions) == 1 + + actual = Attribution.objects.last() + assert actual.name_de == attribution["name_de"] + assert actual.name_fr == attribution["name_fr"] + assert actual.name_en == attribution["name_en"] + assert actual.name_it == attribution["name_it"] + assert actual.name_rm == attribution["name_rm"] + + assert actual.description_de == attribution["description_de"] + assert actual.description_fr == attribution["description_fr"] + assert actual.description_en == attribution["description_en"] + assert actual.description_it == attribution["description_it"] + assert actual.description_rm == attribution["description_rm"] + + +def test_raises_exception_when_creating_db_object_with_mandatory_field_null(provider): + with raises(IntegrityError): + Attribution.objects.create(name_de=None, provider=provider) + + +def test_form_valid_for_blank_optional_field(provider): + + class AttributionForm(ModelForm): + + class Meta: + model = Attribution + fields = "__all__" + + data = { + "name_de": "BAFU", + "name_fr": "OFEV", + "name_en": "FOEN", + "description_de": "Bundesamt für Umwelt", + "description_fr": "Office fédéral de l'environnement", + "description_en": "Federal Office for the Environment", + "provider": provider.id, + } + form = AttributionForm(data) + + assert form.is_valid() is True + + +def test_form_invalid_for_blank_mandatory_field(provider): + + class AttributionForm(ModelForm): + + class Meta: + model = Attribution + fields = "__all__" + + data = { + "name_de": "BAFU", + "name_fr": "OFEV", + "name_en": "FOEN", + "description_de": "Bundesamt für Umwelt", + "description_fr": "Office fédéral de l'environnement", + "description_en": "", # empty but mandatory field + "provider": provider.id, + } + form = AttributionForm(data) + + assert form.is_valid() is False diff --git a/app/tests/distributions/test_dataset_model.py b/app/tests/distributions/test_dataset_model.py new file mode 100644 index 0000000..dcb32d8 --- /dev/null +++ b/app/tests/distributions/test_dataset_model.py @@ -0,0 +1,60 @@ +import datetime +from unittest import mock + +from distributions.models import Attribution +from distributions.models import Dataset +from provider.models import Provider + + +def test_object_created_in_db_with_all_fields_defined(db): + slug = "ch.bafu.neophyten-haargurke" + provider = Provider.objects.create(acronym_de="BAFU") + attribution = Attribution.objects.create( + name_de="Kantone", + provider=provider, + ) + time_created = datetime.datetime(2024, 9, 12, 15, 28, 0, tzinfo=datetime.UTC) + with mock.patch('django.utils.timezone.now', mock.Mock(return_value=time_created)): + Dataset.objects.create( + slug=slug, + provider=provider, + attribution=attribution, + ) + datasets = Dataset.objects.all() + + assert len(datasets) == 1 + + dataset = Dataset.objects.last() + + assert dataset.slug == slug + assert dataset.provider.acronym_de == "BAFU" + assert dataset.attribution.name_de == "Kantone" + + assert dataset.created == time_created + assert dataset.updated == time_created + + +def test_field_created_matches_creation_time(db): + provider = Provider.objects.create() + attribution = Attribution.objects.create(provider=provider) + + time_created = datetime.datetime(2024, 9, 12, 15, 28, 0, tzinfo=datetime.UTC) + with mock.patch('django.utils.timezone.now', mock.Mock(return_value=time_created)): + dataset = Dataset.objects.create( + provider=provider, + attribution=attribution, + ) + assert dataset.created == time_created + + +def test_field_updated_matches_update_time(db): + provider = Provider.objects.create() + attribution = Attribution.objects.create(provider=provider) + dataset = Dataset.objects.create(provider=provider, attribution=attribution) + + time_updated = datetime.datetime(2024, 9, 12, 15, 42, 0, tzinfo=datetime.UTC) + with mock.patch('django.utils.timezone.now', mock.Mock(return_value=time_updated)): + dataset.slug = "ch.bafu.neophyten-goetterbaum" + dataset.save() + + assert dataset.updated == time_updated diff --git a/app/tests/distributions/test_distributions_api.py b/app/tests/distributions/test_distributions_api.py new file mode 100644 index 0000000..92b36fa --- /dev/null +++ b/app/tests/distributions/test_distributions_api.py @@ -0,0 +1,726 @@ +import datetime +from unittest import mock + +from distributions.api import attribution_to_response +from distributions.models import Attribution +from distributions.models import Dataset +from distributions.schemas import AttributionSchema +from provider.models import Provider +from pytest import fixture +from schemas import TranslationsSchema + + +@fixture(name='time_created') +def fixture_time_created(): + yield datetime.datetime(2024, 9, 12, 15, 28, 0, tzinfo=datetime.UTC) + + +@fixture(name='provider') +def fixture_provider(db): + yield Provider.objects.create() + + +@fixture(name='attribution') +def fixture_attribution(provider): + yield Attribution.objects.create( + name_de="BAFU", + name_fr="OFEV", + name_en="FOEN", + name_it="UFAM", + name_rm="UFAM", + description_de="Bundesamt für Umwelt", + description_fr="Office fédéral de l'environnement", + description_en="Federal Office for the Environment", + description_it="Ufficio federale dell'ambiente", + description_rm="Uffizi federal per l'ambient", + provider=provider + ) + + +@fixture(name='dataset') +def fixture_dataset(provider, attribution, time_created): + with mock.patch('django.utils.timezone.now', mock.Mock(return_value=time_created)): + yield Dataset.objects.create( + slug="ch.bafu.neophyten-haargurke", provider=provider, attribution=attribution + ) + + +def test_attribution_to_response_returns_response_with_language_as_defined(dataset): + + model = Attribution.objects.last() + actual = attribution_to_response(model, lang="de") + + expected = AttributionSchema( + id=model.id, + name="BAFU", + name_translations=TranslationsSchema( + de="BAFU", + fr="OFEV", + en="FOEN", + it="UFAM", + rm="UFAM", + ), + description="Bundesamt für Umwelt", + description_translations=TranslationsSchema( + de="Bundesamt für Umwelt", + fr="Office fédéral de l'environnement", + en="Federal Office for the Environment", + it="Ufficio federale dell'ambiente", + rm="Uffizi federal per l'ambient", + ), + provider_id=Provider.objects.last().id + ) + + assert actual == expected + + +def test_attribution_to_response_returns_response_with_default_language_if_undefined(dataset): + + model = Attribution.objects.last() + model.name_it = None + model.name_rm = None + model.description_it = None + model.description_rm = None + + actual = attribution_to_response(model, lang="it") + + expected = AttributionSchema( + id=model.id, + name="FOEN", + name_translations=TranslationsSchema( + de="BAFU", + fr="OFEV", + en="FOEN", + it=None, + rm=None, + ), + description="Federal Office for the Environment", + description_translations=TranslationsSchema( + de="Bundesamt für Umwelt", + fr="Office fédéral de l'environnement", + en="Federal Office for the Environment", + it=None, + rm=None, + ), + provider_id=Provider.objects.last().id, + ) + + assert actual == expected + + +def test_get_attribution_returns_existing_attribution_with_default_language( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + attribution_id = Attribution.objects.last().id + + response = client.get(f"/api/attributions/{attribution_id}") + + assert response.status_code == 200 + assert response.json() == { + "id": attribution_id, + "name": "FOEN", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + }, + "description": "Federal Office for the Environment", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "provider_id": Provider.objects.last().id, + } + + +def test_get_attribution_returns_attribution_with_language_from_query( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + attribution_id = Attribution.objects.last().id + + response = client.get(f"/api/attributions/{attribution_id}?lang=de") + + assert response.status_code == 200 + assert response.json() == { + "id": attribution_id, + "name": "BAFU", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + }, + "description": "Bundesamt für Umwelt", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "provider_id": Provider.objects.last().id, + } + + +def test_get_attribution_returns_404_for_nonexisting_attribution( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + response = client.get("/api/attributions/9999") + + assert response.status_code == 404 + assert response.json() == {"code": 404, "description": "Resource not found"} + + +def test_get_attribution_skips_translations_that_are_not_available( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + model = Attribution.objects.last() + model.name_it = None + model.name_rm = None + model.description_it = None + model.description_rm = None + model.save() + + response = client.get(f"/api/attributions/{model.id}") + + assert response.status_code == 200 + assert response.json() == { + "id": model.id, + "name": "FOEN", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + }, + "description": "Federal Office for the Environment", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + }, + "provider_id": Provider.objects.last().id, + } + + +def test_get_attribution_returns_attribution_with_language_from_header( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + attribution_id = Attribution.objects.last().id + + response = client.get(f"/api/attributions/{attribution_id}", headers={"Accept-Language": "de"}) + + assert response.status_code == 200 + assert response.json() == { + "id": attribution_id, + "name": "BAFU", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + }, + "description": "Bundesamt für Umwelt", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "provider_id": Provider.objects.last().id, + } + + +def test_get_attribution_returns_attribution_with_language_from_query_param_even_if_header_set( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + attribution_id = Attribution.objects.last().id + + response = client.get( + f"/api/attributions/{attribution_id}?lang=fr", headers={"Accept-Language": "de"} + ) + + assert response.status_code == 200 + assert response.json() == { + "id": attribution_id, + "name": "OFEV", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + }, + "description": "Office fédéral de l'environnement", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "provider_id": Provider.objects.last().id, + } + + +def test_get_attribution_returns_attribution_with_default_language_if_header_empty( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + attribution_id = Attribution.objects.last().id + + response = client.get(f"/api/attributions/{attribution_id}", headers={"Accept-Language": ""}) + + assert response.status_code == 200 + assert response.json() == { + "id": attribution_id, + "name": "FOEN", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + }, + "description": "Federal Office for the Environment", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "provider_id": Provider.objects.last().id, + } + + +def test_get_attribution_returns_attribution_with_first_known_language_from_header( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + attribution_id = Attribution.objects.last().id + + response = client.get( + f"/api/attributions/{attribution_id}", headers={"Accept-Language": "cn, *, de-DE, en"} + ) + + assert response.status_code == 200 + assert response.json() == { + "id": attribution_id, + "name": "BAFU", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + }, + "description": "Bundesamt für Umwelt", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "provider_id": Provider.objects.last().id, + } + + +def test_get_attribution_returns_attribution_with_first_language_from_header_ignoring_qfactor( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + attribution_id = Attribution.objects.last().id + + response = client.get( + f"/api/attributions/{attribution_id}", headers={"Accept-Language": "fr;q=0.9, de;q=0.8"} + ) + + assert response.status_code == 200 + assert response.json() == { + "id": attribution_id, + "name": "OFEV", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + }, + "description": "Office fédéral de l'environnement", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "provider_id": Provider.objects.last().id, + } + + +def test_get_attribution_returns_401_if_not_logged_in(dataset, client): + attribution_id = Attribution.objects.last().id + response = client.get(f"/api/attributions/{attribution_id}") + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + + +def test_get_attribution_returns_403_if_no_permission(dataset, client, django_user_factory): + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + attribution_id = Attribution.objects.last().id + response = client.get(f"/api/attributions/{attribution_id}") + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} + + +def test_get_attributions_returns_single_attribution_with_given_language( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + response = client.get("/api/attributions?lang=fr") + + assert response.status_code == 200 + assert response.json() == { + "items": [{ + "id": Attribution.objects.last().id, + "name": "OFEV", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + }, + "description": "Office fédéral de l'environnement", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "provider_id": Provider.objects.last().id, + }] + } + + +def test_get_attributions_skips_translations_that_are_not_available( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + model = Attribution.objects.last() + model.name_it = None + model.name_rm = None + model.description_it = None + model.description_rm = None + model.save() + + response = client.get("/api/attributions") + + assert response.status_code == 200 + assert response.json() == { + "items": [{ + "id": Attribution.objects.last().id, + "name": "FOEN", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + }, + "description": "Federal Office for the Environment", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + }, + "provider_id": Provider.objects.last().id, + }] + } + + +def test_get_attributions_returns_attribution_with_language_from_header( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + response = client.get("/api/attributions", headers={"Accept-Language": "de"}) + + assert response.status_code == 200 + assert response.json() == { + "items": [{ + "id": Attribution.objects.last().id, + "name": "BAFU", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + }, + "description": "Bundesamt für Umwelt", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "provider_id": Provider.objects.last().id, + }] + } + + +def test_get_attributions_returns_all_attributions_ordered_by_id_with_given_language( + dataset, client, django_user_factory +): + django_user_factory('test', 'test', [('distributions', 'attribution', 'view_attribution')]) + client.login(username='test', password='test') + + provider1 = Provider.objects.last() + attribution_id_1 = Attribution.objects.last().id + + provider2 = Provider.objects.create() + model_fields = { + "name_de": "BAV", + "name_fr": "OFT", + "name_en": "FOT", + "name_it": "UFT", + "name_rm": "UFT", + "description_de": "Bundesamt für Verkehr", + "description_fr": "Office fédéral des transports", + "description_en": "Federal Office of Transport", + "description_it": "Ufficio federale dei trasporti", + "description_rm": "Uffizi federal da traffic", + "provider": provider2, + } + attribution_id_2 = Attribution.objects.create(**model_fields).id + + response = client.get("/api/attributions?lang=fr") + + assert response.status_code == 200 + assert response.json() == { + "items": [ + { + "id": attribution_id_1, + "name": "OFEV", + "name_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + }, + "description": "Office fédéral de l'environnement", + "description_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "provider_id": provider1.id, + }, + { + "id": attribution_id_2, + "name": "OFT", + "name_translations": { + "de": "BAV", + "fr": "OFT", + "en": "FOT", + "it": "UFT", + "rm": "UFT", + }, + "description": "Office fédéral des transports", + "description_translations": { + "de": "Bundesamt für Verkehr", + "fr": "Office fédéral des transports", + "en": "Federal Office of Transport", + "it": "Ufficio federale dei trasporti", + "rm": "Uffizi federal da traffic", + }, + "provider_id": provider2.id, + }, + ] + } + + +def test_get_attributions_returns_401_if_not_logged_in(dataset, client, django_user_factory): + response = client.get("/api/providers") + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + + +def test_get_attributions_returns_403_if_no_permission(dataset, client, django_user_factory): + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + response = client.get("/api/providers") + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} + + +def test_get_dataset_returns_specified_dataset(dataset, client, django_user_factory, time_created): + django_user_factory('test', 'test', [('distributions', 'dataset', 'view_dataset')]) + client.login(username='test', password='test') + + dataset_id = Dataset.objects.last().id + + response = client.get(f"/api/datasets/{dataset_id}") + + assert response.status_code == 200 + assert response.json() == { + "id": dataset_id, + "slug": "ch.bafu.neophyten-haargurke", + "created": time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), + "updated": time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), + "provider_id": Provider.objects.last().id, + "attribution_id": Attribution.objects.last().id, + } + + +def test_get_dataset_returns_401_if_not_logged_in(dataset, client): + dataset_id = Dataset.objects.last().id + response = client.get(f"/api/datasets/{dataset_id}") + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + + +def test_get_dataset_returns_403_if_no_permission(dataset, client, django_user_factory): + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + dataset_id = Dataset.objects.last().id + response = client.get(f"/api/datasets/{dataset_id}") + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} + + +def test_get_datasets_returns_single_dataset_as_expected( + dataset, client, django_user_factory, time_created +): + django_user_factory('test', 'test', [('distributions', 'dataset', 'view_dataset')]) + client.login(username='test', password='test') + + response = client.get("/api/datasets") + + dataset = Dataset.objects.last() + assert response.status_code == 200 + assert response.json() == { + "items": [{ + "id": dataset.id, + "slug": "ch.bafu.neophyten-haargurke", + "created": time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), + "updated": time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), + "provider_id": Provider.objects.last().id, + "attribution_id": Attribution.objects.last().id, + }] + } + + +def test_get_datasets_returns_all_datasets_ordered_by_id( + dataset, client, django_user_factory, time_created +): + django_user_factory('test', 'test', [('distributions', 'dataset', 'view_dataset')]) + client.login(username='test', password='test') + + provider2 = Provider.objects.create(acronym_de="Provider2") + attribution2 = Attribution.objects.create( + name_de="Attribution2", + provider=provider2, + ) + model_fields2 = { + "slug": "slug2", + "provider": provider2, + "attribution": attribution2, + } + time_created2 = datetime.datetime(2024, 9, 12, 16, 28, 0, tzinfo=datetime.UTC) + with mock.patch('django.utils.timezone.now', mock.Mock(return_value=time_created2)): + dataset2 = Dataset.objects.create(**model_fields2) + + response = client.get("/api/datasets") + + dataset1 = Dataset.objects.first() + assert response.status_code == 200 + assert response.json() == { + "items": [ + { + "id": dataset1.id, + "slug": dataset1.slug, + "created": time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), + "updated": time_created.strftime("%Y-%m-%dT%H:%M:%SZ"), + "provider_id": Provider.objects.first().id, + "attribution_id": Attribution.objects.first().id, + }, + { + "id": dataset2.id, + "slug": "slug2", + "created": dataset2.created.strftime("%Y-%m-%dT%H:%M:%SZ"), + "updated": dataset2.updated.strftime("%Y-%m-%dT%H:%M:%SZ"), + "provider_id": provider2.id, + "attribution_id": attribution2.id, + }, + ] + } + + +def test_get_datasets_returns_401_if_not_logged_in(dataset, client): + response = client.get("/api/datasets") + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + + +def test_get_datasets_returns_403_if_no_permission(dataset, client, django_user_factory): + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + response = client.get("/api/datasets") + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} diff --git a/app/tests/provider/test_provider_api.py b/app/tests/provider/test_provider_api.py new file mode 100644 index 0000000..2aadaf3 --- /dev/null +++ b/app/tests/provider/test_provider_api.py @@ -0,0 +1,537 @@ +from provider.api import provider_to_response +from provider.models import Provider +from provider.schemas import ProviderSchema +from pytest import fixture +from schemas import TranslationsSchema + + +@fixture(name='provider') +def fixture_provider(db): + yield Provider.objects.create( + name_de="Bundesamt für Umwelt", + name_fr="Office fédéral de l'environnement", + name_en="Federal Office for the Environment", + name_it="Ufficio federale dell'ambiente", + name_rm="Uffizi federal per l'ambient", + acronym_de="BAFU", + acronym_fr="OFEV", + acronym_en="FOEN", + acronym_it="UFAM", + acronym_rm="UFAM" + ) + + +def test_provider_to_response_returns_response_with_language_as_defined(provider): + actual = provider_to_response(provider, lang="de") + + expected = ProviderSchema( + id=provider.id, + name="Bundesamt für Umwelt", + name_translations=TranslationsSchema( + de="Bundesamt für Umwelt", + fr="Office fédéral de l'environnement", + en="Federal Office for the Environment", + it="Ufficio federale dell'ambiente", + rm="Uffizi federal per l'ambient", + ), + acronym="BAFU", + acronym_translations=TranslationsSchema( + de="BAFU", + fr="OFEV", + en="FOEN", + it="UFAM", + rm="UFAM", + ) + ) + + assert actual == expected + + +def test_provider_to_response_returns_response_with_default_language_if_undefined(provider): + provider.name_it = None + provider.name_rm = None + provider.acronym_it = None + provider.acronym_rm = None + + actual = provider_to_response(provider, lang="it") + + expected = ProviderSchema( + id=str(provider.id), + name="Federal Office for the Environment", + name_translations=TranslationsSchema( + de="Bundesamt für Umwelt", + fr="Office fédéral de l'environnement", + en="Federal Office for the Environment", + it=None, + rm=None, + ), + acronym="FOEN", + acronym_translations=TranslationsSchema( + de="BAFU", + fr="OFEV", + en="FOEN", + it=None, + rm=None, + ) + ) + + assert actual == expected + + +def test_get_provider_returns_existing_provider_with_default_language( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + response = client.get(f"/api/providers/{provider.id}") + + assert response.status_code == 200 + assert response.json() == { + "id": provider.id, + "name": "Federal Office for the Environment", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "acronym": "FOEN", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + } + } + + +def test_get_provider_returns_provider_with_language_from_query( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + response = client.get(f"/api/providers/{provider.id}?lang=de") + + assert response.status_code == 200 + assert response.json() == { + "id": provider.id, + "name": "Bundesamt für Umwelt", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "acronym": "BAFU", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + } + } + + +def test_get_provider_returns_404_for_nonexisting_provider(client, django_user_factory): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + response = client.get("/api/providers/2") + + assert response.status_code == 404 + assert response.json() == {"code": 404, "description": "Resource not found"} + + +def test_get_provider_skips_translations_that_are_not_available( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + provider = Provider.objects.last() + provider.name_it = None + provider.name_rm = None + provider.acronym_it = None + provider.acronym_rm = None + provider.save() + + response = client.get(f"/api/providers/{provider.id}") + + assert response.status_code == 200 + assert response.json() == { + "id": provider.id, + "name": "Federal Office for the Environment", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + }, + "acronym": "FOEN", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + } + } + + +def test_get_provider_returns_provider_with_language_from_header( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + response = client.get(f"/api/providers/{provider.id}", headers={"Accept-Language": "de"}) + + assert response.status_code == 200 + assert response.json() == { + "id": provider.id, + "name": "Bundesamt für Umwelt", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "acronym": "BAFU", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + } + } + + +def test_get_provider_returns_provider_with_language_from_query_param_even_if_header_set( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + response = client.get( + f"/api/providers/{provider.id}?lang=fr", headers={"Accept-Language": "de"} + ) + + assert response.status_code == 200 + assert response.json() == { + "id": provider.id, + "name": "Office fédéral de l'environnement", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "acronym": "OFEV", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + } + } + + +def test_get_provider_returns_provider_with_default_language_if_header_empty( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + response = client.get(f"/api/providers/{provider.id}", headers={"Accept-Language": ""}) + + assert response.status_code == 200 + assert response.json() == { + "id": provider.id, + "name": "Federal Office for the Environment", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "acronym": "FOEN", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + } + } + + +def test_get_provider_returns_provider_with_first_known_language_from_header( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + response = client.get( + f"/api/providers/{provider.id}", headers={"Accept-Language": "cn, *, de-DE, en"} + ) + + assert response.status_code == 200 + assert response.json() == { + "id": provider.id, + "name": "Bundesamt für Umwelt", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "acronym": "BAFU", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + } + } + + +def test_get_provider_returns_provider_with_first_known_language_from_header_ignoring_qfactor( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + response = client.get( + f"/api/providers/{provider.id}", headers={"Accept-Language": "fr;q=0.9, de;q=0.8"} + ) + + assert response.status_code == 200 + assert response.json() == { + "id": provider.id, + "name": "Office fédéral de l'environnement", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "acronym": "OFEV", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + } + } + + +def test_get_provider_returns_401_if_not_logged_in(provider, client): + response = client.get(f"/api/providers/{provider.id}") + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + + +def test_get_provider_returns_403_if_no_permission(provider, client, django_user_factory): + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + response = client.get(f"/api/providers/{provider.id}") + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} + + +def test_get_providers_returns_single_provider_with_given_language( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + response = client.get("/api/providers?lang=fr") + + assert response.status_code == 200 + assert response.json() == { + "items": [{ + "id": Provider.objects.last().id, + "name": "Office fédéral de l'environnement", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "acronym": "OFEV", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + } + }] + } + + +def test_get_providers_skips_translations_that_are_not_available( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + provider = Provider.objects.last() + provider.name_it = None + provider.name_rm = None + provider.acronym_it = None + provider.acronym_rm = None + provider.save() + + response = client.get("/api/providers") + + assert response.status_code == 200 + assert response.json() == { + "items": [{ + "id": provider.id, + "name": "Federal Office for the Environment", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + }, + "acronym": "FOEN", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + } + }] + } + + +def test_get_providers_returns_provider_with_language_from_header( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + response = client.get("/api/providers", headers={"Accept-Language": "de"}) + + assert response.status_code == 200 + assert response.json() == { + "items": [{ + "id": Provider.objects.last().id, + "name": "Bundesamt für Umwelt", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "acronym": "BAFU", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + } + }] + } + + +def test_get_providers_returns_all_providers_ordered_by_id_with_given_language( + provider, client, django_user_factory +): + django_user_factory('test', 'test', [('provider', 'provider', 'view_provider')]) + client.login(username='test', password='test') + + provider = { + "name_de": "Bundesamt für Verkehr", + "name_fr": "Office fédéral des transports", + "name_en": "Federal Office of Transport", + "name_it": "Ufficio federale dei trasporti", + "name_rm": "Uffizi federal da traffic", + "acronym_de": "BAV", + "acronym_fr": "OFT", + "acronym_en": "FOT", + "acronym_it": "UFT", + "acronym_rm": "UFT", + } + Provider.objects.create(**provider) + + response = client.get("/api/providers?lang=fr") + + assert response.status_code == 200 + assert response.json() == { + "items": [ + { + "id": Provider.objects.first().id, + "name": "Office fédéral de l'environnement", + "name_translations": { + "de": "Bundesamt für Umwelt", + "fr": "Office fédéral de l'environnement", + "en": "Federal Office for the Environment", + "it": "Ufficio federale dell'ambiente", + "rm": "Uffizi federal per l'ambient", + }, + "acronym": "OFEV", + "acronym_translations": { + "de": "BAFU", + "fr": "OFEV", + "en": "FOEN", + "it": "UFAM", + "rm": "UFAM", + } + }, + { + "id": Provider.objects.last().id, + "name": "Office fédéral des transports", + "name_translations": { + "de": "Bundesamt für Verkehr", + "fr": "Office fédéral des transports", + "en": "Federal Office of Transport", + "it": "Ufficio federale dei trasporti", + "rm": "Uffizi federal da traffic", + }, + "acronym": "OFT", + "acronym_translations": { + "de": "BAV", + "fr": "OFT", + "en": "FOT", + "it": "UFT", + "rm": "UFT", + } + }, + ] + } + + +def test_get_providers_returns_401_if_not_logged_in(client): + response = client.get("/api/providers") + + assert response.status_code == 401 + assert response.json() == {"code": 401, "description": "Unauthorized"} + + +def test_get_providers_returns_403_if_no_permission(client, django_user_factory): + django_user_factory('test', 'test', []) + client.login(username='test', password='test') + + response = client.get("/api/providers") + + assert response.status_code == 403 + assert response.json() == {"code": 403, "description": "Forbidden"} diff --git a/app/tests/provider/test_provider_model.py b/app/tests/provider/test_provider_model.py new file mode 100644 index 0000000..ff08770 --- /dev/null +++ b/app/tests/provider/test_provider_model.py @@ -0,0 +1,118 @@ +from provider.models import Provider +from pytest import raises + +from django.db import IntegrityError +from django.forms import ModelForm + + +def test_object_created_in_db_with_all_fields_defined(db): + provider_in = { + "name_de": "Bundesamt für Umwelt", + "name_fr": "Office fédéral de l'environnement", + "name_en": "Federal Office for the Environment", + "name_it": "Ufficio federale dell'ambiente", + "name_rm": "Uffizi federal per l'ambient", + "acronym_de": "BAFU", + "acronym_fr": "OFEV", + "acronym_en": "FOEN", + "acronym_it": "UFAM", + "acronym_rm": "UFAM", + } + Provider.objects.create(**provider_in) + + providers = Provider.objects.all() + + assert len(providers) == 1 + + actual = Provider.objects.last() + assert provider_in["name_de"] == actual.name_de + assert provider_in["name_fr"] == actual.name_fr + assert provider_in["name_en"] == actual.name_en + assert provider_in["name_it"] == actual.name_it + assert provider_in["name_rm"] == actual.name_rm + + assert provider_in["acronym_de"] == actual.acronym_de + assert provider_in["acronym_fr"] == actual.acronym_fr + assert provider_in["acronym_en"] == actual.acronym_en + assert provider_in["acronym_it"] == actual.acronym_it + assert provider_in["acronym_rm"] == actual.acronym_rm + + +def test_object_created_in_db_with_optional_fields_null(db): + provider_in = { + "name_de": "Bundesamt für Umwelt", + "name_fr": "Office fédéral de l'environnement", + "name_en": "Federal Office for the Environment", + "name_it": None, + "name_rm": None, + "acronym_de": "BAFU", + "acronym_fr": "OFEV", + "acronym_en": "FOEN", + "acronym_it": None, + "acronym_rm": None, + } + Provider.objects.create(**provider_in) + + providers = Provider.objects.all() + + assert len(providers) == 1 + + actual = Provider.objects.last() + assert actual.name_de == provider_in["name_de"] + assert actual.name_fr == provider_in["name_fr"] + assert actual.name_en == provider_in["name_en"] + assert actual.name_it == provider_in["name_it"] + assert actual.name_rm == provider_in["name_rm"] + + assert actual.acronym_de == provider_in["acronym_de"] + assert actual.acronym_fr == provider_in["acronym_fr"] + assert actual.acronym_en == provider_in["acronym_en"] + assert actual.acronym_it == provider_in["acronym_it"] + assert actual.acronym_rm == provider_in["acronym_rm"] + + +def test_raises_exception_when_creating_db_object_with_mandatory_field_null(db): + with raises(IntegrityError): + Provider.objects.create(name_de=None) + + +def test_form_valid_for_blank_optional_field(db): + + class ProviderForm(ModelForm): + + class Meta: + model = Provider + fields = "__all__" + + data = { + "name_de": "Bundesamt für Umwelt", + "name_fr": "Office fédéral de l'environnement", + "name_en": "Federal Office for the Environment", + "acronym_de": "BAFU", + "acronym_fr": "OFEV", + "acronym_en": "FOEN", + } + form = ProviderForm(data) + + assert form.is_valid() is True + + +def test_form_invalid_for_blank_mandatory_field(db): + + class ProviderForm(ModelForm): + + class Meta: + model = Provider + fields = "__all__" + + data = { + "name_de": "Bundesamt für Umwelt", + "name_fr": "Office fédéral de l'environnement", + "name_en": "Federal Office for the Environment", + "acronym_de": "BAFU", + "acronym_fr": "OFEV", + "acronym_en": "", # empty but mandatory field + } + form = ProviderForm(data) + + assert form.is_valid() is False diff --git a/app/tests/utils/test_command.py b/app/tests/utils/test_command.py new file mode 100644 index 0000000..9088860 --- /dev/null +++ b/app/tests/utils/test_command.py @@ -0,0 +1,196 @@ +from io import StringIO +from unittest.mock import MagicMock +from unittest.mock import call + +from utils.command import CommandHandler +from utils.command import CustomBaseCommand + +from django.core.management import call_command + + +class Handler(CommandHandler): + + def __init__(self, command, options): + super().__init__(command, options) + self.logger = MagicMock() + + def run(self): + # level + self.print("Print default") + self.print("Print 0", level=0) + self.print("Print 1", level=1) + self.print("Print 2", level=2) + self.print_success("Success default") + self.print_success("Success 0", level=0) + self.print_success("Success 1", level=1) + self.print_success("Success 2", level=2) + self.print_warning("Warning default") + self.print_warning("Warning 0", level=0) + self.print_warning("Warning 1", level=1) + self.print_warning("Warning 2", level=2) + self.print_error("Error") + + # args and kwargs + self.print("Print %s", "JohnDoe") + self.print("Print", extra={"n": "JohnDoe"}) + self.print("Print %s", "John", extra={"n": "Doe"}) + self.print_success("Success %s", "JohnDoe") + self.print_success("Success", extra={"n": "JohnDoe"}) + self.print_success("Success %s", "John", extra={"n": "Doe"}) + self.print_warning("Warning %s", "JohnDoe") + self.print_warning("Warning", extra={"n": "JohnDoe"}) + self.print_warning("Warning %s", "John", extra={"n": "Doe"}) + self.print_error("Error %s", "JohnDoe") + self.print_error("Error", extra={"n": "JohnDoe"}) + self.print_error("Error %s", "John", extra={"n": "Doe"}) + + +class Command(CustomBaseCommand): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.handler = None + + def handle(self, *args, **options): + self.handler = Handler(self, options) + self.handler.run() + + +def test_print_to_stdout_default_verbosity(): + # default verbosity == 1 + out = StringIO() + err = StringIO() + call_command(Command(), stdout=out, stderr=err) + assert "Print default" not in out.getvalue() + assert "Print 0" in out.getvalue() + assert "Print 1" in out.getvalue() + assert "Print 2" not in out.getvalue() + assert "Success default" in out.getvalue() + assert "Success 0" in out.getvalue() + assert "Success 1" in out.getvalue() + assert "Success 2" not in out.getvalue() + assert "Warning default" in out.getvalue() + assert "Warning 0" in out.getvalue() + assert "Warning 1" in out.getvalue() + assert "Warning 2" not in out.getvalue() + assert "Error" in err.getvalue() + + +def test_print_to_stdout_verbosity_0(): + out = StringIO() + err = StringIO() + call_command(Command(), verbosity=0, stdout=out, stderr=err) + assert "Print 0" in out.getvalue() + assert "Print 1" not in out.getvalue() + assert "Print 2" not in out.getvalue() + assert "Success 0" in out.getvalue() + assert "Success 1" not in out.getvalue() + assert "Success 2" not in out.getvalue() + assert "Warning 0" in out.getvalue() + assert "Warning 1" not in out.getvalue() + assert "Warning 2" not in out.getvalue() + assert "Error" in err.getvalue() + + +def test_print_to_stdout_verbosity_3(): + out = StringIO() + err = StringIO() + call_command(Command(), verbosity=3, stdout=out, stderr=err) + assert "Print 0" in out.getvalue() + assert "Print 1" in out.getvalue() + assert "Print 2" in out.getvalue() + assert "Success 0" in out.getvalue() + assert "Success 1" in out.getvalue() + assert "Success 2" in out.getvalue() + assert "Warning 0" in out.getvalue() + assert "Warning 1" in out.getvalue() + assert "Warning 2" in out.getvalue() + assert "Error" in err.getvalue() + + +def test_print_to_stdout_args_kwargs(): + out = StringIO() + err = StringIO() + call_command(Command(), verbosity=3, stdout=out, stderr=err) + assert "Print JohnDoe" in out.getvalue() + assert "Print extra={'n': 'JohnDoe'}" in out.getvalue() + assert "Print John extra={'n': 'Doe'}" in out.getvalue() + assert "Success JohnDoe" in out.getvalue() + assert "Success extra={'n': 'JohnDoe'}" in out.getvalue() + assert "Success John extra={'n': 'Doe'}" in out.getvalue() + assert "Warning JohnDoe" in out.getvalue() + assert "Warning extra={'n': 'JohnDoe'}" in out.getvalue() + assert "Warning John extra={'n': 'Doe'}" in out.getvalue() + assert "Error JohnDoe" in err.getvalue() + assert "Error\nextra={'n': 'JohnDoe'}" in err.getvalue() + assert "Error John\nextra={'n': 'Doe'}" in err.getvalue() + + +def test_print_to_log_default_verbosity(): + # default verbosity == 1 + command = Command() + call_command(command, logger=True) + calls = command.handler.logger.mock_calls + assert call.info("Print default") not in calls + assert call.info("Print 0") in calls + assert call.info("Print 1") in calls + assert call.info("Print 2") not in calls + assert call.info("Success default") in calls + assert call.info("Success 0") in calls + assert call.info("Success 1") in calls + assert call.info("Success 2") not in calls + assert call.warning("Warning default") in calls + assert call.warning("Warning 0") in calls + assert call.warning("Warning 1") in calls + assert call.warning("Warning 2") not in calls + assert call.error("Error") in calls + + +def test_print_to_log_verbosity_0(): + command = Command() + call_command(command, verbosity=0, logger=True) + calls = command.handler.logger.mock_calls + assert call.info("Print 0") in calls + assert call.info("Print 1") not in calls + assert call.info("Print 2") not in calls + assert call.info("Success 0") in calls + assert call.info("Success 1") not in calls + assert call.info("Success 2") not in calls + assert call.warning("Warning 0") in calls + assert call.warning("Warning 1") not in calls + assert call.warning("Warning 2") not in calls + assert call.error("Error") in calls + + +def test_print_to_log_verbosity_3(): + command = Command() + call_command(command, verbosity=3, logger=True) + calls = command.handler.logger.mock_calls + assert call.info("Print 0") in calls + assert call.info("Print 1") in calls + assert call.info("Print 2") in calls + assert call.info("Success 0") in calls + assert call.info("Success 1") in calls + assert call.info("Success 2") in calls + assert call.warning("Warning 0") in calls + assert call.warning("Warning 1") in calls + assert call.warning("Warning 2") in calls + assert call.error("Error") in calls + + +def test_print_to_log_args_kwargs(): + command = Command() + call_command(command, verbosity=3, logger=True) + calls = command.handler.logger.mock_calls + assert call.info('Print %s', 'JohnDoe') in calls + assert call.info('Print', extra={'n': 'JohnDoe'}) in calls + assert call.info('Print %s', 'John', extra={'n': 'Doe'}) in calls + assert call.info('Success JohnDoe') in calls + assert call.info('Success', extra={'n': 'JohnDoe'}) in calls + assert call.info('Success John', extra={'n': 'Doe'}) in calls + assert call.warning('Warning JohnDoe') in calls + assert call.warning('Warning', extra={'n': 'JohnDoe'}) in calls + assert call.warning('Warning John', extra={'n': 'Doe'}) in calls + assert call.error('Error JohnDoe') in calls + assert call.error('Error', extra={'n': 'JohnDoe'}) in calls + assert call.error('Error John', extra={'n': 'Doe'}) in calls diff --git a/app/tests/utils/test_database_router.py b/app/tests/utils/test_database_router.py new file mode 100644 index 0000000..ce65f3d --- /dev/null +++ b/app/tests/utils/test_database_router.py @@ -0,0 +1,25 @@ +from bod.models import BodContactOrganisation +from provider.models import Provider +from pytest import raises + + +def test_database_routing_inside_tests(): + assert BodContactOrganisation.objects.db == 'default' + assert Provider.objects.db == 'default' + + +def test_database_routing_outside_tests(settings): + settings.TESTING = False + assert BodContactOrganisation.objects.db == 'bod' + assert Provider.objects.db == 'default' + + +def test_writing_to_bod_supported_inside_tests(db): + BodContactOrganisation.objects.create() + assert BodContactOrganisation.objects.count() == 1 + + +def test_writing_to_bod_not_supported_outside_tests(settings, db): + settings.TESTING = False + with raises(RuntimeError): + BodContactOrganisation.objects.create() diff --git a/app/utils/tests/test_exceptions.py b/app/tests/utils/test_exceptions.py similarity index 100% rename from app/utils/tests/test_exceptions.py rename to app/tests/utils/test_exceptions.py diff --git a/app/utils/tests/test_header.py b/app/tests/utils/test_header.py similarity index 100% rename from app/utils/tests/test_header.py rename to app/tests/utils/test_header.py diff --git a/app/utils/tests/test_language.py b/app/tests/utils/test_language.py similarity index 100% rename from app/utils/tests/test_language.py rename to app/tests/utils/test_language.py diff --git a/app/utils/tests/test_command.py b/app/utils/tests/test_command.py deleted file mode 100644 index d033212..0000000 --- a/app/utils/tests/test_command.py +++ /dev/null @@ -1,192 +0,0 @@ -from io import StringIO -from unittest.mock import MagicMock -from unittest.mock import call - -from utils.command import CommandHandler -from utils.command import CustomBaseCommand - -from django.core.management import call_command -from django.test import TestCase - - -class Handler(CommandHandler): - - def __init__(self, command, options): - super().__init__(command, options) - self.logger = MagicMock() - - def run(self): - # level - self.print("Print default") - self.print("Print 0", level=0) - self.print("Print 1", level=1) - self.print("Print 2", level=2) - self.print_success("Success default") - self.print_success("Success 0", level=0) - self.print_success("Success 1", level=1) - self.print_success("Success 2", level=2) - self.print_warning("Warning default") - self.print_warning("Warning 0", level=0) - self.print_warning("Warning 1", level=1) - self.print_warning("Warning 2", level=2) - self.print_error("Error") - - # args and kwargs - self.print("Print %s", "JohnDoe") - self.print("Print", extra={"n": "JohnDoe"}) - self.print("Print %s", "John", extra={"n": "Doe"}) - self.print_success("Success %s", "JohnDoe") - self.print_success("Success", extra={"n": "JohnDoe"}) - self.print_success("Success %s", "John", extra={"n": "Doe"}) - self.print_warning("Warning %s", "JohnDoe") - self.print_warning("Warning", extra={"n": "JohnDoe"}) - self.print_warning("Warning %s", "John", extra={"n": "Doe"}) - self.print_error("Error %s", "JohnDoe") - self.print_error("Error", extra={"n": "JohnDoe"}) - self.print_error("Error %s", "John", extra={"n": "Doe"}) - - -class Command(CustomBaseCommand): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.handler = None - - def handle(self, *args, **options): - self.handler = Handler(self, options) - self.handler.run() - - -class CustomCommandTest(TestCase): - - def test_print_to_stdout_default_verbosity(self): - # default verbosity == 1 - out = StringIO() - err = StringIO() - call_command(Command(), stdout=out, stderr=err) - self.assertNotIn("Print default", out.getvalue()) - self.assertIn("Print 0", out.getvalue()) - self.assertIn("Print 1", out.getvalue()) - self.assertNotIn("Print 2", out.getvalue()) - self.assertIn("Success default", out.getvalue()) - self.assertIn("Success 0", out.getvalue()) - self.assertIn("Success 1", out.getvalue()) - self.assertNotIn("Success 2", out.getvalue()) - self.assertIn("Warning default", out.getvalue()) - self.assertIn("Warning 0", out.getvalue()) - self.assertIn("Warning 1", out.getvalue()) - self.assertNotIn("Warning 2", out.getvalue()) - self.assertIn("Error", err.getvalue()) - - def test_print_to_stdout_verbosity_0(self): - out = StringIO() - err = StringIO() - call_command(Command(), verbosity=0, stdout=out, stderr=err) - self.assertIn("Print 0", out.getvalue()) - self.assertNotIn("Print 1", out.getvalue()) - self.assertNotIn("Print 2", out.getvalue()) - self.assertIn("Success 0", out.getvalue()) - self.assertNotIn("Success 1", out.getvalue()) - self.assertNotIn("Success 2", out.getvalue()) - self.assertIn("Warning 0", out.getvalue()) - self.assertNotIn("Warning 1", out.getvalue()) - self.assertNotIn("Warning 2", out.getvalue()) - self.assertIn("Error", err.getvalue()) - - def test_print_to_stdout_verbosity_3(self): - out = StringIO() - err = StringIO() - call_command(Command(), verbosity=3, stdout=out, stderr=err) - self.assertIn("Print 0", out.getvalue()) - self.assertIn("Print 1", out.getvalue()) - self.assertIn("Print 2", out.getvalue()) - self.assertIn("Success 0", out.getvalue()) - self.assertIn("Success 1", out.getvalue()) - self.assertIn("Success 2", out.getvalue()) - self.assertIn("Warning 0", out.getvalue()) - self.assertIn("Warning 1", out.getvalue()) - self.assertIn("Warning 2", out.getvalue()) - self.assertIn("Error", err.getvalue()) - - def test_print_to_stdout_args_kwargs(self): - out = StringIO() - err = StringIO() - call_command(Command(), verbosity=3, stdout=out, stderr=err) - self.assertIn("Print JohnDoe", out.getvalue()) - self.assertIn("Print extra={'n': 'JohnDoe'}", out.getvalue()) - self.assertIn("Print John extra={'n': 'Doe'}", out.getvalue()) - self.assertIn("Success JohnDoe", out.getvalue()) - self.assertIn("Success extra={'n': 'JohnDoe'}", out.getvalue()) - self.assertIn("Success John extra={'n': 'Doe'}", out.getvalue()) - self.assertIn("Warning JohnDoe", out.getvalue()) - self.assertIn("Warning extra={'n': 'JohnDoe'}", out.getvalue()) - self.assertIn("Warning John extra={'n': 'Doe'}", out.getvalue()) - self.assertIn("Error JohnDoe", err.getvalue()) - self.assertIn("Error\nextra={'n': 'JohnDoe'}", err.getvalue()) - self.assertIn("Error John\nextra={'n': 'Doe'}", err.getvalue()) - - def test_print_to_log_default_verbosity(self): - # default verbosity == 1 - command = Command() - call_command(command, logger=True) - calls = command.handler.logger.mock_calls - self.assertNotIn(call.info("Print default"), calls) - self.assertIn(call.info("Print 0"), calls) - self.assertIn(call.info("Print 1"), calls) - self.assertNotIn(call.info("Print 2"), calls) - self.assertIn(call.info("Success default"), calls) - self.assertIn(call.info("Success 0"), calls) - self.assertIn(call.info("Success 1"), calls) - self.assertNotIn(call.info("Success 2"), calls) - self.assertIn(call.warning("Warning default"), calls) - self.assertIn(call.warning("Warning 0"), calls) - self.assertIn(call.warning("Warning 1"), calls) - self.assertNotIn(call.warning("Warning 2"), calls) - self.assertIn(call.error("Error"), calls) - - def test_print_to_log_verbosity_0(self): - command = Command() - call_command(command, verbosity=0, logger=True) - calls = command.handler.logger.mock_calls - self.assertIn(call.info("Print 0"), calls) - self.assertNotIn(call.info("Print 1"), calls) - self.assertNotIn(call.info("Print 2"), calls) - self.assertIn(call.info("Success 0"), calls) - self.assertNotIn(call.info("Success 1"), calls) - self.assertNotIn(call.info("Success 2"), calls) - self.assertIn(call.warning("Warning 0"), calls) - self.assertNotIn(call.warning("Warning 1"), calls) - self.assertNotIn(call.warning("Warning 2"), calls) - self.assertIn(call.error("Error"), calls) - - def test_print_to_log_verbosity_3(self): - command = Command() - call_command(command, verbosity=3, logger=True) - calls = command.handler.logger.mock_calls - self.assertIn(call.info("Print 0"), calls) - self.assertIn(call.info("Print 1"), calls) - self.assertIn(call.info("Print 2"), calls) - self.assertIn(call.info("Success 0"), calls) - self.assertIn(call.info("Success 1"), calls) - self.assertIn(call.info("Success 2"), calls) - self.assertIn(call.warning("Warning 0"), calls) - self.assertIn(call.warning("Warning 1"), calls) - self.assertIn(call.warning("Warning 2"), calls) - self.assertIn(call.error("Error"), calls) - - def test_print_to_log_args_kwargs(self): - command = Command() - call_command(command, verbosity=3, logger=True) - calls = command.handler.logger.mock_calls - self.assertIn(call.info('Print %s', 'JohnDoe'), calls) - self.assertIn(call.info('Print', extra={'n': 'JohnDoe'}), calls) - self.assertIn(call.info('Print %s', 'John', extra={'n': 'Doe'}), calls) - self.assertIn(call.info('Success JohnDoe'), calls) - self.assertIn(call.info('Success', extra={'n': 'JohnDoe'}), calls) - self.assertIn(call.info('Success John', extra={'n': 'Doe'}), calls) - self.assertIn(call.warning('Warning JohnDoe'), calls) - self.assertIn(call.warning('Warning', extra={'n': 'JohnDoe'}), calls) - self.assertIn(call.warning('Warning John', extra={'n': 'Doe'}), calls) - self.assertIn(call.error('Error JohnDoe'), calls) - self.assertIn(call.error('Error', extra={'n': 'JohnDoe'}), calls) - self.assertIn(call.error('Error John', extra={'n': 'Doe'}), calls) diff --git a/app/utils/tests/test_database_router.py b/app/utils/tests/test_database_router.py deleted file mode 100644 index c618442..0000000 --- a/app/utils/tests/test_database_router.py +++ /dev/null @@ -1,24 +0,0 @@ -from bod.models import BodContactOrganisation -from provider.models import Provider - -from django.test import TestCase - - -class DatabaseRouterTestCase(TestCase): - - def test_database_routing_inside_tests(self): - self.assertEqual(BodContactOrganisation.objects.db, 'default') - self.assertEqual(Provider.objects.db, 'default') - - def test_database_routing_outside_tests(self): - with self.settings(TESTING=False): - self.assertEqual(BodContactOrganisation.objects.db, 'bod') - self.assertEqual(Provider.objects.db, 'default') - - def test_writing_to_bod_supported_inside_tests(self): - BodContactOrganisation.objects.create() - self.assertEqual(BodContactOrganisation.objects.count(), 1) - - def test_writing_to_bod_not_supported_outside_tests(self): - with self.settings(TESTING=False): - self.assertRaises(RuntimeError, BodContactOrganisation.objects.create) diff --git a/mypy.ini b/mypy.ini index c8300e9..34a19cf 100644 --- a/mypy.ini +++ b/mypy.ini @@ -4,7 +4,7 @@ mypy_path = ./app,./app/stubs plugins = mypy_django_plugin.main explicit_package_bases = True -exclude = ^.*[/\\]test_.* +exclude = app/tests # add some additional checks, but not quite strict strict = True