Files
xiaoxia-saas/tests/unit/test_validation_utils.py
xiaoxia 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
test(validation): P3-1第十六波 验证工具与schema守卫 57个 (#720)
2026-07-22 16:28:39 +08:00

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