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.
63 lines
3.6 KiB
Python
63 lines
3.6 KiB
Python
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"]
|