eb4ad569be
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Failing after 0s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 0s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 0s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 30s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 34s
CI/CD Pipeline / Frontend Lint (push) Successful in 54s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m31s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 16s
CI/CD Pipeline / Integration Tests (push) Failing after 2m33s
CI/CD Pipeline / Unit Tests (push) Successful in 2m37s
283 lines
8.4 KiB
Python
Executable File
283 lines
8.4 KiB
Python
Executable File
"""
|
|
验证工具与schema守卫单元测试
|
|
|
|
覆盖:
|
|
- validate_phone / normalize_phone / validate_email
|
|
- schema_guard: normalize_environment / assert_auto_create_schema_allowed
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from packages.adapters.sqlalchemy_impl.schema_guard import (
|
|
BLOCKED_AUTO_CREATE_ENVIRONMENTS,
|
|
assert_auto_create_schema_allowed,
|
|
normalize_environment,
|
|
)
|
|
from packages.application.auth.verification_code_service import (
|
|
normalize_phone,
|
|
validate_email,
|
|
validate_phone,
|
|
)
|
|
|
|
# ============================================================
|
|
# validate_phone
|
|
# ============================================================
|
|
|
|
|
|
class TestValidatePhone:
|
|
"""validate_phone 手机号校验"""
|
|
|
|
def test_valid_11digit_mainland(self):
|
|
ok, msg = validate_phone("13812345678")
|
|
assert ok is True
|
|
assert msg == ""
|
|
|
|
def test_valid_with_plus86_prefix(self):
|
|
ok, msg = validate_phone("+8613812345678")
|
|
assert ok is True
|
|
assert msg == ""
|
|
|
|
def test_valid_13x_prefix(self):
|
|
ok, _ = validate_phone("13012345678")
|
|
assert ok is True
|
|
|
|
def test_valid_19x_prefix(self):
|
|
ok, _ = validate_phone("19912345678")
|
|
assert ok is True
|
|
|
|
def test_valid_17x_prefix(self):
|
|
ok, _ = validate_phone("17612345678")
|
|
assert ok is True
|
|
|
|
def test_empty_returns_false(self):
|
|
ok, msg = validate_phone("")
|
|
assert ok is False
|
|
assert "不能为空" in msg
|
|
|
|
def test_whitespace_stripped_empty(self):
|
|
ok, msg = validate_phone(" ")
|
|
assert ok is False
|
|
assert "不能为空" in msg
|
|
|
|
def test_too_short_returns_false(self):
|
|
ok, msg = validate_phone("138123456") # 9位
|
|
assert ok is False
|
|
assert "格式不正确" in msg
|
|
|
|
def test_too_long_returns_false(self):
|
|
ok, msg = validate_phone("138123456789") # 12位
|
|
assert ok is False
|
|
assert "格式不正确" in msg
|
|
|
|
def test_invalid_prefix_12x(self):
|
|
ok, _ = validate_phone("12312345678")
|
|
assert ok is False
|
|
|
|
def test_invalid_prefix_11x(self):
|
|
ok, _ = validate_phone("11012345678")
|
|
assert ok is False
|
|
|
|
def test_contains_letters(self):
|
|
ok, _ = validate_phone("138abc45678")
|
|
assert ok is False
|
|
|
|
def test_contains_spaces_in_middle(self):
|
|
ok, _ = validate_phone("138 1234 5678")
|
|
assert ok is False
|
|
|
|
def test_whitespace_stripped_valid(self):
|
|
ok, _ = validate_phone(" 13812345678 ")
|
|
assert ok is True
|
|
|
|
def test_plus86_with_spaces(self):
|
|
ok, _ = validate_phone(" +8613812345678 ")
|
|
assert ok is True
|
|
|
|
|
|
# ============================================================
|
|
# normalize_phone
|
|
# ============================================================
|
|
|
|
|
|
class TestNormalizePhone:
|
|
"""normalize_phone 手机号标准化"""
|
|
|
|
def test_plain_number_unchanged(self):
|
|
assert normalize_phone("13812345678") == "13812345678"
|
|
|
|
def test_plus86_prefix_removed(self):
|
|
assert normalize_phone("+8613812345678") == "13812345678"
|
|
|
|
def test_whitespace_stripped(self):
|
|
assert normalize_phone(" 13812345678 ") == "13812345678"
|
|
|
|
def test_plus86_with_spaces(self):
|
|
assert normalize_phone(" +8613900001111 ") == "13900001111"
|
|
|
|
def test_empty_string(self):
|
|
assert normalize_phone("") == ""
|
|
|
|
def test_only_plus86(self):
|
|
assert normalize_phone("+86") == ""
|
|
|
|
|
|
# ============================================================
|
|
# validate_email
|
|
# ============================================================
|
|
|
|
|
|
class TestValidateEmail:
|
|
"""validate_email 邮箱校验"""
|
|
|
|
def test_valid_simple(self):
|
|
ok, msg = validate_email("user@example.com")
|
|
assert ok is True
|
|
assert msg == ""
|
|
|
|
def test_valid_with_subdomain(self):
|
|
ok, _ = validate_email("user@mail.example.com")
|
|
assert ok is True
|
|
|
|
def test_valid_with_plus_suffix(self):
|
|
ok, _ = validate_email("user+tag@example.com")
|
|
assert ok is True
|
|
|
|
def test_valid_with_dots_in_local(self):
|
|
ok, _ = validate_email("user.name@example.com")
|
|
assert ok is True
|
|
|
|
def test_valid_with_underscore(self):
|
|
ok, _ = validate_email("user_name@example.com")
|
|
assert ok is True
|
|
|
|
def test_valid_with_hyphen(self):
|
|
ok, _ = validate_email("user-name@example.com")
|
|
assert ok is True
|
|
|
|
def test_valid_uppercase(self):
|
|
ok, _ = validate_email("User.Name@Example.COM")
|
|
assert ok is True
|
|
|
|
def test_empty_returns_false(self):
|
|
ok, msg = validate_email("")
|
|
assert ok is False
|
|
assert "不能为空" in msg
|
|
|
|
def test_whitespace_only_returns_false(self):
|
|
ok, msg = validate_email(" ")
|
|
assert ok is False
|
|
assert "不能为空" in msg
|
|
|
|
def test_no_at_sign(self):
|
|
ok, _ = validate_email("userexample.com")
|
|
assert ok is False
|
|
|
|
def test_no_domain(self):
|
|
ok, _ = validate_email("user@")
|
|
assert ok is False
|
|
|
|
def test_no_local_part(self):
|
|
ok, _ = validate_email("@example.com")
|
|
assert ok is False
|
|
|
|
def test_no_dot_in_domain(self):
|
|
ok, _ = validate_email("user@example")
|
|
assert ok is False
|
|
|
|
def test_single_letter_tld(self):
|
|
ok, _ = validate_email("user@example.c")
|
|
assert ok is False
|
|
|
|
def test_whitespace_stripped(self):
|
|
ok, _ = validate_email(" user@example.com ")
|
|
assert ok is True
|
|
|
|
def test_special_chars_percent(self):
|
|
ok, _ = validate_email("user%name@example.com")
|
|
assert ok is True
|
|
|
|
|
|
# ============================================================
|
|
# normalize_environment
|
|
# ============================================================
|
|
|
|
|
|
class TestNormalizeEnvironment:
|
|
"""normalize_environment 环境名标准化"""
|
|
|
|
def test_development(self):
|
|
assert normalize_environment("development") == "development"
|
|
|
|
def test_production(self):
|
|
assert normalize_environment("production") == "production"
|
|
|
|
def test_staging(self):
|
|
assert normalize_environment("staging") == "staging"
|
|
|
|
def test_none_defaults_to_development(self):
|
|
assert normalize_environment(None) == "development"
|
|
|
|
def test_empty_defaults_to_development(self):
|
|
assert normalize_environment("") == "development"
|
|
|
|
def test_uppercase_lowered(self):
|
|
assert normalize_environment("PRODUCTION") == "production"
|
|
|
|
def test_mixed_case(self):
|
|
assert normalize_environment("Staging") == "staging"
|
|
|
|
def test_whitespace_stripped(self):
|
|
assert normalize_environment(" production ") == "production"
|
|
|
|
def test_custom_env_name(self):
|
|
assert normalize_environment("custom-env") == "custom-env"
|
|
|
|
|
|
# ============================================================
|
|
# assert_auto_create_schema_allowed
|
|
# ============================================================
|
|
|
|
|
|
class TestAssertAutoCreateSchemaAllowed:
|
|
"""assert_auto_create_schema_allowed 安全守卫"""
|
|
|
|
def test_development_enabled_ok(self):
|
|
# 不抛异常
|
|
assert_auto_create_schema_allowed("development", enabled=True)
|
|
|
|
def test_development_disabled_ok(self):
|
|
assert_auto_create_schema_allowed("development", enabled=False)
|
|
|
|
def test_staging_enabled_raises(self):
|
|
with pytest.raises(RuntimeError, match="forbidden"):
|
|
assert_auto_create_schema_allowed("staging", enabled=True)
|
|
|
|
def test_production_enabled_raises(self):
|
|
with pytest.raises(RuntimeError, match="forbidden"):
|
|
assert_auto_create_schema_allowed("production", enabled=True)
|
|
|
|
def test_staging_disabled_ok(self):
|
|
assert_auto_create_schema_allowed("staging", enabled=False)
|
|
|
|
def test_production_disabled_ok(self):
|
|
assert_auto_create_schema_allowed("production", enabled=False)
|
|
|
|
def test_none_env_enabled_ok(self):
|
|
# None → development → 允许
|
|
assert_auto_create_schema_allowed(None, enabled=True)
|
|
|
|
def test_uppercase_still_blocked(self):
|
|
with pytest.raises(RuntimeError):
|
|
assert_auto_create_schema_allowed("PRODUCTION", enabled=True)
|
|
|
|
def test_whitespace_staging_blocked(self):
|
|
with pytest.raises(RuntimeError):
|
|
assert_auto_create_schema_allowed(" staging ", enabled=True)
|
|
|
|
def test_custom_env_enabled_ok(self):
|
|
assert_auto_create_schema_allowed("test-env", enabled=True)
|
|
|
|
def test_blocked_envs_constant(self):
|
|
assert "staging" in BLOCKED_AUTO_CREATE_ENVIRONMENTS
|
|
assert "production" in BLOCKED_AUTO_CREATE_ENVIRONMENTS
|