96 lines
3.6 KiB
Python
96 lines
3.6 KiB
Python
"""Replay signup, OTP login, and password reset in the isolated test database.
|
|
|
|
SMS and the short-lived OTP store are mocked. Run after the fixture seed with
|
|
--settings=config.settings.test and BUNDLE0_TEST_PASSWORD set.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from unittest.mock import patch
|
|
|
|
from django.conf import settings
|
|
from django.test import Client, override_settings
|
|
|
|
from apps.users.models import User
|
|
|
|
|
|
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.test"
|
|
assert settings.DATABASES["default"]["ENGINE"] == "django.db.backends.sqlite3"
|
|
password = os.environ["BUNDLE0_TEST_PASSWORD"]
|
|
mobile = "09198000113"
|
|
client = Client()
|
|
|
|
|
|
class TemporaryOtpStore:
|
|
def __init__(self):
|
|
self.values = {}
|
|
|
|
def setex(self, key, _seconds, value):
|
|
self.values[key] = str(value).encode("utf-8")
|
|
|
|
def get(self, key):
|
|
return self.values.get(key)
|
|
|
|
def delete(self, key):
|
|
self.values.pop(key, None)
|
|
|
|
|
|
store = TemporaryOtpStore()
|
|
|
|
|
|
def post(path, payload):
|
|
return client.post(path, data=json.dumps(payload), content_type="application/json")
|
|
|
|
|
|
with (
|
|
override_settings(ALLOWED_HOSTS=["testserver", "localhost"]),
|
|
patch("apps.users.services.auth._redis", return_value=store),
|
|
patch("apps.users.services.auth.send_critical_sms.delay"),
|
|
):
|
|
invalid = post("/api/auth/otp/send", {"mobile": "123", "mode": "register"})
|
|
assert invalid.status_code == 400
|
|
|
|
sent = post("/api/auth/otp/send", {"mobile": mobile, "mode": "register"})
|
|
assert sent.status_code == 200, sent.content
|
|
code = store.get(f"auth_otp:register:{mobile}").decode("utf-8")
|
|
wrong = "00000" if code != "00000" else "99999"
|
|
rejected = post("/api/auth/otp/verify-register", {"mobile": mobile, "code": wrong})
|
|
assert rejected.status_code == 400
|
|
verified = post("/api/auth/otp/verify-register", {"mobile": mobile, "code": code})
|
|
assert verified.status_code == 200, verified.content
|
|
registered = post(
|
|
"/api/auth/register",
|
|
{"username": "bundle0_signup", "mobile": mobile, "code": code, "password": password},
|
|
)
|
|
assert registered.status_code == 201, registered.content
|
|
assert User.objects.get(username="bundle0_signup").is_mobile_verified
|
|
|
|
duplicate = post("/api/auth/otp/send", {"mobile": mobile, "mode": "register"})
|
|
assert duplicate.status_code == 400
|
|
|
|
sent = post("/api/auth/otp/send", {"mobile": mobile, "mode": "login"})
|
|
assert sent.status_code == 200, sent.content
|
|
code = store.get(f"auth_otp:login:{mobile}").decode("utf-8")
|
|
login = post("/api/auth/login/otp", {"mobile": mobile, "code": code})
|
|
assert login.status_code == 200 and login.json()["access_token"]
|
|
|
|
sent = post("/api/auth/otp/send", {"mobile": mobile, "mode": "reset_password"})
|
|
assert sent.status_code == 200, sent.content
|
|
code = store.get(f"auth_otp:reset_password:{mobile}").decode("utf-8")
|
|
new_password = password + "New"
|
|
rejected = post(
|
|
"/api/auth/reset-password",
|
|
{"mobile": mobile, "code": wrong if wrong != code else "88888", "new_password": new_password},
|
|
)
|
|
assert rejected.status_code == 400
|
|
changed = post(
|
|
"/api/auth/reset-password",
|
|
{"mobile": mobile, "code": code, "new_password": new_password},
|
|
)
|
|
assert changed.status_code == 200, changed.content
|
|
old_login = post("/api/auth/login", {"identifier": mobile, "password": password})
|
|
new_login = post("/api/auth/login", {"identifier": mobile, "password": new_password})
|
|
assert old_login.status_code == 401 and new_login.status_code == 200
|
|
|
|
print("Auth replay: invalid OTP request and code rejected; signup, OTP login, and password reset succeeded; old password rejected.")
|