feat: Update test configurations and add smoke tests for named view routes
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client
|
||||
from django.urls import URLPattern, URLResolver, get_resolver, reverse
|
||||
|
||||
|
||||
SAFE_STATUS_CODES = {
|
||||
200,
|
||||
201,
|
||||
202,
|
||||
204,
|
||||
301,
|
||||
302,
|
||||
303,
|
||||
304,
|
||||
307,
|
||||
308,
|
||||
400,
|
||||
401,
|
||||
403,
|
||||
404,
|
||||
405,
|
||||
}
|
||||
|
||||
SKIP_NAMES = {
|
||||
"admin:index", # admin app has its own dedicated tests and redirects heavily.
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class NamedRoute:
|
||||
name: str
|
||||
kwargs: dict[str, object]
|
||||
|
||||
|
||||
def _default_for_converter(converter_name: str) -> object:
|
||||
if converter_name == "int":
|
||||
return 1
|
||||
if converter_name == "str":
|
||||
return "smoke"
|
||||
if converter_name == "slug":
|
||||
return "smoke"
|
||||
if converter_name == "uuid":
|
||||
return UUID("00000000-0000-0000-0000-000000000001")
|
||||
if converter_name == "path":
|
||||
return "smoke/path"
|
||||
return "smoke"
|
||||
|
||||
|
||||
def _iter_named_patterns(
|
||||
patterns: Iterable[URLPattern | URLResolver],
|
||||
namespace: str = "",
|
||||
) -> Iterable[NamedRoute]:
|
||||
for pattern in patterns:
|
||||
if isinstance(pattern, URLResolver):
|
||||
nested_namespace = namespace
|
||||
if pattern.namespace:
|
||||
nested_namespace = f"{namespace}:{pattern.namespace}" if namespace else pattern.namespace
|
||||
yield from _iter_named_patterns(pattern.url_patterns, nested_namespace)
|
||||
continue
|
||||
|
||||
if not pattern.name:
|
||||
continue
|
||||
|
||||
full_name = f"{namespace}:{pattern.name}" if namespace else pattern.name
|
||||
converters = getattr(pattern.pattern, "converters", {})
|
||||
kwargs = {key: _default_for_converter(converter.__class__.__name__.replace("Converter", "").lower()) for key, converter in converters.items()}
|
||||
yield NamedRoute(name=full_name, kwargs=kwargs)
|
||||
|
||||
|
||||
def _all_named_routes() -> list[NamedRoute]:
|
||||
resolver = get_resolver()
|
||||
routes = list(_iter_named_patterns(resolver.url_patterns))
|
||||
|
||||
unique_routes: dict[tuple[str, tuple[tuple[str, object], ...]], NamedRoute] = {}
|
||||
for route in routes:
|
||||
key = (route.name, tuple(sorted(route.kwargs.items())))
|
||||
unique_routes[key] = route
|
||||
|
||||
return sorted(unique_routes.values(), key=lambda route: (route.name, sorted(route.kwargs)))
|
||||
|
||||
|
||||
ALL_NAMED_ROUTES = _all_named_routes()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def smoke_client(db) -> Client:
|
||||
user_model = get_user_model()
|
||||
user = user_model.objects.create_user(
|
||||
username="view_smoke_user",
|
||||
password="testpassword",
|
||||
email="view_smoke_user@example.com",
|
||||
)
|
||||
client = Client()
|
||||
client.raise_request_exception = False
|
||||
client.force_login(user)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("route", ALL_NAMED_ROUTES, ids=lambda route: route.name)
|
||||
def test_named_view_routes_do_not_return_server_errors(smoke_client: Client, route: NamedRoute):
|
||||
if route.name in SKIP_NAMES:
|
||||
pytest.skip("Route is intentionally excluded from this smoke suite.")
|
||||
|
||||
try:
|
||||
url = reverse(route.name, kwargs=route.kwargs or None)
|
||||
except Exception as exc: # pragma: no cover
|
||||
pytest.skip(f"Unable to reverse route {route.name}: {exc}")
|
||||
|
||||
response = smoke_client.get(url, follow=False)
|
||||
|
||||
assert response.status_code in SAFE_STATUS_CODES, (
|
||||
f"Unexpected status for {route.name} ({url}): {response.status_code}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("route", ALL_NAMED_ROUTES, ids=lambda route: route.name)
|
||||
def test_named_view_routes_anonymous_do_not_return_server_errors(route: NamedRoute):
|
||||
if route.name in SKIP_NAMES:
|
||||
pytest.skip("Route is intentionally excluded from this smoke suite.")
|
||||
|
||||
try:
|
||||
url = reverse(route.name, kwargs=route.kwargs or None)
|
||||
except Exception as exc: # pragma: no cover
|
||||
pytest.skip(f"Unable to reverse route {route.name}: {exc}")
|
||||
|
||||
client = Client()
|
||||
client.raise_request_exception = False
|
||||
response = client.get(url, follow=False)
|
||||
|
||||
assert response.status_code in SAFE_STATUS_CODES, (
|
||||
f"Unexpected anonymous status for {route.name} ({url}): {response.status_code}"
|
||||
)
|
||||
Reference in New Issue
Block a user