test: add invite code validation tests and fix migration rollback

- Add TestInviteCodeSignup integration tests for valid/invalid invite codes
- Fix migration downgrade: avoid dropping trigger dependency
- Add DB CHECK constraint for invite_codes.code format
- Update runtime-route.md with invite_code documentation
- Update runtime-runbook.md with change log
This commit is contained in:
qzl
2026-02-28 10:56:09 +08:00
parent 3d6ae7695f
commit dbd3f68dd4
4 changed files with 103 additions and 3 deletions
@@ -20,7 +20,7 @@ def upgrade() -> None:
"""
CREATE TABLE invite_codes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(8) NOT NULL UNIQUE,
code VARCHAR(8) NOT NULL UNIQUE CHECK (code ~ '^[ABCDEFGHJKMNPQRSTUVWXYZ23456789]{8}$'),
owner_id UUID REFERENCES profiles(id) ON DELETE SET NULL,
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled', 'expired')),
used_count INTEGER NOT NULL DEFAULT 0 CHECK (used_count >= 0),
@@ -153,7 +153,6 @@ def upgrade() -> None:
def downgrade() -> None:
op.execute("DROP FUNCTION IF EXISTS public.create_profile_for_new_user()")
op.execute(
"""
CREATE OR REPLACE FUNCTION public.create_profile_for_new_user()
@@ -792,3 +792,96 @@ def test_password_reset_confirm_weak_password_returns_422() -> None:
assert response.headers["content-type"].startswith("application/problem+json")
finally:
app.dependency_overrides = {}
class TestInviteCodeSignup:
def test_signup_with_valid_invite_code_returns_202(self) -> None:
user = AuthUser(id="user-1", email="user@example.com")
token_response = SessionResponse(
access_token="access",
refresh_token="refresh",
expires_in=3600,
token_type="bearer",
user=user,
)
app.dependency_overrides[get_auth_service] = _override_auth_service(
FakeAuthService(token_response)
)
client = TestClient(app)
try:
response = client.post(
"/api/v1/auth/verifications",
json={
"username": "demo",
"email": "user@example.com",
"password": "secret123",
"invite_code": "A2B3C4D5",
},
)
assert response.status_code == 202
assert response.json() == {"email": "user@example.com"}
finally:
app.dependency_overrides = {}
def test_signup_with_invalid_invite_code_length_returns_422(self) -> None:
user = AuthUser(id="user-1", email="user@example.com")
token_response = SessionResponse(
access_token="access",
refresh_token="refresh",
expires_in=3600,
token_type="bearer",
user=user,
)
app.dependency_overrides[get_auth_service] = _override_auth_service(
FakeAuthService(token_response)
)
client = TestClient(app)
try:
response = client.post(
"/api/v1/auth/verifications",
json={
"username": "demo",
"email": "user@example.com",
"password": "secret123",
"invite_code": "ABC123",
},
)
assert response.status_code == 422
assert response.headers["content-type"].startswith(
"application/problem+json"
)
finally:
app.dependency_overrides = {}
def test_signup_with_invalid_invite_code_chars_returns_422(self) -> None:
user = AuthUser(id="user-1", email="user@example.com")
token_response = SessionResponse(
access_token="access",
refresh_token="refresh",
expires_in=3600,
token_type="bearer",
user=user,
)
app.dependency_overrides[get_auth_service] = _override_auth_service(
FakeAuthService(token_response)
)
client = TestClient(app)
try:
response = client.post(
"/api/v1/auth/verifications",
json={
"username": "demo",
"email": "user@example.com",
"password": "secret123",
"invite_code": "ABCD1234",
},
)
assert response.status_code == 422
assert response.headers["content-type"].startswith(
"application/problem+json"
)
finally:
app.dependency_overrides = {}
+8 -1
View File
@@ -20,7 +20,8 @@
"username": "string (3-30 chars)",
"email": "string (email)",
"password": "string (min 6 chars)",
"redirect_to": "string? (optional)"
"redirect_to": "string? (optional)",
"invite_code": "string? (8 chars, 排除易混淆字符 0/1/I/L/O)"
}
```
@@ -31,6 +32,12 @@
}
```
**邀请码说明:**
- 可选字段,不填则注册不受影响
- 格式:8 位字母数字组合,排除易混淆字符 (0, 1, I, L, O)
- 注册时传入有效邀请码会建立邀请关系并增加邀请码使用次数
- 无效邀请码(不存在/已禁用/已过期/已达上限)不会阻断注册成功
**Errors:**
- 422: 请求参数无效
- 429: 请求过于频繁
+1
View File
@@ -245,3 +245,4 @@ docker compose --env-file .env -f infra/docker/docker-compose.yml up -d --force-
| 2026-02-25 | 重构为运维分层手册:Bootstrap Gate、分层验证、故障与回滚流程 |
| 2026-02-25 | 新增配置漂移故障条目:修复 Auth 邮件模板失效与 signup 超时场景 |
| 2026-02-27 | 用户搜索支持邮箱精确匹配:query 含 @ 符号时走 auth.users → profiles 两步查询 |
| 2026-02-28 | 邀请码功能:新增 invite_codes 表、profiles.referred_by,注册时可选填邀请码并记录邀请关系 |