docs: record redesign review artifacts

This commit is contained in:
2026-09-28 20:29:02 +03:30
parent 6f7a30bc0d
commit 37189a4ac8
11 changed files with 380 additions and 4 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@@ -0,0 +1,74 @@
"""Create disposable Bundle 0 browser fixtures in the isolated Django test database.
Run only with --settings=config.settings.test and BUNDLE0_TEST_PASSWORD set.
Never point TEST_DB_NAME at a shared or production database.
"""
import os
from datetime import timedelta
from django.conf import settings
from django.utils import timezone
from apps.blog.models import Post
from apps.events.models import Event
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"]
now = timezone.now()
def fixture_user(username, mobile, *, staff=False):
user, _ = User.objects.get_or_create(
username=username,
defaults={"mobile": mobile, "email": f"{username}@example.invalid"},
)
user.mobile = mobile
user.email = f"{username}@example.invalid"
user.is_email_verified = True
user.is_mobile_verified = True
user.is_staff = staff
user.is_superuser = staff
user.set_password(password)
user.save()
return user
student = fixture_user("bundle0_student", "09198000111")
staff = fixture_user("bundle0_staff", "09198000112", staff=True)
for slug, title, price, capacity in (
("bundle0-free", "Bundle 0 Free Event", 0, 20),
("bundle0-full", "Bundle 0 Full Event", 0, 0),
("bundle0-paid", "Bundle 0 Paid Event", 50000, 20),
):
Event.objects.update_or_create(
slug=slug,
defaults={
"title": title,
"description": "Local baseline event for browser review.",
"start_time": now + timedelta(days=7),
"end_time": now + timedelta(days=7, hours=2),
"registration_start_date": now - timedelta(days=1),
"registration_end_date": now + timedelta(days=6),
"status": Event.StatusChoices.PUBLISHED,
"price": price,
"capacity": capacity,
},
)
Post.objects.update_or_create(
slug="bundle0-article",
defaults={
"title": "Bundle 0 Article",
"content": "# Local baseline article\n\nA sample article for the browser review.",
"author": staff,
"status": Post.StatusChoices.PUBLISHED,
"published_at": now,
},
)
print("Created isolated student, staff, free/full/paid events, and article fixtures.")

View File

@@ -0,0 +1,95 @@
"""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.")

View File

@@ -0,0 +1,85 @@
"""Replay paid-event outcomes in the isolated Bundle 0 Django test database.
External gateway calls, notifications, and SMS are mocked. This script is baseline
evidence, not a production payment test or a browser gateway substitute.
"""
import json
import os
from unittest.mock import Mock, patch
from django.conf import settings
from django.test import Client, override_settings
from apps.events.models import Event, Registration
from apps.payments.models import Payment
from apps.users.models import User
from core.authentication import create_jwt_token
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.test"
assert settings.DATABASES["default"]["ENGINE"] == "django.db.backends.sqlite3"
student = User.objects.get(username="bundle0_student")
event = Event.objects.get(slug="bundle0-paid")
client = Client()
headers = {"HTTP_AUTHORIZATION": f"Bearer {create_jwt_token(student)}"}
body = json.dumps({"event_id": event.pk, "description": "Local mocked payment replay"})
def create_payment():
return client.post(
"/api/payments/create",
data=body,
content_type="application/json",
**headers,
)
with (
override_settings(ALLOWED_HOSTS=["testserver", "localhost"]),
patch("apps.payments.api.views.notify_user"),
patch("apps.payments.api.views.send_critical_sms.delay"),
patch("apps.payments.api.views.requests.post") as gateway,
):
gateway.side_effect = RuntimeError("simulated gateway outage")
failed_request = create_payment()
assert failed_request.status_code == 502, failed_request.content
assert not Payment.objects.filter(user=student, event=event).exists()
gateway.side_effect = None
gateway.return_value = Mock()
gateway.return_value.json.return_value = {
"data": {"code": 100, "authority": "BUNDLE0-CANCEL"}
}
pending = create_payment()
assert pending.status_code == 200, pending.content
assert pending.json()["start_pay_url"]
canceled = client.get(
"/api/payments/callback",
{"Authority": "BUNDLE0-CANCEL", "Status": "NOK"},
)
assert canceled.status_code == 302
assert "status=failed" in canceled.url
assert Payment.objects.get(authority="BUNDLE0-CANCEL").status == Payment.OrderStatusChoices.CANCELED
gateway.return_value.json.return_value = {
"data": {"code": 100, "authority": "BUNDLE0-SUCCESS"}
}
pending = create_payment()
assert pending.status_code == 200, pending.content
gateway.return_value.json.return_value = {
"data": {"code": 100, "ref_id": "BUNDLE0-REF"}
}
paid = client.get(
"/api/payments/callback",
{"Authority": "BUNDLE0-SUCCESS", "Status": "OK"},
)
assert paid.status_code == 302
assert "status=success" in paid.url
assert Payment.objects.get(authority="BUNDLE0-SUCCESS").status == Payment.OrderStatusChoices.PAID
assert Registration.objects.get(user=student, event=event).status == Registration.StatusChoices.CONFIRMED
duplicate = create_payment()
assert duplicate.status_code == 400
print("Paid replay: gateway outage 502; cancellation redirect; mocked success and confirmed registration; duplicate blocked.")