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.
99 lines
5.2 KiB
Python
99 lines
5.2 KiB
Python
import pytest
|
|
|
|
from apps.users.models import User, UserSocialAccount
|
|
from apps.users.services import google_oauth as google
|
|
|
|
|
|
pytestmark = [pytest.mark.integration, pytest.mark.django_db]
|
|
|
|
|
|
@pytest.fixture
|
|
def google_profile():
|
|
return google.GoogleProfile("provider-123", "google-person@example.com", True, "Google", "Person", "")
|
|
|
|
|
|
def _start(profile, **overrides):
|
|
payload = google.build_pending_google_flow_payload(profile)
|
|
payload.update(overrides)
|
|
return google.create_google_flow(payload)
|
|
|
|
|
|
def test_new_google_account_requires_claim_and_links_after_otp(google_profile, mocker):
|
|
send = mocker.patch("apps.users.services.google_oauth.generate_and_send_otp")
|
|
verify = mocker.patch("apps.users.services.google_oauth.verify_otp_code")
|
|
flow = _start(google_profile)
|
|
pending = google.complete_google_signup(flow=flow, mobile="09197770011", username="google_new")
|
|
assert pending["status"] == "claim_required" and pending["resolution"] == "new_account"
|
|
assert not User.objects.filter(username="google_new").exists()
|
|
send.assert_called_once_with("09197770011", "google_claim")
|
|
assert google.send_google_claim_otp(flow)["message"]
|
|
assert send.call_count == 2
|
|
authenticated = google.verify_google_claim(flow, "12345")
|
|
assert authenticated["status"] == "authenticated" and authenticated["access_token"]
|
|
verify.assert_called_once_with(mobile="09197770011", code="12345", mode="google_claim")
|
|
user = User.objects.get(username="google_new")
|
|
assert user.is_mobile_verified and not user.has_usable_password()
|
|
assert UserSocialAccount.objects.filter(user=user, provider_user_id="provider-123").exists()
|
|
assert google.get_google_flow(flow)["status"] == "authenticated"
|
|
|
|
|
|
def test_existing_email_claim_links_matching_user(google_profile, user_factory, mocker):
|
|
existing = user_factory(email=google_profile.email, is_mobile_verified=False)
|
|
mocker.patch("apps.users.services.google_oauth.generate_and_send_otp")
|
|
mocker.patch("apps.users.services.google_oauth.verify_otp_code")
|
|
flow = _start(google_profile)
|
|
pending = google.complete_google_signup(flow=flow, mobile=existing.mobile)
|
|
assert pending["resolution"] == "existing_email_claim"
|
|
authenticated = google.verify_google_claim(flow, "12345")
|
|
assert authenticated["status"] == "authenticated"
|
|
existing.refresh_from_db()
|
|
assert existing.is_mobile_verified and existing.is_email_verified
|
|
assert UserSocialAccount.objects.get(provider_user_id=google_profile.provider_user_id).user_id == existing.pk
|
|
|
|
|
|
@pytest.mark.parametrize("values,expected_code", [
|
|
({"mobile": "bad", "username": "new"}, "google_flow_error"),
|
|
({"mobile": "09197770022"}, "google_missing_username"),
|
|
({"mobile": "09197770022", "username": "taken"}, "google_username_taken"),
|
|
({"mobile": "09197770022", "username": "new", "student_id": "123"}, "google_invalid_student_id"),
|
|
({"mobile": "09197770022", "username": "new", "major": "missing"}, "google_invalid_major"),
|
|
({"mobile": "09197770022", "username": "new", "university": "missing"}, "google_invalid_university"),
|
|
])
|
|
def test_google_signup_rejects_invalid_profile_data(google_profile, user_factory, mocker, values, expected_code):
|
|
if values.get("username") == "taken":
|
|
user_factory(username="taken")
|
|
send = mocker.patch("apps.users.services.google_oauth.generate_and_send_otp")
|
|
flow = _start(google_profile)
|
|
with pytest.raises(google.GoogleOAuthFlowError) as error:
|
|
google.complete_google_signup(flow=flow, **values)
|
|
assert error.value.code == expected_code
|
|
send.assert_not_called()
|
|
assert not UserSocialAccount.objects.exists()
|
|
|
|
|
|
def test_google_mobile_conflict_is_rejected(google_profile, user_factory, mocker):
|
|
conflict = user_factory(mobile="09197770033", email="other@example.com")
|
|
send = mocker.patch("apps.users.services.google_oauth.generate_and_send_otp")
|
|
flow = _start(google_profile)
|
|
with pytest.raises(google.GoogleOAuthFlowError) as error:
|
|
google.complete_google_signup(flow=flow, mobile=conflict.mobile, username="new_google")
|
|
assert error.value.code == "google_mobile_belongs_to_other_email"
|
|
send.assert_not_called()
|
|
|
|
|
|
def test_google_claim_rejects_missing_target_and_existing_email(google_profile, user_factory, mocker):
|
|
mocker.patch("apps.users.services.google_oauth.verify_otp_code")
|
|
missing = google.create_google_flow({"status": "claim_required", "resolution": "existing_email_claim",
|
|
"target_user_id": 999999, "mobile": "09197770044",
|
|
"google_profile": google._profile_payload(google_profile)})
|
|
with pytest.raises(google.GoogleOAuthFlowError) as error:
|
|
google.verify_google_claim(missing, "12345")
|
|
assert error.value.code == "google_email_claim_failed"
|
|
user_factory(email=google_profile.email)
|
|
new_flow = google.create_google_flow({"status": "claim_required", "resolution": "new_account",
|
|
"username": "fresh", "mobile": "09197770045",
|
|
"google_profile": google._profile_payload(google_profile)})
|
|
with pytest.raises(google.GoogleOAuthFlowError) as error:
|
|
google.verify_google_claim(new_flow, "12345")
|
|
assert error.value.code == "google_email_taken"
|