95 lines
3.1 KiB
Python
95 lines
3.1 KiB
Python
|
|
"""User/member DTO validation (no DB)."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from uuid import uuid4
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from pydantic import ValidationError
|
||
|
|
|
||
|
|
from mimic.db.types import UserType
|
||
|
|
from mimic.schemas import EngagementMemberCreate, UserCreate, UserUpdate
|
||
|
|
from mimic.schemas.pagination import MAX_PAGE_SIZE, PageQuery
|
||
|
|
|
||
|
|
|
||
|
|
class TestUserCreate:
|
||
|
|
def test_minimal_valid_payload(self) -> None:
|
||
|
|
u = UserCreate.model_validate(
|
||
|
|
{
|
||
|
|
"email": "alice@example.org",
|
||
|
|
"password": "longenough",
|
||
|
|
"type": "rt_operator",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
assert u.email == "alice@example.org"
|
||
|
|
assert u.type is UserType.RT_OPERATOR
|
||
|
|
assert u.display_name is None
|
||
|
|
|
||
|
|
def test_password_min_length(self) -> None:
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
UserCreate.model_validate(
|
||
|
|
{"email": "a@b.c", "password": "short", "type": "rt_operator"}
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_invalid_email_rejected(self) -> None:
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
UserCreate.model_validate(
|
||
|
|
{"email": "not-an-email", "password": "longenough", "type": "rt_operator"}
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_invalid_type_rejected(self) -> None:
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
UserCreate.model_validate(
|
||
|
|
{"email": "a@b.c", "password": "longenough", "type": "not-a-role"}
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestUserUpdate:
|
||
|
|
def test_all_optional(self) -> None:
|
||
|
|
u = UserUpdate.model_validate({})
|
||
|
|
assert u.display_name is None
|
||
|
|
assert u.password is None
|
||
|
|
assert u.type is None
|
||
|
|
|
||
|
|
def test_password_validates_when_provided(self) -> None:
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
UserUpdate.model_validate({"password": "tiny"})
|
||
|
|
|
||
|
|
|
||
|
|
class TestEngagementMemberCreate:
|
||
|
|
def test_default_role(self) -> None:
|
||
|
|
member = EngagementMemberCreate.model_validate({"user_id": str(uuid4())})
|
||
|
|
assert member.role == "member"
|
||
|
|
|
||
|
|
def test_explicit_role(self) -> None:
|
||
|
|
member = EngagementMemberCreate.model_validate(
|
||
|
|
{"user_id": str(uuid4()), "role": "lead-on-mission"}
|
||
|
|
)
|
||
|
|
assert member.role == "lead-on-mission"
|
||
|
|
|
||
|
|
def test_role_max_length(self) -> None:
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
EngagementMemberCreate.model_validate({"user_id": str(uuid4()), "role": "x" * 41})
|
||
|
|
|
||
|
|
|
||
|
|
class TestPageQuery:
|
||
|
|
def test_defaults(self) -> None:
|
||
|
|
page = PageQuery.model_validate({})
|
||
|
|
assert page.page == 1
|
||
|
|
assert page.page_size == 50
|
||
|
|
assert page.offset == 0
|
||
|
|
assert page.limit == 50
|
||
|
|
|
||
|
|
def test_offset_arithmetic(self) -> None:
|
||
|
|
page = PageQuery.model_validate({"page": 4, "page_size": 25})
|
||
|
|
assert page.offset == 75
|
||
|
|
assert page.limit == 25
|
||
|
|
|
||
|
|
def test_page_size_clamped(self) -> None:
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
PageQuery.model_validate({"page_size": MAX_PAGE_SIZE + 1})
|
||
|
|
|
||
|
|
def test_page_lower_bound(self) -> None:
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
PageQuery.model_validate({"page": 0})
|