feat(demo): add auth flow UI service

This commit is contained in:
2026-07-14 10:12:22 +03:30
parent e382c4f93a
commit d43eb54e2d
16 changed files with 895 additions and 10 deletions

View File

@@ -0,0 +1,20 @@
from gapido_auth.infrastructure.debug_sms_client import DebugSmsClient, debug_sms_key
class FakeDebugStore:
def __init__(self) -> None:
self.values: dict[str, tuple[int, str]] = {}
async def setex(self, name: str, time: int, value: str) -> object:
self.values[name] = (time, value)
return True
async def test_debug_sms_client_stores_latest_otp() -> None:
store = FakeDebugStore()
client = DebugSmsClient(store, ttl_seconds=300)
await client.send_otp("989120000000", "123456", "debug-otp")
assert store.values[debug_sms_key("989120000000")] == (300, "123456")

132
tests/test_demo_app.py Normal file
View File

@@ -0,0 +1,132 @@
from dataclasses import dataclass
from fastapi.testclient import TestClient
from gapido_auth.config import Settings
from gapido_auth.demo.app import app, get_app_settings, get_auth_client, get_debug_redis
from gapido_auth.demo.grpc_client import DemoTokenResponse
@dataclass(slots=True)
class FakeAuthClient:
last_user_token: str | None = None
last_admin_token: str | None = None
async def request_otp(self, mobile: str, purpose: str) -> dict[str, bool]:
assert mobile == "989120000000"
assert purpose == "login"
return {"accepted": True}
async def verify_otp(self, mobile: str, code: str, purpose: str) -> DemoTokenResponse:
assert mobile == "989120000000"
assert code == "123456"
assert purpose == "login"
return DemoTokenResponse(
"access-token-value-123", "refresh-token-value-123", "Bearer", 900, "user"
)
async def refresh_token(self, refresh_token: str) -> DemoTokenResponse:
assert refresh_token == "refresh-token-value-123"
return DemoTokenResponse(
"new-access-token-value", "new-refresh-token-value", "Bearer", 900, "user"
)
async def revoke_refresh_token(self, access_token: str, refresh_token: str) -> dict[str, bool]:
assert access_token == "access-token-value-123"
assert refresh_token == "refresh-token-value-123"
return {"revoked": True}
async def public_ping(self) -> dict[str, str]:
return {"message": "public ok"}
async def user_only(self, access_token: str) -> dict[str, str]:
self.last_user_token = access_token
return {"user_id": "1", "role": "user", "message": "authenticated user ok"}
async def admin_only(self, access_token: str) -> dict[str, str]:
self.last_admin_token = access_token
return {"user_id": "2", "role": "admin", "message": "admin ok"}
class FakeDebugRedis:
async def get(self, key: str) -> str | None:
assert key == "debug:sms:last:989120000000"
return "123456"
def test_demo_auth_flow_forwards_to_grpc_client() -> None:
fake_client = FakeAuthClient()
app.dependency_overrides[get_auth_client] = lambda: fake_client
client = TestClient(app)
try:
request = client.post(
"/api/auth/request-otp", json={"mobile": "989120000000", "purpose": "login"}
)
assert request.status_code == 200
assert request.json() == {"accepted": True}
verify = client.post(
"/api/auth/verify-otp",
json={"mobile": "989120000000", "code": "123456", "purpose": "login"},
)
assert verify.status_code == 200
assert verify.json()["access_token"] == "access-token-value-123"
public = client.post("/api/demo/public")
assert public.status_code == 200
assert public.json() == {"message": "public ok"}
user = client.post("/api/demo/user", json={"access_token": "access-token-value-123"})
assert user.status_code == 200
assert fake_client.last_user_token == "access-token-value-123"
admin = client.post("/api/demo/admin", json={"access_token": "access-token-value-123"})
assert admin.status_code == 200
assert fake_client.last_admin_token == "access-token-value-123"
refresh = client.post(
"/api/auth/refresh", json={"refresh_token": "refresh-token-value-123"}
)
assert refresh.status_code == 200
assert refresh.json()["refresh_token"] == "new-refresh-token-value"
revoke = client.post(
"/api/auth/revoke",
json={
"access_token": "access-token-value-123",
"refresh_token": "refresh-token-value-123",
},
)
assert revoke.status_code == 200
assert revoke.json() == {"revoked": True}
finally:
client.close()
app.dependency_overrides.clear()
def test_debug_otp_endpoint_reads_redis_when_enabled() -> None:
app.dependency_overrides[get_app_settings] = lambda: Settings(demo_enable_debug_otp=True)
app.dependency_overrides[get_debug_redis] = lambda: FakeDebugRedis()
client = TestClient(app)
try:
response = client.get("/api/debug/otp?mobile=989120000000")
assert response.status_code == 200
assert response.json() == {"mobile": "989120000000", "code": "123456"}
finally:
client.close()
app.dependency_overrides.clear()
def test_debug_otp_endpoint_is_disabled() -> None:
app.dependency_overrides[get_app_settings] = lambda: Settings(demo_enable_debug_otp=False)
app.dependency_overrides[get_debug_redis] = lambda: None
client = TestClient(app)
try:
response = client.get("/api/debug/otp?mobile=989120000000")
assert response.status_code == 404
finally:
client.close()
app.dependency_overrides.clear()