test: migrate backend suites to pytest with module coverage gate
Split backend tests into unit and integration suites, add isolated fixtures and coverage enforcement, and update CI and test documentation. Cover current authentication, content, notification, payment, media, and admin flows; fix behaviors revealed by the new tests. Validate both suites and the per-module gate against local PostgreSQL.
This commit is contained in:
62
apps/users/tests/integration/test_google_api.py
Normal file
62
apps/users/tests/integration/test_google_api.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import json
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import pytest
|
||||
|
||||
from apps.users.models import UserSocialAccount
|
||||
from apps.users.services import google_oauth as google
|
||||
|
||||
|
||||
pytestmark = [pytest.mark.integration, pytest.mark.django_db]
|
||||
|
||||
|
||||
def _flow_from_redirect(response):
|
||||
return parse_qs(urlparse(response.url).query)["flow"][0]
|
||||
|
||||
|
||||
def test_google_oauth_start_callback_and_flow_lookup(client, settings, mocker):
|
||||
settings.GOOGLE_OAUTH_CLIENT_ID = "client-id"
|
||||
settings.GOOGLE_OAUTH_REDIRECT_URI = "https://backend.test/callback"
|
||||
settings.GOOGLE_OAUTH_FRONTEND_CALLBACK_URL = "https://frontend.test/google"
|
||||
start = client.get("/api/auth/oauth/google/start")
|
||||
assert start.status_code == 302 and "accounts.google.com" in start.url
|
||||
state = parse_qs(urlparse(start.url).query)["state"][0]
|
||||
profile = google.GoogleProfile("google-555", "new@example.com", True, "New", "Person", "")
|
||||
exchange = mocker.patch("apps.users.api.views.exchange_code_for_google_profile", return_value=profile)
|
||||
callback = client.get("/api/auth/oauth/google/callback", {"state": state, "code": "auth-code"})
|
||||
assert callback.status_code == 302
|
||||
exchange.assert_called_once_with("auth-code")
|
||||
flow = _flow_from_redirect(callback)
|
||||
response = client.get("/api/auth/oauth/google/flow", {"flow": flow})
|
||||
assert response.status_code == 200
|
||||
assert response.json()["resolution"] == "new_account"
|
||||
assert client.get("/api/auth/oauth/google/flow", {"flow": "unknown"}).status_code == 400
|
||||
assert client.get("/api/auth/oauth/google/callback", {"state": state, "code": "replay"}).status_code == 302
|
||||
cancelled = client.get("/api/auth/oauth/google/callback", {"error": "access_denied"})
|
||||
assert google.get_google_flow(_flow_from_redirect(cancelled))["status"] == "error"
|
||||
|
||||
|
||||
def test_google_api_complete_claim_and_linked_login(client, settings, mocker, user_factory):
|
||||
settings.GOOGLE_OAUTH_FRONTEND_CALLBACK_URL = "https://frontend.test/google"
|
||||
profile = google.GoogleProfile("google-556", "person@example.com", True, "Google", "Person", "")
|
||||
flow = google.create_google_flow(google.build_pending_google_flow_payload(profile))
|
||||
mocker.patch("apps.users.services.google_oauth.generate_and_send_otp")
|
||||
mocker.patch("apps.users.services.google_oauth.verify_otp_code")
|
||||
complete = client.post("/api/auth/oauth/google/complete", data=json.dumps({"flow": flow, "mobile": "09197770056", "username": "google_api"}),
|
||||
content_type="application/json")
|
||||
assert complete.status_code == 200, complete.content
|
||||
assert complete.json()["status"] == "claim_required"
|
||||
assert client.post("/api/auth/oauth/google/claim/send-otp", data=json.dumps({"flow": flow}),
|
||||
content_type="application/json").status_code == 200
|
||||
verified = client.post("/api/auth/oauth/google/claim/verify", data=json.dumps({"flow": flow, "code": "12345"}),
|
||||
content_type="application/json")
|
||||
assert verified.status_code == 200, verified.content
|
||||
assert verified.json()["status"] == "authenticated"
|
||||
social = UserSocialAccount.objects.get(provider_user_id="google-556")
|
||||
assert social.user.username == "google_api"
|
||||
mocker.patch("apps.users.api.views.consume_google_state")
|
||||
mocker.patch("apps.users.api.views.exchange_code_for_google_profile", return_value=profile)
|
||||
callback = client.get("/api/auth/oauth/google/callback", {"state": "fresh", "code": "code"})
|
||||
assert callback.status_code == 302
|
||||
linked = google.get_google_flow(_flow_from_redirect(callback))
|
||||
assert linked["status"] == "authenticated" and linked["access_token"]
|
||||
Reference in New Issue
Block a user