From fa0c34fc669ba579ced8f8c7ede97278c7d6cbf8 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Thu, 12 Dec 2024 16:12:59 +0100 Subject: [PATCH] Upgrade django-setup-configuration Certain steps (e.g loading sites, tokens, services) will be replaced through separate branches. Those branches will branch from this branch. --- bin/setup_configuration.sh | 14 +- docker-compose.yml | 46 +++---- docker/setup_configuration/data.yaml | 1 + docs/installation/config_cli.rst | 101 ++------------ requirements/base.txt | 19 ++- requirements/ci.txt | 23 +++- requirements/dev.txt | 23 +++- src/objects/conf/base.py | 36 +---- src/objects/config/__init__.py | 0 src/objects/config/demo.py | 56 -------- src/objects/config/objecttypes.py | 59 --------- src/objects/config/site.py | 37 ------ .../commands/test_setup_configuration.py | 123 ------------------ .../tests/config/test_demo_configuration.py | 73 ----------- .../config/test_objecttypes_configuration.py | 80 ------------ .../tests/config/test_site_configuration.py | 66 ---------- 16 files changed, 103 insertions(+), 654 deletions(-) create mode 100644 docker/setup_configuration/data.yaml delete mode 100644 src/objects/config/__init__.py delete mode 100644 src/objects/config/demo.py delete mode 100644 src/objects/config/objecttypes.py delete mode 100644 src/objects/config/site.py delete mode 100644 src/objects/tests/commands/test_setup_configuration.py delete mode 100644 src/objects/tests/config/test_demo_configuration.py delete mode 100644 src/objects/tests/config/test_objecttypes_configuration.py delete mode 100644 src/objects/tests/config/test_site_configuration.py diff --git a/bin/setup_configuration.sh b/bin/setup_configuration.sh index 716b22e8..19f53f00 100755 --- a/bin/setup_configuration.sh +++ b/bin/setup_configuration.sh @@ -1,10 +1,14 @@ #!/bin/bash -# setup initial configuration using environment variables +# setup initial configuration using an yaml file # Run this script from the root of the repository -#set -e -${SCRIPTPATH}/wait_for_db.sh +set -e -src/manage.py migrate -src/manage.py setup_configuration --no-selftest +if [[ "${RUN_SETUP_CONFIG,,}" =~ ^(true|1|yes)$ ]]; then + # wait for required services + /wait_for_db.sh + + src/manage.py migrate + src/manage.py setup_configuration --yaml-file setup_configuration/data.yaml +fi diff --git a/docker-compose.yml b/docker-compose.yml index 9ac0ce1d..e1ff1f45 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,28 +16,17 @@ services: build: &web_build context: . environment: &web_env - - DJANGO_SETTINGS_MODULE=objects.conf.docker - - SECRET_KEY=${SECRET_KEY:-1(@f(-6s_u(5fd&1sg^uvu2s(c-9sapw)1era8q&)g)h@cwxxg} - - IS_HTTPS=no - - ALLOWED_HOSTS=* - - CACHE_DEFAULT=redis:6379/0 - - CACHE_AXES=redis:6379/0 - - CELERY_BROKER_URL=redis://redis:6379/1 - - CELERY_RESULT_BACKEND=redis://redis:6379/1 - - CELERY_LOGLEVEL=DEBUG - - DISABLE_2FA=${DISABLE_2FA:-yes} - - SUBPATH=${SUBPATH} - # setup_configuration env vars - - SITES_CONFIG_ENABLE=yes - - OBJECTS_DOMAIN=web:8000 - - OBJECTS_ORGANIZATION=Objects - - OBJECTS_OBJECTTYPES_CONFIG_ENABLE=false - # - OBJECTTYPES_API_ROOT=https://objecttypes.example.com/api/v2/ - - OBJECTS_OBJECTTYPES_TOKEN=some-random-string - - DEMO_CONFIG_ENABLE=yes - - DEMO_TOKEN=demo-random-string - - DEMO_PERSON=Demo - - DEMO_EMAIL=demo@demo.local + DJANGO_SETTINGS_MODULE: objects.conf.docker + SECRET_KEY: ${SECRET_KEY:-1(@f(-6s_u(5fd&1sg^uvu2s(c-9sapw)1era8q&)g)h@cwxxg} + IS_HTTPS: no + ALLOWED_HOSTS: '*' + CACHE_DEFAULT: redis:6379/0 + CACHE_AXES: redis:6379/0 + CELERY_BROKER_URL: redis://redis:6379/1 + CELERY_RESULT_BACKEND: redis://redis:6379/1 + CELERY_LOGLEVEL: DEBUG + DISABLE_2FA: ${DISABLE_2FA:-yes} + SUBPATH: ${SUBPATH} healthcheck: test: ["CMD", "python", "-c", "import requests; exit(requests.head('http://localhost:8000/admin/').status_code not in [200, 302])"] interval: 30s @@ -46,7 +35,7 @@ services: # This should allow for enough time for migrations to run before the max # retries have passed. This healthcheck in turn allows other containers # to wait for the database migrations. - start_period: 30s + start_period: 30s ports: - 8000:8000 depends_on: @@ -58,12 +47,17 @@ services: web-init: image: maykinmedia/objects-api:latest build: *web_build - environment: *web_env + environment: + <<: *web_env + # + # Django-setup-configuration + RUN_SETUP_CONFIG: ${RUN_SETUP_CONFIG:-true} command: /setup_configuration.sh depends_on: - db - redis - volumes: *web_volumes + volumes: + - ./docker/setup_configuration:/app/setup_configuration celery: image: maykinmedia/objects-api:latest @@ -75,7 +69,7 @@ services: interval: 30s timeout: 5s retries: 3 - start_period: 10s + start_period: 10s depends_on: web: condition: service_healthy diff --git a/docker/setup_configuration/data.yaml b/docker/setup_configuration/data.yaml new file mode 100644 index 00000000..eb1ae458 --- /dev/null +++ b/docker/setup_configuration/data.yaml @@ -0,0 +1 @@ +... diff --git a/docs/installation/config_cli.rst b/docs/installation/config_cli.rst index 79e499b9..ac0dfcc1 100644 --- a/docs/installation/config_cli.rst +++ b/docs/installation/config_cli.rst @@ -8,10 +8,6 @@ Configuration (CLI) After deploying Objecttypes API and Objects API, they need to be configured to be fully functional. The command line tool `setup_configuration`_ assist with this configuration: -* It uses environment variables for all configuration choices, therefore you can integrate this with your - infrastructure tooling such as init containers and/or Kubernetes Jobs. -* The command can self-test the configuration to detect problems early on - You can get the full command documentation with: .. code-block:: bash @@ -28,88 +24,28 @@ Preparation =========== The command executes the list of pluggable configuration steps, and each step -required specific environment variables, that should be prepared. -Here is the description of all available configuration steps and the environment variables, -use by each step for both APIs. - +requires specific configuration information, that should be prepared. +Here is the description of all available configuration steps and the configuration +format, use by each step. Objects API =========== -Sites configuration -------------------- - -Configure the domain where Objects API is hosted +Objecttypes connection configuration +------------------------------------ -* ``SITES_CONFIG_ENABLE``: enable Site configuration. Defaults to ``False``. -* ``OBJECTS_DOMAIN``: a ``[host]:[port]`` or ``[host]`` value. Required. -* ``OBJECTS_ORGANIZATION``: name of Objects API organization. Required. +Tokens configuration +------------------- -Objecttypes configuration +Notifications configuration ------------------------- -Objects API uses Objecttypes API to validate data against JSON schemas, therefore -it should be able to request Objecttypes API. - -* ``OBJECTS_OBJECTTYPES_CONFIG_ENABLE``: enable Objecttypes configuration. Defaults - to ``False``. -* ``OBJECTTYPES_API_ROOT``: full URL to the Objecttypes API root, for example - ``https://objecttypes.gemeente.local/api/v1/``. Required. -* ``OBJECTTYPES_API_OAS``: full URL to the Objecttypes OpenAPI specification. -* ``OBJECTS_OBJECTTYPES_TOKEN``: authorization token. Required. -* ``OBJECTS_OBJECTTYPES_PERSON``: Objects API contact person. Required. -* ``OBJECTS_OBJECTTYPES_EMAIL``: Objects API contact email. Required. - -Demo user configuration ------------------------ - -Demo user can be created to check if Objects API work. It has superuser permissions, -so its creation is not recommended on production environment. - -* ``DEMO_CONFIG_ENABLE``: enable demo user configuration. Defaults to ``False``. -* ``DEMO_PERSON``: demo user contact person. Required. -* ``DEMO_EMAIL``: demo user email. Required. -* ``DEMO_TOKEN``: demo token. Required. - - -Objecttypes API -=============== - -ObjectTypes API has similar configuration steps as the Objects API. +Mozilla-django-oidc-db +---------------------- Sites configuration ------------------- -Configure the domain where Objects API is hosted - -* ``SITES_CONFIG_ENABLE``: enable Site configuration. Defaults to ``False``. -* ``OBJECTTYPES_DOMAIN``: a ``[host]:[port]`` or ``[host]`` value. Required. -* ``OBJECTTYPES_ORGANIZATION``: name of Objecttypes API organization. Required. - -Objects configuration ---------------------- - -Objects API uses Objecttypes API to validate data against JSON schemas, therefore -it should be able to request Objecttypes API. - -* ``OBJECTS_OBJECTTYPES_CONFIG_ENABLE``: enable Objecttypes configuration. Defaults - to ``False``. -* ``OBJECTTYPES_API_ROOT``: full URL to the Objecttypes API root, for example - ``https://objecttypes.gemeente.local/api/v1/``. Required. -* ``OBJECTTYPES_API_OAS``: full URL to the Objecttypes OpenAPI specification. -* ``OBJECTS_OBJECTTYPES_TOKEN``: authorization token. Required. - -Demo user configuration ------------------------ - -The similar configuration as in Objects API. - -* ``DEMO_CONFIG_ENABLE``: enable demo user configuration. Defaults to ``False``. -* ``DEMO_PERSON``: demo user contact person. Required. -* ``DEMO_EMAIL``: demo user email. Required. -* ``DEMO_TOKEN``: demo token. Required. - - Execution ========= @@ -119,19 +55,4 @@ tested. .. code-block:: bash - src/manage.py setup_configuration - - -You can skip the self-tests by using the ``--no-selftest`` flag. - -.. code-block:: bash - - src/manage.py setup_configuration --no-self-test - - -``setup_configuration`` command checks if the configuration already exists before changing it. -If you want to change some of the values of the existing configuration you can use ``--overwrite`` flag. - -.. code-block:: bash - - src/manage.py setup_configuration --overwrite + src/manage.py setup_configuration --yaml-file /path/to/config.yaml diff --git a/requirements/base.txt b/requirements/base.txt index d4cf6b3e..e6719118 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -6,6 +6,8 @@ # amqp==5.2.0 # via kombu +annotated-types==0.7.0 + # via pydantic ape-pie==0.2.0 # via # commonground-api-common @@ -151,7 +153,7 @@ django-sendfile2==0.7.0 # via django-privates django-sessionprofile==3.0.0 # via open-api-framework -django-setup-configuration==0.1.0 +django-setup-configuration==0.4.0 # via open-api-framework django-simple-certmanager==1.4.1 # via zgw-consumers @@ -260,6 +262,14 @@ psycopg2==2.9.9 # via open-api-framework pycparser==2.20 # via cffi +pydantic==2.9.2 + # via + # django-setup-configuration + # pydantic-settings +pydantic-core==2.23.4 + # via pydantic +pydantic-settings[yaml]==2.6.1 + # via django-setup-configuration pyjwt==2.4.0 # via # commonground-api-common @@ -278,7 +288,9 @@ python-dateutil==2.9.0.post0 python-decouple==3.8 # via open-api-framework python-dotenv==1.0.0 - # via open-api-framework + # via + # open-api-framework + # pydantic-settings pytz==2024.1 # via # drf-yasg @@ -288,6 +300,7 @@ pyyaml==6.0.1 # drf-spectacular # drf-yasg # oyaml + # pydantic-settings qrcode==6.1 # via django-two-factor-auth redis==3.5.3 @@ -321,6 +334,8 @@ tornado==6.4.1 typing-extensions==4.9.0 # via # mozilla-django-oidc-db + # pydantic + # pydantic-core # zgw-consumers tzdata==2024.1 # via celery diff --git a/requirements/ci.txt b/requirements/ci.txt index a9bab76e..61b94b65 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -8,6 +8,10 @@ amqp==5.2.0 # via # -r requirements/base.txt # kombu +annotated-types==0.7.0 + # via + # -r requirements/base.txt + # pydantic ape-pie==0.2.0 # via # -r requirements/base.txt @@ -235,7 +239,7 @@ django-sessionprofile==3.0.0 # via # -r requirements/base.txt # open-api-framework -django-setup-configuration==0.1.0 +django-setup-configuration==0.4.0 # via # -r requirements/base.txt # open-api-framework @@ -447,6 +451,19 @@ pycparser==2.20 # via # -r requirements/base.txt # cffi +pydantic==2.9.2 + # via + # -r requirements/base.txt + # django-setup-configuration + # pydantic-settings +pydantic-core==2.23.4 + # via + # -r requirements/base.txt + # pydantic +pydantic-settings[yaml]==2.6.1 + # via + # -r requirements/base.txt + # django-setup-configuration pyflakes==3.2.0 # via flake8 pyjwt==2.4.0 @@ -483,6 +500,7 @@ python-dotenv==1.0.0 # via # -r requirements/base.txt # open-api-framework + # pydantic-settings pytz==2024.1 # via # -r requirements/base.txt @@ -494,6 +512,7 @@ pyyaml==6.0.1 # drf-spectacular # drf-yasg # oyaml + # pydantic-settings # vcrpy qrcode==6.1 # via @@ -552,6 +571,8 @@ typing-extensions==4.9.0 # via # -r requirements/base.txt # mozilla-django-oidc-db + # pydantic + # pydantic-core # zgw-consumers tzdata==2024.1 # via diff --git a/requirements/dev.txt b/requirements/dev.txt index 8a115576..d065a802 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -10,6 +10,10 @@ amqp==5.2.0 # via # -r requirements/base.txt # kombu +annotated-types==0.7.0 + # via + # -r requirements/base.txt + # pydantic ape-pie==0.2.0 # via # -r requirements/base.txt @@ -250,7 +254,7 @@ django-sessionprofile==3.0.0 # via # -r requirements/base.txt # open-api-framework -django-setup-configuration==0.1.0 +django-setup-configuration==0.4.0 # via # -r requirements/base.txt # open-api-framework @@ -470,6 +474,19 @@ pycparser==2.20 # via # -r requirements/base.txt # cffi +pydantic==2.9.2 + # via + # -r requirements/base.txt + # django-setup-configuration + # pydantic-settings +pydantic-core==2.23.4 + # via + # -r requirements/base.txt + # pydantic +pydantic-settings[yaml]==2.6.1 + # via + # -r requirements/base.txt + # django-setup-configuration pyflakes==3.2.0 # via flake8 pygments==2.17.2 @@ -512,6 +529,7 @@ python-dotenv==1.0.0 # via # -r requirements/base.txt # open-api-framework + # pydantic-settings pytz==2024.1 # via # -r requirements/base.txt @@ -524,6 +542,7 @@ pyyaml==6.0.1 # drf-spectacular # drf-yasg # oyaml + # pydantic-settings # vcrpy qrcode==6.1 # via @@ -612,6 +631,8 @@ typing-extensions==4.9.0 # via # -r requirements/base.txt # mozilla-django-oidc-db + # pydantic + # pydantic-core # zgw-consumers tzdata==2024.1 # via diff --git a/src/objects/conf/base.py b/src/objects/conf/base.py index be71aeec..3848b24f 100644 --- a/src/objects/conf/base.py +++ b/src/objects/conf/base.py @@ -18,7 +18,6 @@ # Project applications. "objects.accounts", "objects.api", - "objects.config", "objects.core", "objects.token", "objects.utils", @@ -83,37 +82,4 @@ # # Django setup configuration # -SETUP_CONFIGURATION_STEPS = [ - "objects.config.site.SiteConfigurationStep", - "objects.config.objecttypes.ObjecttypesStep", - "objects.config.demo.DemoUserStep", -] - - -# -# Objecttypes settings -# - -# setup_configuration command -# sites config -SITES_CONFIG_ENABLE = config("SITES_CONFIG_ENABLE", default=False, add_to_docs=False) -OBJECTS_DOMAIN = config("OBJECTS_DOMAIN", "", add_to_docs=False) -OBJECTS_ORGANIZATION = config("OBJECTS_ORGANIZATION", "", add_to_docs=False) -# objecttypes config -OBJECTS_OBJECTTYPES_CONFIG_ENABLE = config( - "OBJECTS_OBJECTTYPES_CONFIG_ENABLE", default=False, add_to_docs=False -) -OBJECTTYPES_API_ROOT = config("OBJECTTYPES_API_ROOT", "", add_to_docs=False) -if OBJECTTYPES_API_ROOT and not OBJECTTYPES_API_ROOT.endswith("/"): - OBJECTTYPES_API_ROOT = f"{OBJECTTYPES_API_ROOT.strip()}/" -OBJECTTYPES_API_OAS = config( - "OBJECTTYPES_API_OAS", - default=f"{OBJECTTYPES_API_ROOT}schema/openapi.yaml", - add_to_docs=False, -) -OBJECTS_OBJECTTYPES_TOKEN = config("OBJECTS_OBJECTTYPES_TOKEN", "", add_to_docs=False) -# Demo User Configuration -DEMO_CONFIG_ENABLE = config("DEMO_CONFIG_ENABLE", default=False, add_to_docs=False) -DEMO_TOKEN = config("DEMO_TOKEN", "", add_to_docs=False) -DEMO_PERSON = config("DEMO_PERSON", "", add_to_docs=False) -DEMO_EMAIL = config("DEMO_EMAIL", "", add_to_docs=False) +SETUP_CONFIGURATION_STEPS = tuple() diff --git a/src/objects/config/__init__.py b/src/objects/config/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/objects/config/demo.py b/src/objects/config/demo.py deleted file mode 100644 index 8155717c..00000000 --- a/src/objects/config/demo.py +++ /dev/null @@ -1,56 +0,0 @@ -from django.conf import settings -from django.urls import reverse - -import requests -from django_setup_configuration.configuration import BaseConfigurationStep -from django_setup_configuration.exceptions import SelfTestFailed - -from objects.token.models import TokenAuth -from objects.utils import build_absolute_url - - -class DemoUserStep(BaseConfigurationStep): - """ - Create demo user to request Objects API - - **NOTE** For now demo user has all permissions. - """ - - verbose_name = "Demo User Configuration" - required_settings = [ - "DEMO_TOKEN", - "DEMO_PERSON", - "DEMO_EMAIL", - ] - enable_setting = "DEMO_CONFIG_ENABLE" - - def is_configured(self) -> bool: - return TokenAuth.objects.filter(token=settings.DEMO_TOKEN).exists() - - def configure(self): - TokenAuth.objects.update_or_create( - token=settings.DEMO_TOKEN, - defaults={ - "contact_person": settings.DEMO_PERSON, - "email": settings.DEMO_EMAIL, - "is_superuser": True, - }, - ) - - def test_configuration(self): - endpoint = reverse("v2:object-list") - full_url = build_absolute_url(endpoint, request=None) - - try: - response = requests.get( - full_url, - headers={ - "Authorization": f"Token {settings.DEMO_TOKEN}", - "Accept": "application/json", - }, - ) - response.raise_for_status() - except requests.RequestException as exc: - raise SelfTestFailed( - "Could not list objects for the configured token" - ) from exc diff --git a/src/objects/config/objecttypes.py b/src/objects/config/objecttypes.py deleted file mode 100644 index 919fdff7..00000000 --- a/src/objects/config/objecttypes.py +++ /dev/null @@ -1,59 +0,0 @@ -from django.conf import settings - -import requests -from django_setup_configuration.configuration import BaseConfigurationStep -from django_setup_configuration.exceptions import SelfTestFailed -from zgw_consumers.client import build_client -from zgw_consumers.constants import APITypes, AuthTypes -from zgw_consumers.models import Service - - -class ObjecttypesStep(BaseConfigurationStep): - """ - Configure credentials for Objects API to request Objecttypes API - - Normal mode doesn't change the token after its initial creation. - If the token is changed, run this command with 'overwrite' flag - """ - - verbose_name = "Objecttypes Configuration" - required_settings = [ - "OBJECTTYPES_API_ROOT", - "OBJECTS_OBJECTTYPES_TOKEN", - ] - enable_setting = "OBJECTS_OBJECTTYPES_CONFIG_ENABLE" - - def is_configured(self) -> bool: - return Service.objects.filter(api_root=settings.OBJECTTYPES_API_ROOT).exists() - - def configure(self) -> None: - Service.objects.update_or_create( - api_root=settings.OBJECTTYPES_API_ROOT, - defaults={ - "label": "Objecttypes API", - "api_type": APITypes.orc, - "oas": settings.OBJECTTYPES_API_OAS, - "auth_type": AuthTypes.api_key, - "header_key": "Authorization", - "header_value": f"Token {settings.OBJECTS_OBJECTTYPES_TOKEN}", - }, - ) - - def test_configuration(self) -> None: - """ - This check depends on the configuration in Objecttypes - """ - client = build_client( - Service.objects.get(api_root=settings.OBJECTTYPES_API_ROOT) - ) - try: - response = client.get("objecttypes") - except requests.RequestException as exc: - raise SelfTestFailed( - "Could not Could not retrieve list of objecttypes from Objecttypes API." - ) from exc - - try: - response.json() - except requests.exceptions.JSONDecodeError: - raise SelfTestFailed("Object type version didn't have any data") diff --git a/src/objects/config/site.py b/src/objects/config/site.py deleted file mode 100644 index af20fb0e..00000000 --- a/src/objects/config/site.py +++ /dev/null @@ -1,37 +0,0 @@ -from django.conf import settings -from django.contrib.sites.models import Site -from django.urls import reverse - -import requests -from django_setup_configuration.configuration import BaseConfigurationStep -from django_setup_configuration.exceptions import SelfTestFailed - -from objects.utils import build_absolute_url - - -class SiteConfigurationStep(BaseConfigurationStep): - """ - Configure the application site/domain. - """ - - verbose_name = "Site Configuration" - required_settings = ["OBJECTS_DOMAIN", "OBJECTS_ORGANIZATION"] - enable_setting = "SITES_CONFIG_ENABLE" - - def is_configured(self) -> bool: - site = Site.objects.get_current() - return site.domain == settings.OBJECTS_DOMAIN - - def configure(self): - site = Site.objects.get_current() - site.domain = settings.OBJECTS_DOMAIN - site.name = f"Objects {settings.OBJECTS_ORGANIZATION}".strip() - site.save() - - def test_configuration(self): - full_url = build_absolute_url(reverse("home")) - try: - response = requests.get(full_url) - response.raise_for_status() - except requests.RequestException as exc: - raise SelfTestFailed(f"Could not access home page at '{full_url}'") from exc diff --git a/src/objects/tests/commands/test_setup_configuration.py b/src/objects/tests/commands/test_setup_configuration.py deleted file mode 100644 index 61b0d656..00000000 --- a/src/objects/tests/commands/test_setup_configuration.py +++ /dev/null @@ -1,123 +0,0 @@ -from io import StringIO - -from django.contrib.sites.models import Site -from django.core.management import CommandError, call_command -from django.test import TestCase, override_settings -from django.urls import reverse - -import requests_mock -from rest_framework import status -from zgw_consumers.client import build_client -from zgw_consumers.models import Service - -from objects.config.demo import DemoUserStep -from objects.config.objecttypes import ObjecttypesStep -from objects.config.site import SiteConfigurationStep - -from ..utils import mock_service_oas_get - - -@override_settings( - SITES_CONFIG_ENABLE=True, - OBJECTS_DOMAIN="objects.example.com", - OBJECTS_ORGANIZATION="ACME", - OBJECTS_OBJECTTYPES_CONFIG_ENABLE=True, - OBJECTTYPES_API_ROOT="https://objecttypes.example.com/api/v2/", - OBJECTTYPES_API_OAS="https://objecttypes.example.com/api/v2/schema/openapi.yaml", - OBJECTS_OBJECTTYPES_TOKEN="some-random-string", - DEMO_CONFIG_ENABLE=True, - DEMO_TOKEN="demo-random-string", - DEMO_PERSON="Demo", - DEMO_EMAIL="demo@demo.local", -) -class SetupConfigurationTests(TestCase): - def setUp(self): - super().setUp() - - self.addCleanup(Site.objects.clear_cache) - - @requests_mock.Mocker() - def test_setup_configuration(self, m): - stdout = StringIO() - # mocks - m.get("http://objects.example.com/", status_code=200) - m.get("http://objects.example.com/api/v2/objects", json=[]) - mock_service_oas_get( - m, "https://objecttypes.example.com/api/v2/", "objecttypes" - ) - m.get("https://objecttypes.example.com/api/v2/objecttypes", json={}) - - call_command("setup_configuration", stdout=stdout) - - with self.subTest("Command output"): - command_output = stdout.getvalue().splitlines() - expected_output = [ - f"Configuration will be set up with following steps: [{SiteConfigurationStep()}, " - f"{ObjecttypesStep()}, {DemoUserStep()}]", - f"Configuring {SiteConfigurationStep()}...", - f"{SiteConfigurationStep()} is successfully configured", - f"Configuring {ObjecttypesStep()}...", - f"{ObjecttypesStep()} is successfully configured", - f"Configuring {DemoUserStep()}...", - f"{DemoUserStep()} is successfully configured", - "Instance configuration completed.", - ] - - self.assertEqual(command_output, expected_output) - - with self.subTest("Site configured correctly"): - site = Site.objects.get_current() - self.assertEqual(site.domain, "objects.example.com") - self.assertEqual(site.name, "Objects ACME") - - with self.subTest("Objects can query Objecttypes API"): - client = build_client( - Service.objects.get(api_root="https://objecttypes.example.com/api/v2/") - ) - - self.assertIsNotNone(client) - - client.get("objecttypes") - - list_call = m.last_request - self.assertEqual( - list_call.url, "https://objecttypes.example.com/api/v2/objecttypes" - ) - self.assertIn("Authorization", list_call.headers) - self.assertEqual( - list_call.headers["Authorization"], "Token some-random-string" - ) - - with self.subTest("Demo user configured correctly"): - response = self.client.get( - reverse("v2:object-list"), - HTTP_AUTHORIZATION="Token demo-random-string", - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - @requests_mock.Mocker() - def test_setup_configuration_selftest_fails(self, m): - m.get("http://objects.example.com/", status_code=500) - m.get("http://objects.example.com/api/v2/objects", status_code=200) - mock_service_oas_get( - m, "https://objecttypes.example.com/api/v2/", "objecttypes" - ) - m.get("https://objecttypes.example.com/api/v2/objecttypes", json={}) - - with self.assertRaisesMessage( - CommandError, - "Configuration test failed with errors: " - "Site Configuration: Could not access home page at 'http://objects.example.com/'", - ): - call_command("setup_configuration") - - @requests_mock.Mocker() - def test_setup_configuration_without_selftest(self, m): - stdout = StringIO() - - call_command("setup_configuration", no_selftest=True, stdout=stdout) - command_output = stdout.getvalue() - - self.assertEqual(len(m.request_history), 0) - self.assertTrue("Selftest is skipped" in command_output) diff --git a/src/objects/tests/config/test_demo_configuration.py b/src/objects/tests/config/test_demo_configuration.py deleted file mode 100644 index 5d9a2cce..00000000 --- a/src/objects/tests/config/test_demo_configuration.py +++ /dev/null @@ -1,73 +0,0 @@ -from unittest.mock import patch - -from django.test import TestCase, override_settings - -import requests -import requests_mock -from django_setup_configuration.exceptions import SelfTestFailed - -from objects.config.demo import DemoUserStep -from objects.token.models import TokenAuth - - -@override_settings( - DEMO_TOKEN="demo-random-string", DEMO_PERSON="Demo", DEMO_EMAIL="demo@demo.local" -) -class DemoConfigurationTests(TestCase): - def test_configure(self): - configuration = DemoUserStep() - - configuration.configure() - - token_auth = TokenAuth.objects.get() - self.assertEqual(token_auth.token, "demo-random-string") - self.assertTrue(token_auth.is_superuser) - self.assertEqual(token_auth.contact_person, "Demo") - self.assertEqual(token_auth.email, "demo@demo.local") - - @requests_mock.Mocker() - @patch( - "objects.config.demo.build_absolute_url", - return_value="http://testserver/objects", - ) - def test_configuration_check_ok(self, m, *mocks): - configuration = DemoUserStep() - configuration.configure() - m.get("http://testserver/objects", json=[]) - - configuration.test_configuration() - - self.assertEqual(m.last_request.url, "http://testserver/objects") - self.assertEqual(m.last_request.method, "GET") - - @requests_mock.Mocker() - @patch( - "objects.config.demo.build_absolute_url", - return_value="http://testserver/objects", - ) - def test_configuration_check_failures(self, m, *mocks): - configuration = DemoUserStep() - configuration.configure() - - mock_kwargs = ( - {"exc": requests.ConnectTimeout}, - {"exc": requests.ConnectionError}, - {"status_code": 404}, - {"status_code": 403}, - {"status_code": 500}, - ) - for mock_config in mock_kwargs: - with self.subTest(mock=mock_config): - m.get("http://testserver/objects", **mock_config) - - with self.assertRaises(SelfTestFailed): - configuration.test_configuration() - - def test_is_configured(self): - configuration = DemoUserStep() - - self.assertFalse(configuration.is_configured()) - - configuration.configure() - - self.assertTrue(configuration.is_configured()) diff --git a/src/objects/tests/config/test_objecttypes_configuration.py b/src/objects/tests/config/test_objecttypes_configuration.py deleted file mode 100644 index 37f8c336..00000000 --- a/src/objects/tests/config/test_objecttypes_configuration.py +++ /dev/null @@ -1,80 +0,0 @@ -from django.test import TestCase, override_settings - -import requests -import requests_mock -from django_setup_configuration.exceptions import SelfTestFailed -from zgw_consumers.constants import AuthTypes -from zgw_consumers.models import Service - -from objects.config.objecttypes import ObjecttypesStep - -from ..utils import mock_service_oas_get - - -@override_settings( - OBJECTTYPES_API_ROOT="https://objecttypes.example.com/api/v2/", - OBJECTTYPES_API_OAS="https://objecttypes.example.com/api/v2/schema/openapi.yaml", - OBJECTS_OBJECTTYPES_TOKEN="some-random-string", -) -class ObjecttypesConfigurationTests(TestCase): - def test_configure(self): - configuration = ObjecttypesStep() - - configuration.configure() - - service = Service.objects.get( - api_root="https://objecttypes.example.com/api/v2/" - ) - self.assertEqual( - service.oas, "https://objecttypes.example.com/api/v2/schema/openapi.yaml" - ) - self.assertEqual(service.auth_type, AuthTypes.api_key) - self.assertEqual(service.header_key, "Authorization") - self.assertEqual(service.header_value, "Token some-random-string") - - @requests_mock.Mocker() - def test_selftest_ok(self, m): - configuration = ObjecttypesStep() - configuration.configure() - mock_service_oas_get( - m, "https://objecttypes.example.com/api/v2/", "objecttypes" - ) - m.get("https://objecttypes.example.com/api/v2/objecttypes", json={}) - - configuration.test_configuration() - - self.assertEqual( - m.last_request.url, "https://objecttypes.example.com/api/v2/objecttypes" - ) - - @requests_mock.Mocker() - def test_selftest_fail(self, m): - configuration = ObjecttypesStep() - configuration.configure() - mock_service_oas_get( - m, "https://objecttypes.example.com/api/v2/", "objecttypes" - ) - - mock_kwargs = ( - {"exc": requests.ConnectTimeout}, - {"exc": requests.ConnectionError}, - {"status_code": 404}, - {"status_code": 403}, - {"status_code": 500}, - ) - for mock_config in mock_kwargs: - with self.subTest(mock=mock_config): - m.get( - "https://objecttypes.example.com/api/v2/objecttypes", **mock_config - ) - - with self.assertRaises(SelfTestFailed): - configuration.test_configuration() - - def test_is_configured(self): - configuration = ObjecttypesStep() - self.assertFalse(configuration.is_configured()) - - configuration.configure() - - self.assertTrue(configuration.is_configured()) diff --git a/src/objects/tests/config/test_site_configuration.py b/src/objects/tests/config/test_site_configuration.py deleted file mode 100644 index b6e47ecd..00000000 --- a/src/objects/tests/config/test_site_configuration.py +++ /dev/null @@ -1,66 +0,0 @@ -from django.contrib.sites.models import Site -from django.test import TestCase, override_settings - -import requests -import requests_mock -from django_setup_configuration.exceptions import SelfTestFailed - -from objects.config.site import SiteConfigurationStep - - -@override_settings( - OBJECTS_DOMAIN="localhost:8000", - OBJECTS_ORGANIZATION="ACME", -) -class SiteConfigurationTests(TestCase): - def setUp(self): - super().setUp() - - self.addCleanup(Site.objects.clear_cache) - - def test_set_domain(self): - configuration = SiteConfigurationStep() - configuration.configure() - - site = Site.objects.get_current() - self.assertEqual(site.domain, "localhost:8000") - self.assertEqual(site.name, "Objects ACME") - - @requests_mock.Mocker() - def test_configuration_check_ok(self, m): - m.get("http://localhost:8000/", status_code=200) - configuration = SiteConfigurationStep() - configuration.configure() - - configuration.test_configuration() - - self.assertEqual(m.last_request.url, "http://localhost:8000/") - self.assertEqual(m.last_request.method, "GET") - - @requests_mock.Mocker() - def test_configuration_check_failures(self, m): - configuration = SiteConfigurationStep() - configuration.configure() - - mock_kwargs = ( - {"exc": requests.ConnectTimeout}, - {"exc": requests.ConnectionError}, - {"status_code": 404}, - {"status_code": 403}, - {"status_code": 500}, - ) - for mock_config in mock_kwargs: - with self.subTest(mock=mock_config): - m.get("http://localhost:8000/", **mock_config) - - with self.assertRaises(SelfTestFailed): - configuration.test_configuration() - - def test_is_configured(self): - configuration = SiteConfigurationStep() - - self.assertFalse(configuration.is_configured()) - - configuration.configure() - - self.assertTrue(configuration.is_configured())