Skip to content

Commit

Permalink
✅ [#1617] added admin page tests for get_connection_check field
Browse files Browse the repository at this point in the history
  • Loading branch information
bart-maykin committed Jul 25, 2024
1 parent 9bfd0e8 commit 3e30c39
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 80 deletions.
73 changes: 73 additions & 0 deletions tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from django.test import Client
from django.urls import reverse

import requests
import requests_mock
from pytest_django.asserts import assertContains

from zgw_consumers.constants import AuthTypes
from zgw_consumers.test.factories import ServiceFactory


def test_oas_fields_enabled(admin_client: Client, settings):
settings.ZGW_CONSUMERS_IGNORE_OAS_FIELDS = False
Expand Down Expand Up @@ -32,3 +39,69 @@ def test_oas_fields_disabled(admin_client: Client, settings):

assert "oas" not in form.fields
assert "oas_file" not in form.fields


def test_get_connection_check_correct_status_code(admin_client: Client, settings):
service = ServiceFactory.create(
api_root="https://example.com/",
api_connection_check_path="foo",
auth_type=AuthTypes.zgw,
client_id="my-client-id",
secret="my-secret",
)
with requests_mock.Mocker() as m:
m.get("https://example.com/foo", status_code=401)
url = reverse(
"admin:zgw_consumers_service_change", kwargs={"object_id": service.id}
)
response = admin_client.get(url)

connection_check_inner_html = '<label>Connection check status code:</label><div class="readonly">401</div>'
assertContains(response, connection_check_inner_html, html=True)


def test_get_connection_check_encountering_error(admin_client: Client, settings):
service = ServiceFactory.create(
api_root="https://example.com/",
api_connection_check_path="foo",
auth_type=AuthTypes.zgw,
client_id="my-client-id",
secret="my-secret",
)
with requests_mock.Mocker() as m:
m.get("https://example.com/foo", exc=requests.RequestException)
url = reverse(
"admin:zgw_consumers_service_change", kwargs={"object_id": service.id}
)
response = admin_client.get(url)

connection_check_inner_html = '<label>Connection check status code:</label><div class="readonly">None</div>'
assertContains(response, connection_check_inner_html, html=True)


def test_get_connection_check_not_configured(admin_client: Client, settings):
service = ServiceFactory.create(
api_root="https://example.com/",
auth_type=AuthTypes.zgw,
client_id="my-client-id",
secret="my-secret",
)
with requests_mock.Mocker() as m:
m.get("https://example.com/", status_code=200)
url = reverse(
"admin:zgw_consumers_service_change", kwargs={"object_id": service.id}
)
response = admin_client.get(url)

connection_check_inner_html = '<label>Connection check status code:</label><div class="readonly">200</div>'
assertContains(response, connection_check_inner_html, html=True)


def test_get_connection_opening_add_page(admin_client: Client, settings):
url = reverse("admin:zgw_consumers_service_add")
response = admin_client.get(url)

connection_check_inner_html = (
'<label>Connection check status code:</label><div class="readonly">n/a</div>'
)
assertContains(response, connection_check_inner_html, html=True)
53 changes: 24 additions & 29 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,32 @@

import pytest

from zgw_consumers.models.validators import NonUrlValidator, PrefixValidator


def test_start_with_validator_starts_with_false():
validator = PrefixValidator(prefix="/", starts_with=False)

assert validator.__call__("no_leading_slash") is None

with pytest.raises(ValidationError) as exc_context:
validator.__call__("/with_leading_slash")

assert "The given value cannot start with '/'" in exc_context.value


def test_start_with_validator_starts_with_true():
validator = PrefixValidator(prefix="/")

with pytest.raises(ValidationError) as exc_context:
validator.__call__("no_leading_slash")

assert "The given value must start with '/'" in exc_context.value

assert validator.__call__("/with_leading_slash") is None
from zgw_consumers.models.validators import NonUrlValidator, validate_leading_slashes


def test_is_not_url_validator():
validator = NonUrlValidator()

assert validator.__call__("some random text") is None

with pytest.raises(ValidationError) as exc_context:
assert validator.__call__("http://www.example.com")

assert "Value cannot be a URL" in exc_context.value
try:
validator("some random text")
except ValidationError:
pytest.fail("Expected validation to pass")

with pytest.raises(
ValidationError,
match="Value cannot be a URL.",
):
validator("http://www.example.com")


def test_validate_leading_slashes():
try:
validate_leading_slashes("foo")
except ValidationError:
pytest.fail("Expected validation to pass")

with pytest.raises(
ValidationError,
match="The value must be a relative path.",
):
validate_leading_slashes("/foo")
3 changes: 3 additions & 0 deletions zgw_consumers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class ServiceAdmin(admin.ModelAdmin):

@admin.display(description=_("Connection check status code"))
def get_connection_check(self, obj):
if obj._state.adding:
return "n/a"

return obj.connection_check

def get_fields(self, request: HttpRequest, obj: models.Model | None = None):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by Django 4.2 on 2024-05-21 08:15

import django.core.validators
from django.db import migrations, models

import zgw_consumers.models.validators
Expand All @@ -17,11 +18,13 @@ class Migration(migrations.Migration):
name="api_connection_check_path",
field=models.CharField(
blank=True,
help_text="An optional API endpoint which will be used to check if the API is configured correctly and is currently up or down. This field is only used for the admin's 'Connection check status code' field.",
help_text="A relative URL to perform a connection test. If left blank, the API root itself is used. This connection check is only performed in the admin when viewing the service configuration.",
max_length=255,
validators=[
zgw_consumers.models.validators.PrefixValidator(
prefix="/", starts_with=False
django.core.validators.RegexValidator(
code="invalid",
message="The value must be a relative path.",
regex="^[^/#][^\\s]*",
),
zgw_consumers.models.validators.NonUrlValidator(),
],
Expand Down
11 changes: 6 additions & 5 deletions zgw_consumers/models/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from ..constants import APITypes, AuthTypes, NLXDirectories
from .abstract import RestAPIService
from .validators import NonUrlValidator, PrefixValidator
from .validators import NonUrlValidator, validate_leading_slashes

logger = logging.getLogger(__name__)

Expand All @@ -36,12 +36,13 @@ class Service(RestAPIService):
api_connection_check_path = models.CharField(
_("connection check endpoint"),
help_text=_(
"An optional API endpoint which will be used to check if the API is configured correctly and "
"is currently up or down. This field is only used for the admin's 'Connection check status code' field."
"A relative URL to perform a connection test. If left blank, the API root itself is used. "
"This connection check is only performed in the admin when viewing the service "
"configuration."
),
max_length=255,
validators=[
PrefixValidator(prefix="/", starts_with=False),
validate_leading_slashes,
NonUrlValidator(),
],
blank=True,
Expand Down Expand Up @@ -132,7 +133,7 @@ def clean(self):
)

@property
def connection_check(self) -> bool:
def connection_check(self) -> int | None:
from zgw_consumers.client import build_client

try:
Expand Down
49 changes: 6 additions & 43 deletions zgw_consumers/models/validators.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,14 @@
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.utils.deconstruct import deconstructible
from django.core.validators import RegexValidator, URLValidator
from django.utils.translation import gettext_lazy as _

validate_leading_slashes = RegexValidator(
regex=r"^[^/#][^\s]*",
message="The value must be a relative path.",
code="invalid",
)

@deconstructible
class PrefixValidator:
code = "invalid"

def __init__(
self,
prefix: str,
message: str = None,
code: str = None,
starts_with: bool = True,
):
self.prefix = prefix
self.starts_with = starts_with

if code is not None:
self.code = code

if message is not None:
self.message = message
else:
self.message = _(
"The given value {must_or_cannot} start with '{prefix}'"
).format(
must_or_cannot="must" if self.starts_with else "cannot",
prefix=self.prefix,
)

def __call__(self, value: str) -> bool:
if value.startswith(self.prefix) != self.starts_with:
raise ValidationError(self.message, code=self.code, params={"value": value})

def __eq__(self, other):
return (
isinstance(other, self.__class__)
and self.prefix == other.prefix
and (self.message == other.message)
and (self.code == other.code)
and (self.starts_with == other.starts_with)
)


@deconstructible
class NonUrlValidator(URLValidator):
message = _("Value cannot be a URL")

Expand Down

0 comments on commit 3e30c39

Please sign in to comment.