Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ [#100] Add ConfigurationStep for Service model #99

Merged
merged 5 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ python:
- docs
- db
- drf
- setup-configuration

sphinx:
configuration: docs/conf.py
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ drf = [
zds-client = [
"gemma-zds-client>=2.0.0",
]
setup-configuration = [
"django-setup-configuration>=0.4.0",
]
# These are not the test requirements! They are extras to be installed when making use of `zgw_consumers.test`
testutils = [
"Faker>=0.7.0",
Expand Down Expand Up @@ -99,6 +102,9 @@ skip = ["env", ".tox", ".history"]
[tool.pytest.ini_options]
testpaths = ["tests"]
DJANGO_SETTINGS_MODULE = "testapp.settings"
markers = [
"config_path: the path to the YAML file that is loaded for setup_configuration",
]

[tool.bumpversion]
current_version = "0.35.1"
Expand Down
1 change: 1 addition & 0 deletions testapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"simple_certmanager",
"solo",
"testapp",
"django_setup_configuration",
]

MIDDLEWARE = [
Expand Down
18 changes: 18 additions & 0 deletions tests/files/setup_config_services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
zgw_consumers_config_enable: True
zgw_consumers:
services:
- identifier: objecten-test
label: Objecten API test
api_root: http://objecten.local/api/v1/
api_connection_check_path: objects
api_type: orc
auth_type: api_key
header_key: Authorization
header_value: Token foo
- identifier: zaken-test
label: Zaken API test
api_root: http://zaken.local/api/v1/
api_type: zrc
auth_type: zgw
client_id: client
secret: super-secret
20 changes: 20 additions & 0 deletions tests/files/setup_config_services_all_fields.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
zgw_consumers_config_enable: True
zgw_consumers:
services:
- identifier: objecten-test
label: Objecten API test
api_root: http://objecten.local/api/v1/
api_connection_check_path: objects
api_type: orc
auth_type: api_key
header_key: Authorization
header_value: Token foo
client_id: client
secret: super-secret
nlx: http://some-outway-adress.local:8080/
user_id: open-formulieren
user_representation: Open Formulieren
timeout: 5
# NOT SUPPORTED YET
# client_certificatie: ...
# server_certificatie: ...
8 changes: 8 additions & 0 deletions tests/files/setup_config_services_required_fields.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
zgw_consumers_config_enable: True
zgw_consumers:
services:
- identifier: objecten-test
label: Objecten API test
api_root: http://objecten.local/api/v1/
api_type: orc
auth_type: zgw
152 changes: 152 additions & 0 deletions tests/test_configuration_steps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import pytest
from django_setup_configuration.test_utils import execute_single_step

from zgw_consumers.constants import APITypes, AuthTypes
from zgw_consumers.contrib.setup_configuration.steps import ServiceConfigurationStep
from zgw_consumers.models import Service
from zgw_consumers.test.factories import ServiceFactory

CONFIG_FILE_PATH = "tests/files/setup_config_services.yaml"
CONFIG_FILE_PATH_REQUIRED_FIELDS = (
"tests/files/setup_config_services_required_fields.yaml"
)
CONFIG_FILE_PATH_ALL_FIELDS = "tests/files/setup_config_services_all_fields.yaml"


@pytest.mark.django_db
def test_execute_configuration_step_success():
execute_single_step(ServiceConfigurationStep, yaml_source=CONFIG_FILE_PATH)

assert Service.objects.count() == 2

objects_service, zaken_service = Service.objects.all()

assert objects_service.slug == "objecten-test"
assert objects_service.label == "Objecten API test"
assert objects_service.api_root == "http://objecten.local/api/v1/"
assert objects_service.api_type == APITypes.orc
assert objects_service.auth_type == AuthTypes.api_key
assert objects_service.header_key == "Authorization"
assert objects_service.header_value == "Token foo"
assert objects_service.timeout == 10

assert zaken_service.slug == "zaken-test"
assert zaken_service.label == "Zaken API test"
assert zaken_service.api_root == "http://zaken.local/api/v1/"
assert zaken_service.api_type == APITypes.zrc
assert zaken_service.auth_type == AuthTypes.zgw
assert zaken_service.client_id == "client"
assert zaken_service.secret == "super-secret"
assert zaken_service.timeout == 10


@pytest.mark.django_db
def test_execute_configuration_step_update_existing():
ServiceFactory.create(
slug="zaken-test",
label="Objecttypen",
api_root="http://some.existing.service.local/api/v1/",
)

execute_single_step(ServiceConfigurationStep, yaml_source=CONFIG_FILE_PATH)

assert Service.objects.count() == 2

objects_service, zaken_service = Service.objects.all()

assert objects_service.slug == "objecten-test"
assert objects_service.label == "Objecten API test"
assert objects_service.api_root == "http://objecten.local/api/v1/"

assert zaken_service.slug == "zaken-test"
assert zaken_service.label == "Zaken API test"
assert zaken_service.api_root == "http://zaken.local/api/v1/"


@pytest.mark.django_db
def test_execute_configuration_step_with_required_fields():
execute_single_step(
ServiceConfigurationStep, yaml_source=CONFIG_FILE_PATH_REQUIRED_FIELDS
)

assert Service.objects.count() == 1

objects_service = Service.objects.get()

assert objects_service.slug == "objecten-test"
assert objects_service.label == "Objecten API test"
assert objects_service.api_root == "http://objecten.local/api/v1/"
assert objects_service.api_type == APITypes.orc
assert objects_service.auth_type == AuthTypes.zgw
assert objects_service.timeout == 10

# Not required fields
assert objects_service.api_connection_check_path == ""
assert objects_service.header_key == ""
assert objects_service.header_value == ""
assert objects_service.client_id == ""
assert objects_service.secret == ""
assert objects_service.nlx == ""
assert objects_service.user_id == ""
assert objects_service.user_representation == ""


@pytest.mark.django_db
def test_execute_configuration_step_with_all_fields():
execute_single_step(
ServiceConfigurationStep, yaml_source=CONFIG_FILE_PATH_ALL_FIELDS
)

assert Service.objects.count() == 1

objects_service = Service.objects.get()

assert objects_service.slug == "objecten-test"
assert objects_service.label == "Objecten API test"
assert objects_service.api_root == "http://objecten.local/api/v1/"
assert objects_service.api_type == APITypes.orc
assert objects_service.auth_type == AuthTypes.api_key
assert objects_service.api_connection_check_path == "objects"
assert objects_service.header_key == "Authorization"
assert objects_service.header_value == "Token foo"
assert objects_service.client_id == "client"
assert objects_service.secret == "super-secret"
assert objects_service.nlx == "http://some-outway-adress.local:8080/"
assert objects_service.user_id == "open-formulieren"
assert objects_service.user_representation == "Open Formulieren"
assert objects_service.timeout == 5


@pytest.mark.django_db
def test_execute_configuration_step_idempotent():
def make_assertions():
assert Service.objects.count() == 1

objects_service = Service.objects.get()

assert objects_service.slug == "objecten-test"
assert objects_service.label == "Objecten API test"
assert objects_service.api_root == "http://objecten.local/api/v1/"
assert objects_service.api_type == APITypes.orc
assert objects_service.auth_type == AuthTypes.api_key
assert objects_service.api_connection_check_path == "objects"
assert objects_service.header_key == "Authorization"
assert objects_service.header_value == "Token foo"
assert objects_service.client_id == "client"
assert objects_service.secret == "super-secret"
assert objects_service.nlx == "http://some-outway-adress.local:8080/"
assert objects_service.user_id == "open-formulieren"
assert objects_service.user_representation == "Open Formulieren"
assert objects_service.timeout == 5

execute_single_step(
ServiceConfigurationStep, yaml_source=CONFIG_FILE_PATH_ALL_FIELDS
)

make_assertions()

execute_single_step(
ServiceConfigurationStep, yaml_source=CONFIG_FILE_PATH_ALL_FIELDS
)

make_assertions()
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ extras =
testutils
tests
coverage
setup-configuration
deps =
django42: Django~=4.2.0
commands =
Expand Down Expand Up @@ -64,6 +65,7 @@ extras =
db
drf
docs
setup-configuration
commands=
pytest check_sphinx.py -v \
--tb=auto \
Expand Down
Empty file.
Empty file.
35 changes: 35 additions & 0 deletions zgw_consumers/contrib/setup_configuration/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from django_setup_configuration.models import ConfigurationModel, DjangoModelRef
from pydantic import Field

from zgw_consumers.models import Service


class SingleServiceConfigurationModel(ConfigurationModel):
# TODO these should probably be defined in simple_certmanager and referred to?
# client_certificate: FilePath = DjangoModelRef(Service, "client_certificate")
# server_certificate: FilePath = DjangoModelRef(Service, "server_certificate")
Comment on lines +8 to +10
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm checking with Sjoerd if Dimpact uses these currently, if not this might be something for later

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sjoerd told me that Dimpact doesn't use this currently, so I'm leaving this out of scope for now

# Identifier is mapped to slug, because slug isn't a very descriptive name for devops
identifier: str = DjangoModelRef(Service, "slug")

class Meta:
django_model_refs = {
Service: [
"label",
"api_type",
"api_root",
"api_connection_check_path",
"auth_type",
"client_id",
"secret",
"header_key",
"header_value",
"nlx",
"user_id",
"user_representation",
"timeout",
]
}


class ServicesConfigurationModel(ConfigurationModel):
services: list[SingleServiceConfigurationModel] = Field(default_factory=list)
37 changes: 37 additions & 0 deletions zgw_consumers/contrib/setup_configuration/steps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from django_setup_configuration.configuration import BaseConfigurationStep

from zgw_consumers.models import Service

from .models import ServicesConfigurationModel


class ServiceConfigurationStep(BaseConfigurationStep[ServicesConfigurationModel]):
"""
Configure Services to connect with external APIs
"""

verbose_name = "Configuration to connect with external services"
config_model = ServicesConfigurationModel
namespace = "zgw_consumers"
sergei-maertens marked this conversation as resolved.
Show resolved Hide resolved
enable_setting = "zgw_consumers_config_enable"

def execute(self, model: ServicesConfigurationModel):
for config in model.services:
Service.objects.update_or_create(
slug=config.identifier,
defaults={
stevenbal marked this conversation as resolved.
Show resolved Hide resolved
"label": config.label,
"api_type": config.api_type,
swrichards marked this conversation as resolved.
Show resolved Hide resolved
"api_root": config.api_root,
"api_connection_check_path": config.api_connection_check_path,
"auth_type": config.auth_type,
"client_id": config.client_id,
"secret": config.secret,
"header_key": config.header_key,
"header_value": config.header_value,
"nlx": config.nlx,
"user_id": config.user_id,
"user_representation": config.user_representation,
"timeout": config.timeout,
},
swrichards marked this conversation as resolved.
Show resolved Hide resolved
)