From 432614e2cc03634eebed4efa71540199a8d6bcb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20W=C3=B3jcik?= Date: Sat, 1 Aug 2026 21:38:20 +0200 Subject: [PATCH 1/2] test: pin 2.x OPTIONS/HEAD and unmapped-method behavior Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011uYLkj9YnPJ1sNE2Juek2m --- .../tests/test_urls/test_method_parity.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 testproject/testapp/tests/test_urls/test_method_parity.py diff --git a/testproject/testapp/tests/test_urls/test_method_parity.py b/testproject/testapp/tests/test_urls/test_method_parity.py new file mode 100644 index 00000000..1667b0c7 --- /dev/null +++ b/testproject/testapp/tests/test_urls/test_method_parity.py @@ -0,0 +1,67 @@ +""" +Pins djoser 2.x method-handling behavior so the 3.0 view split can prove parity. + +Every test in this module must pass on master unchanged. If an assertion below +disagrees with master's actual behavior, fix the TEST to pin what master does — +never the other way around. +""" + +import pytest +from django.contrib.auth import get_user_model +from rest_framework.test import APIClient + +User = get_user_model() + + +@pytest.fixture +def parity_user(db): + return User.objects.create_user( + username="parity", email="parity@example.com", password="secret123!" + ) + + +@pytest.fixture +def auth_client(parity_user): + client = APIClient() + client.force_authenticate(user=parity_user) + return client + + +@pytest.mark.django_db +@pytest.mark.parametrize( + "path", + ["/auth/users/", "/auth/users/activation/", "/auth/users/reset_password/"], +) +def test_options_returns_200_for_authenticated_clients(auth_client, path): + assert auth_client.options(path).status_code == 200 + + +@pytest.mark.django_db +def test_options_on_user_detail_returns_200(auth_client, parity_user): + assert auth_client.options(f"/auth/users/{parity_user.pk}/").status_code == 200 + + +@pytest.mark.django_db +def test_head_is_supported_wherever_get_is(auth_client, parity_user): + assert auth_client.head("/auth/users/").status_code == 200 + assert auth_client.head(f"/auth/users/{parity_user.pk}/").status_code == 200 + + +@pytest.mark.django_db +def test_options_runs_authentication_first(db): + # DRF runs authentication/permissions before the options handler, + # so anonymous clients get 401, not metadata. + assert APIClient().options("/auth/users/").status_code == 401 + + +@pytest.mark.django_db +def test_unmapped_method_gets_drf_json_405_after_auth(auth_client): + response = auth_client.put("/auth/users/", {}, format="json") + assert response.status_code == 405 + assert response["Content-Type"].startswith("application/json") + assert "detail" in response.data + + +@pytest.mark.django_db +def test_unmapped_method_gets_401_for_anonymous_clients(db): + assert APIClient().put("/auth/users/", {}, format="json").status_code == 401 From e3e12c096aa9e9c2d065a2b03f0a2c64fde73727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20W=C3=B3jcik?= Date: Sat, 1 Aug 2026 21:46:35 +0200 Subject: [PATCH 2/2] test: pin 2.x me-403, api-root, and slash-less URL behavior Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011uYLkj9YnPJ1sNE2Juek2m --- .../tests/test_urls/test_method_parity.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/testproject/testapp/tests/test_urls/test_method_parity.py b/testproject/testapp/tests/test_urls/test_method_parity.py index 1667b0c7..0b781178 100644 --- a/testproject/testapp/tests/test_urls/test_method_parity.py +++ b/testproject/testapp/tests/test_urls/test_method_parity.py @@ -65,3 +65,55 @@ def test_unmapped_method_gets_drf_json_405_after_auth(auth_client): @pytest.mark.django_db def test_unmapped_method_gets_401_for_anonymous_clients(db): assert APIClient().put("/auth/users/", {}, format="json").status_code == 401 + + +@pytest.mark.django_db +def test_me_returns_200_for_authenticated_user(parity_user): + """ + The 2.x `me` action allows any authenticated user to fetch their own profile. + Unlike user detail/list, it doesn't check DJOSER permission overrides; + authentication alone is sufficient. + """ + client = APIClient() + client.force_authenticate(user=parity_user) + response = client.get("/auth/users/me/") + assert response.status_code == 200 + assert response.data["username"] == "parity" + + +@pytest.mark.django_db +def test_api_root_lists_users_endpoint(auth_client): + """DefaultRouter's api-root at the include prefix: GET /auth/ is a live endpoint.""" + response = auth_client.get("/auth/") + assert response.status_code == 200 + assert "users" in response.data + + +@pytest.mark.django_db +def test_slashless_token_login_serves_request_directly(parity_user): + """2.x matched token URLs with an optional trailing slash — no redirect.""" + response = APIClient().post( + "/auth/token/login", + {"username": "parity", "password": "secret123!"}, + format="json", + ) + assert response.status_code == 200 + assert "auth_token" in response.data + + +@pytest.mark.django_db +@pytest.mark.parametrize( + "path", + [ + "/auth/jwt/create", + "/auth/jwt/create/", + "/auth/jwt/create/extra", + "/auth/jwt/createx", + ], +) +def test_jwt_create_matches_with_and_without_anchor(parity_user, path): + """2.x jwt regexes were unanchored — all of these resolve to the same view.""" + response = APIClient().post( + path, {"username": "parity", "password": "secret123!"}, format="json" + ) + assert response.status_code == 200