test(admin): ветка kept_roles, различающий тест экранирования, код LAST_ADMIN (T12.1, T12.3, T12.4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
parent
fda907ba2e
commit
d241c07f54
@ -183,14 +183,15 @@ async def test_list_users_search_filter_and_page(auth_client: AuthClientFactory,
|
||||
office = await _office(seeded, ROSTOV)
|
||||
await make_user(seeded, email="Zoya_Search@example.com", office=office, roles=["KA_RF", "EXPERT"], full_name="Зоя")
|
||||
await make_user(seeded, email="off@example.com", office=office, full_name="Зоя Отключённая", is_active=False)
|
||||
await make_user(seeded, email="zoyaXs@example.com", office=office) # совпал бы с `zoya_s`, если `_` — шаблон LIKE
|
||||
admin = await auth_client(ADMIN)
|
||||
|
||||
everyone = (await admin.get(USERS)).json()
|
||||
assert set(everyone) == {"items", "total", "limit", "offset"}
|
||||
assert everyone["total"] == len(everyone["items"]) >= 9 # noqa: PLR2004 - 7 dev-пользователей + 2 новых
|
||||
assert everyone["total"] == len(everyone["items"]) >= 10 # noqa: PLR2004 - 7 dev-пользователей + 3 новых
|
||||
|
||||
by_email = (await admin.get(USERS, params={"q": "zoya_s"})).json() # `_` ищется буквально, без регистра
|
||||
assert [item["email"] for item in by_email["items"]] == ["Zoya_Search@example.com"]
|
||||
assert [item["email"] for item in by_email["items"]] == ["Zoya_Search@example.com"] # `zoyaXs@…` не найден
|
||||
assert by_email["items"][0]["roles"] == ["EXPERT", "KA_RF"]
|
||||
assert by_email["items"][0]["regional_office"]["name"] == ROSTOV
|
||||
|
||||
@ -364,6 +365,57 @@ async def test_second_admin_allows_dropping_own_admin_role(
|
||||
assert (await admin.get("/admin/roles")).status_code == HTTP_FORBIDDEN # права пересчитаны на следующем запросе
|
||||
|
||||
|
||||
async def _grant_users_admin(session: AsyncSession, role_code: str, *, is_active: bool = True) -> None:
|
||||
"""Даёт роли право `USERS_ADMIN` напрямую в БД (и задаёт `is_active` роли) — как после правки матрицы прав."""
|
||||
role = (await session.execute(select(Role).where(Role.code == role_code))).scalar_one()
|
||||
permission_id = (await session.execute(select(Permission.id).where(Permission.code == "USERS_ADMIN"))).scalar_one()
|
||||
session.add(RolePermission(role_id=role.id, permission_id=permission_id))
|
||||
role.is_active = is_active
|
||||
await session.flush()
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_sole_admin_moved_to_active_role_with_users_admin_stays_admin(
|
||||
auth_client: AuthClientFactory, seeded: AsyncSession
|
||||
) -> None:
|
||||
"""Ветка `kept_roles`: оставшаяся активная роль даёт `USERS_ADMIN` — другого администратора не требуется (T12.1)."""
|
||||
await _grant_users_admin(seeded, "EXPERT")
|
||||
admin_id = await _user_id(seeded, ADMIN)
|
||||
admin = await auth_client(ADMIN)
|
||||
response = await admin.put(f"{USERS}/{admin_id}/roles", json={"roles": ["EXPERT"]})
|
||||
assert response.status_code == HTTP_OK, response.text
|
||||
assert await _role_codes(seeded, admin_id) == ["EXPERT"]
|
||||
assert (await admin.get(USERS)).status_code == HTTP_OK # право USERS_ADMIN сохранено через EXPERT
|
||||
assert len(await _events(seeded, AuditAction.USER_ROLE_CHANGE)) == 1
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_sole_admin_cannot_be_moved_to_inactive_role_with_users_admin(
|
||||
auth_client: AuthClientFactory, seeded: AsyncSession
|
||||
) -> None:
|
||||
"""Неактивная роль с `USERS_ADMIN` не даёт права и не назначается: набор ролей и администратор не меняются (T12.1).
|
||||
|
||||
Сервис отсекает её раньше проверки `LAST_ADMIN` — 422 `UNKNOWN_ROLE`; сам фильтр `is_active` в `kept_roles`-проверке
|
||||
(`roles_grant_permission`) защитный и до неё через API недостижим, поэтому проверяется отдельно.
|
||||
"""
|
||||
await _grant_users_admin(seeded, "EXPERT", is_active=False)
|
||||
admin_id = await _user_id(seeded, ADMIN)
|
||||
admin = await auth_client(ADMIN)
|
||||
response = await admin.put(f"{USERS}/{admin_id}/roles", json={"roles": ["EXPERT"]})
|
||||
assert response.status_code == HTTP_UNPROCESSABLE, response.text
|
||||
assert _detail_code(response) == "UNKNOWN_ROLE"
|
||||
assert await _role_codes(seeded, admin_id) == ["ADMIN"]
|
||||
assert await _events(seeded, AuditAction.USER_ROLE_CHANGE) == []
|
||||
assert (await admin.get(USERS)).status_code == HTTP_OK
|
||||
|
||||
roles = {
|
||||
role.code: role.id
|
||||
for role in (await seeded.execute(select(Role).where(Role.code.in_(["ADMIN", "EXPERT"])))).scalars()
|
||||
}
|
||||
assert await users_repo.roles_grant_permission(seeded, {roles["ADMIN"]}, "USERS_ADMIN") is True
|
||||
assert await users_repo.roles_grant_permission(seeded, {roles["EXPERT"]}, "USERS_ADMIN") is False # неактивная роль
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_last_admin_cannot_deactivate_self(auth_client: AuthClientFactory, seeded: AsyncSession) -> None:
|
||||
"""Review Focus 5: последний активный с `USERS_ADMIN` не может деактивировать себя; при втором — может."""
|
||||
@ -401,6 +453,7 @@ async def test_deactivating_other_admin_respects_last_admin(
|
||||
admin_id = await _user_id(seeded, ADMIN)
|
||||
response = await admin.patch(f"{USERS}/{admin_id}", json={"is_active": False})
|
||||
assert response.status_code == HTTP_CONFLICT
|
||||
assert _detail_code(response) == "LAST_ADMIN"
|
||||
assert (await second_client.get(USERS)).status_code == HTTP_FORBIDDEN
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user