07f6705534
CI/CD Pipeline / Check if frontend-only change (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 / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 36s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m36s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m47s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m15s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m23s
CI/CD Pipeline / Unit Tests (push) Successful in 2m23s
CI/CD Pipeline / Integration Tests (push) Successful in 1m3s
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
Co-authored-by: CI Bot <dev@xiaoxiajianji.com> Co-committed-by: CI Bot <dev@xiaoxiajianji.com>
169 lines
5.7 KiB
Python
169 lines
5.7 KiB
Python
"""
|
|
VerificationCode 验证码领域模型单元测试
|
|
"""
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
import pytest
|
|
from domain.verification_code import VerificationCode
|
|
|
|
|
|
class TestVerificationCodeCreate:
|
|
"""创建验证码测试"""
|
|
|
|
def test_create_default_6digit_code(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
assert vc.id is not None
|
|
assert len(vc.id) == 32 # uuid4 hex
|
|
assert vc.recipient == "test@example.com"
|
|
assert vc.code_type == "email_bind"
|
|
assert len(vc.code) == 6
|
|
assert vc.code.isdigit()
|
|
assert vc.used_at is None
|
|
assert vc.attempts == 0
|
|
|
|
def test_create_custom_code(self):
|
|
vc = VerificationCode.create("13800138000", "phone_bind", custom_code="123456")
|
|
assert vc.code == "123456"
|
|
|
|
def test_create_default_ttl_300s(self):
|
|
before = datetime.now(timezone.utc)
|
|
vc = VerificationCode.create("test@example.com", "email_login")
|
|
after = datetime.now(timezone.utc)
|
|
expected_expiry_min = before + timedelta(seconds=300)
|
|
expected_expiry_max = after + timedelta(seconds=300)
|
|
assert expected_expiry_min <= vc.expires_at <= expected_expiry_max
|
|
|
|
def test_create_custom_ttl(self):
|
|
vc = VerificationCode.create("test@example.com", "reset_password", ttl_seconds=60)
|
|
expected = datetime.now(timezone.utc) + timedelta(seconds=60)
|
|
diff = abs((vc.expires_at - expected).total_seconds())
|
|
assert diff < 2
|
|
|
|
def test_create_recipient_stripped(self):
|
|
vc = VerificationCode.create(" test@example.com ", "email_bind")
|
|
assert vc.recipient == "test@example.com"
|
|
|
|
def test_create_phone_recipient(self):
|
|
vc = VerificationCode.create("13800138000", "phone_login")
|
|
assert vc.recipient == "13800138000"
|
|
assert vc.code_type == "phone_login"
|
|
|
|
def test_create_sets_created_at(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
assert vc.created_at is not None
|
|
assert isinstance(vc.created_at, datetime)
|
|
|
|
|
|
class TestVerificationCodeExpiry:
|
|
"""过期状态测试"""
|
|
|
|
def test_fresh_code_not_expired(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
assert vc.is_expired is False
|
|
|
|
def test_expired_code_is_expired(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=-60)
|
|
assert vc.is_expired is True
|
|
|
|
def test_boundary_not_expired_at_expiry_time(self):
|
|
now = datetime.now(timezone.utc)
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
vc.expires_at = now + timedelta(seconds=1)
|
|
assert vc.is_expired is False
|
|
|
|
def test_boundary_expired_right_after(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
vc.expires_at = datetime.now(timezone.utc) - timedelta(microseconds=1)
|
|
assert vc.is_expired is True
|
|
|
|
|
|
class TestVerificationCodeUsed:
|
|
"""使用状态测试"""
|
|
|
|
def test_fresh_code_not_used(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
assert vc.is_used is False
|
|
|
|
def test_mark_used(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
vc.mark_used()
|
|
assert vc.is_used is True
|
|
assert vc.used_at is not None
|
|
assert isinstance(vc.used_at, datetime)
|
|
|
|
def test_mark_used_sets_recent_time(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
before = datetime.now(timezone.utc)
|
|
vc.mark_used()
|
|
after = datetime.now(timezone.utc)
|
|
assert before <= vc.used_at <= after
|
|
|
|
def test_mark_used_idempotent(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
vc.mark_used()
|
|
first_used_at = vc.used_at
|
|
vc.mark_used()
|
|
# 第二次会更新时间
|
|
assert vc.used_at >= first_used_at
|
|
|
|
|
|
class TestVerificationCodeValidity:
|
|
"""有效性(未过期+未使用)测试"""
|
|
|
|
def test_fresh_code_is_valid(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
assert vc.is_valid is True
|
|
|
|
def test_expired_code_not_valid(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=-10)
|
|
assert vc.is_valid is False
|
|
|
|
def test_used_code_not_valid(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
vc.mark_used()
|
|
assert vc.is_valid is False
|
|
|
|
def test_expired_and_used_not_valid(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=-10)
|
|
vc.mark_used()
|
|
assert vc.is_valid is False
|
|
|
|
|
|
class TestVerificationCodeAttempts:
|
|
"""尝试次数测试"""
|
|
|
|
def test_initial_attempts_zero(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
assert vc.attempts == 0
|
|
|
|
def test_increment_attempts(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
vc.increment_attempts()
|
|
assert vc.attempts == 1
|
|
|
|
def test_increment_attempts_multiple(self):
|
|
vc = VerificationCode.create("test@example.com", "email_bind")
|
|
for _ in range(5):
|
|
vc.increment_attempts()
|
|
assert vc.attempts == 5
|
|
|
|
|
|
class TestVerificationCodeTypes:
|
|
"""不同验证码类型测试"""
|
|
|
|
@pytest.mark.parametrize(
|
|
"code_type",
|
|
[
|
|
"email_bind",
|
|
"phone_bind",
|
|
"email_login",
|
|
"phone_login",
|
|
"reset_password",
|
|
],
|
|
)
|
|
def test_all_supported_types(self, code_type):
|
|
vc = VerificationCode.create("test@example.com", code_type)
|
|
assert vc.code_type == code_type
|
|
assert vc.is_valid is True
|