"""角色请求模型单元测试。 守的是**角色编码的格式红线**:编码会进入授权判断(`ROLE_ADMIN` 按编码 比对)与操作日志,必须限定为小写字母开头的小写/数字/下划线组合 —— 放开大写或中文会让对照与检索困难,超长则与列宽(32)不符。 """ import pytest from pydantic import ValidationError from app.schemas.role import RoleCreateIn, RoleQuery, RoleStatusIn, RoleUpdateIn def _create(**overrides: object) -> RoleCreateIn: base: dict[str, object] = {"role_code": "finance", "role_name": "财务"} base.update(overrides) return RoleCreateIn(**base) class TestRoleCodePattern: """编码规则:^[a-z][a-z0-9_]{1,31}$""" def test_合法编码通过(self) -> None: assert _create().role_code == "finance" def test_下划线与数字通过(self) -> None: assert _create(role_code="sales_01").role_code == "sales_01" def test_大写字母被拒(self) -> None: with pytest.raises(ValidationError): _create(role_code="Finance") def test_中文被拒(self) -> None: with pytest.raises(ValidationError): _create(role_code="财务") def test_数字开头被拒(self) -> None: with pytest.raises(ValidationError): _create(role_code="1abc") def test_下划线开头被拒(self) -> None: with pytest.raises(ValidationError): _create(role_code="_abc") def test_单字符被拒(self) -> None: with pytest.raises(ValidationError): _create(role_code="a") def test_超长被拒(self) -> None: with pytest.raises(ValidationError): _create(role_code="a" * 33) def test_首尾空白被自动去除(self) -> None: assert _create(role_code=" finance ").role_code == "finance" class TestInitialPermissions: def test_旧创建请求兼容且默认不授权(self): assert _create().permission_codes is None def test_初始授权无需手动填写修改原因(self): payload = _create(permission_codes=["base:personnel:view"]) assert payload.reason is None @pytest.mark.parametrize("reason", ["", " "]) def test_显式提供的原因不能是空白(self, reason): with pytest.raises(ValidationError): _create(permission_codes=["base:personnel:view"], reason=reason) def test_初始权限和原因保留(self): payload = _create(permission_codes=["base:personnel:view"], reason=" 增加资料查看职责 ") assert payload.permission_codes == ["base:personnel:view"] assert payload.reason == "增加资料查看职责" class TestRoleUpdate: """编辑模型:不含 role_code。""" def test_编辑不接受role_code(self) -> None: """编码是授权判定锚点,编辑接口有意不接收;多余字段被忽略而非报错 (前端表单可能整份回填提交)。""" payload = RoleUpdateIn(role_name="财务二组", role_code="hacked") assert not hasattr(payload, "role_code") def test_名称为空被拒(self) -> None: with pytest.raises(ValidationError): RoleUpdateIn(role_name="") def test_排序负数被拒(self) -> None: with pytest.raises(ValidationError): RoleUpdateIn(role_name="财务", sort_order=-1) class TestRoleStatusAndQuery: """状态与查询模型。""" def test_状态只能为0或1(self) -> None: assert RoleStatusIn(status=0).status == 0 assert RoleStatusIn(status=1).status == 1 with pytest.raises(ValidationError): RoleStatusIn(status=2) def test_查询默认分页(self) -> None: query = RoleQuery() assert query.page_num == 1 assert query.page_size == 10 assert query.keyword is None assert query.status is None def test_分页别名照常工作(self) -> None: """pageNum/pageSize 是前端契约(PageQuery 的 alias)。""" query = RoleQuery(pageNum=3, pageSize=5) assert query.offset == 10 def test_关键词超长被拒(self) -> None: with pytest.raises(ValidationError): RoleQuery(keyword="a" * 65)