""" 验证工具与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