From 1a29decac381ebd5517d9fc7b576fa53d4ba19df Mon Sep 17 00:00:00 2001 From: CI Bot Date: Wed, 22 Jul 2026 14:48:33 +0800 Subject: [PATCH] =?UTF-8?q?test(validation):=20=E8=A1=A5=E5=85=85=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E5=B7=A5=E5=85=B7=E4=B8=8Eschema=E5=AE=88=E5=8D=AB57?= =?UTF-8?q?=E4=B8=AA=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 覆盖 validate_phone/normalize_phone/validate_email、 normalize_environment/assert_auto_create_schema_allowed --- tests/unit/test_validation_utils.py | 283 ++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100755 tests/unit/test_validation_utils.py diff --git a/tests/unit/test_validation_utils.py b/tests/unit/test_validation_utils.py new file mode 100755 index 000000000..2f3f5ce6e --- /dev/null +++ b/tests/unit/test_validation_utils.py @@ -0,0 +1,283 @@ +""" +验证工具与schema守卫单元测试 + +覆盖: +- validate_phone / normalize_phone / validate_email +- schema_guard: normalize_environment / assert_auto_create_schema_allowed +""" + +import pytest + +from packages.application.auth.verification_code_service import ( + validate_phone, + normalize_phone, + validate_email, +) +from packages.adapters.sqlalchemy_impl.schema_guard import ( + BLOCKED_AUTO_CREATE_ENVIRONMENTS, + normalize_environment, + assert_auto_create_schema_allowed, +) + + +# ============================================================ +# 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