Squashed new RBAC
All checks were successful
Deploy on push / deploy (push) Successful in 7s

This commit is contained in:
2025-07-02 22:30:21 +03:00
parent 7585dae0ab
commit 82111ed0f6
100 changed files with 14785 additions and 5888 deletions

View File

@@ -42,6 +42,43 @@ def db_session(test_session_factory):
Простая реализация без вложенных транзакций.
"""
session = test_session_factory()
# Создаем дефолтное сообщество для тестов
from orm.community import Community
from auth.orm import Author
import time
# Создаем системного автора если его нет
system_author = session.query(Author).filter(Author.slug == "system").first()
if not system_author:
system_author = Author(
name="System",
slug="system",
email="system@test.local",
created_at=int(time.time()),
updated_at=int(time.time()),
last_seen=int(time.time())
)
session.add(system_author)
session.flush()
# Создаем дефолтное сообщество если его нет
default_community = session.query(Community).filter(Community.id == 1).first()
if not default_community:
default_community = Community(
id=1,
name="Главное сообщество",
slug="main",
desc="Основное сообщество для тестов",
pic="",
created_at=int(time.time()),
created_by=system_author.id,
settings={"default_roles": ["reader", "author"], "available_roles": ["reader", "author", "artist", "expert", "editor", "admin"]},
private=False
)
session.add(default_community)
session.commit()
yield session
# Очищаем все данные после теста
@@ -63,6 +100,42 @@ def db_session_commit(test_session_factory):
"""
session = test_session_factory()
# Создаем дефолтное сообщество для интеграционных тестов
from orm.community import Community
from auth.orm import Author
import time
# Создаем системного автора если его нет
system_author = session.query(Author).filter(Author.slug == "system").first()
if not system_author:
system_author = Author(
name="System",
slug="system",
email="system@test.local",
created_at=int(time.time()),
updated_at=int(time.time()),
last_seen=int(time.time())
)
session.add(system_author)
session.flush()
# Создаем дефолтное сообщество если его нет
default_community = session.query(Community).filter(Community.id == 1).first()
if not default_community:
default_community = Community(
id=1,
name="Главное сообщество",
slug="main",
desc="Основное сообщество для тестов",
pic="",
created_at=int(time.time()),
created_by=system_author.id,
settings={"default_roles": ["reader", "author"], "available_roles": ["reader", "author", "artist", "expert", "editor", "admin"]},
private=False
)
session.add(default_community)
session.commit()
yield session
# Очищаем все данные после теста
@@ -121,6 +194,43 @@ def oauth_db_session(test_session_factory):
oauth.set_session_factory(lambda: test_session_factory())
session = test_session_factory()
# Создаем дефолтное сообщество для OAuth тестов
from orm.community import Community
from auth.orm import Author
import time
# Создаем системного автора если его нет
system_author = session.query(Author).filter(Author.slug == "system").first()
if not system_author:
system_author = Author(
name="System",
slug="system",
email="system@test.local",
created_at=int(time.time()),
updated_at=int(time.time()),
last_seen=int(time.time())
)
session.add(system_author)
session.flush()
# Создаем дефолтное сообщество если его нет
default_community = session.query(Community).filter(Community.id == 1).first()
if not default_community:
default_community = Community(
id=1,
name="Главное сообщество",
slug="main",
desc="Основное сообщество для OAuth тестов",
pic="",
created_at=int(time.time()),
created_by=system_author.id,
settings={"default_roles": ["reader", "author"], "available_roles": ["reader", "author", "artist", "expert", "editor", "admin"]},
private=False
)
session.add(default_community)
session.commit()
yield session
# Очищаем данные и восстанавливаем оригинальную фабрику

View File

@@ -16,11 +16,7 @@ from auth.orm import ( # noqa: F401
Author,
AuthorBookmark,
AuthorFollower,
AuthorRating,
AuthorRole,
Permission,
Role,
RolePermission,
AuthorRating
)
from orm.collection import ShoutCollection # noqa: F401
from orm.community import Community, CommunityAuthor, CommunityFollower # noqa: F401

View File

@@ -1,22 +1,13 @@
import pytest
from auth.orm import Author, AuthorRole, Role
from auth.orm import Author
from orm.community import CommunityAuthor
from orm.shout import Shout
from resolvers.draft import create_draft, load_drafts
def ensure_test_user_with_roles(db_session):
"""Создает тестового пользователя с ID 1 и назначает ему роли"""
# Создаем роли если их нет
reader_role = db_session.query(Role).filter(Role.id == "reader").first()
if not reader_role:
reader_role = Role(id="reader", name="Читатель")
db_session.add(reader_role)
author_role = db_session.query(Role).filter(Role.id == "author").first()
if not author_role:
author_role = Role(id="author", name="Автор")
db_session.add(author_role)
"""Создает тестового пользователя с ID 1 и назначает ему роли через CommunityAuthor"""
# Создаем пользователя с ID 1 если его нет
test_user = db_session.query(Author).filter(Author.id == 1).first()
@@ -26,15 +17,25 @@ def ensure_test_user_with_roles(db_session):
db_session.add(test_user)
db_session.flush()
# Удаляем старые роли и добавляем новые
db_session.query(AuthorRole).filter(AuthorRole.author == 1).delete()
# Удаляем старые роли
existing_community_author = (
db_session.query(CommunityAuthor)
.filter(CommunityAuthor.author_id == test_user.id, CommunityAuthor.community_id == 1)
.first()
)
# Добавляем роли
for role_id in ["reader", "author"]:
author_role_link = AuthorRole(community=1, author=1, role=role_id)
db_session.add(author_role_link)
if existing_community_author:
db_session.delete(existing_community_author)
# Создаем новую запись с ролями
community_author = CommunityAuthor(
community_id=1,
author_id=test_user.id,
roles="reader,author", # CSV строка с ролями
)
db_session.add(community_author)
db_session.commit()
return test_user

View File

@@ -0,0 +1,497 @@
"""
Тесты интеграции RBAC системы с существующими компонентами проекта.
Проверяет работу вспомогательных функций из orm/community.py
и интеграцию с GraphQL резолверами.
"""
import pytest
from auth.orm import Author
from orm.community import (
Community,
CommunityAuthor,
assign_role_to_user,
bulk_assign_roles,
check_user_permission_in_community,
get_user_roles_in_community,
remove_role_from_user,
)
from services.rbac import get_permissions_for_role
@pytest.fixture
def integration_users(db_session):
"""Создает тестовых пользователей для интеграционных тестов"""
users = []
# Создаем пользователей с ID 100-105 для избежания конфликтов
for i in range(100, 106):
user = db_session.query(Author).filter(Author.id == i).first()
if not user:
user = Author(
id=i,
email=f"integration_user{i}@example.com",
name=f"Integration User {i}",
slug=f"integration-user-{i}",
)
user.set_password("password123")
db_session.add(user)
users.append(user)
db_session.commit()
return users
@pytest.fixture
def integration_community(db_session, integration_users):
"""Создает тестовое сообщество для интеграционных тестов"""
community = db_session.query(Community).filter(Community.id == 100).first()
if not community:
community = Community(
id=100,
name="Integration Test Community",
slug="integration-test-community",
desc="Community for integration tests",
created_by=integration_users[0].id,
)
db_session.add(community)
db_session.commit()
return community
@pytest.fixture(autouse=True)
def clean_community_authors(db_session, integration_community):
"""Автоматически очищает все записи CommunityAuthor для тестового сообщества перед каждым тестом"""
# Очистка перед тестом - используем более агрессивную очистку
try:
db_session.query(CommunityAuthor).filter(CommunityAuthor.community_id == integration_community.id).delete()
db_session.commit()
except Exception:
db_session.rollback()
# Дополнительная очистка всех записей для тестовых пользователей
try:
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id.in_([100, 101, 102, 103, 104, 105])).delete()
db_session.commit()
except Exception:
db_session.rollback()
yield # Тест выполняется
# Очистка после теста
try:
db_session.query(CommunityAuthor).filter(CommunityAuthor.community_id == integration_community.id).delete()
db_session.commit()
except Exception:
db_session.rollback()
class TestHelperFunctions:
"""Тесты для вспомогательных функций RBAC"""
def test_get_user_roles_in_community(self, db_session, integration_users, integration_community):
"""Тест функции получения ролей пользователя в сообществе"""
# Назначаем роли через функции вместо прямого создания записи
assign_role_to_user(integration_users[0].id, "reader", integration_community.id)
assign_role_to_user(integration_users[0].id, "author", integration_community.id)
assign_role_to_user(integration_users[0].id, "expert", integration_community.id)
# Проверяем функцию
roles = get_user_roles_in_community(integration_users[0].id, integration_community.id)
assert "reader" in roles
assert "author" in roles
assert "expert" in roles
# Проверяем для пользователя без ролей
no_roles = get_user_roles_in_community(integration_users[1].id, integration_community.id)
assert no_roles == []
async def test_check_user_permission_in_community(self, db_session, integration_users, integration_community):
"""Тест функции проверки разрешения в сообществе"""
# Назначаем роли через функции
assign_role_to_user(integration_users[0].id, "author", integration_community.id)
assign_role_to_user(integration_users[0].id, "expert", integration_community.id)
# Проверяем разрешения
assert (
await check_user_permission_in_community(integration_users[0].id, "shout:create", integration_community.id)
is True
)
assert (
await check_user_permission_in_community(integration_users[0].id, "shout:read", integration_community.id) is True
)
# Проверяем для пользователя без ролей
# Сначала проверим какие роли у пользователя
user_roles = get_user_roles_in_community(integration_users[1].id, integration_community.id)
print(f"[DEBUG] User {integration_users[1].id} roles: {user_roles}")
result = await check_user_permission_in_community(integration_users[1].id, "shout:create", integration_community.id)
print(f"[DEBUG] Permission check result: {result}")
assert result is False
def test_assign_role_to_user(self, db_session, integration_users, integration_community):
"""Тест функции назначения роли пользователю"""
# Назначаем роль пользователю без существующих ролей
result = assign_role_to_user(integration_users[0].id, "reader", integration_community.id)
assert result is True
# Проверяем что роль назначилась
roles = get_user_roles_in_community(integration_users[0].id, integration_community.id)
assert "reader" in roles
# Назначаем ещё одну роль
result = assign_role_to_user(integration_users[0].id, "author", integration_community.id)
assert result is True
roles = get_user_roles_in_community(integration_users[0].id, integration_community.id)
assert "reader" in roles
assert "author" in roles
# Попытка назначить существующую роль
result = assign_role_to_user(integration_users[0].id, "reader", integration_community.id)
assert result is False # Роль уже есть
def test_remove_role_from_user(self, db_session, integration_users, integration_community):
"""Тест функции удаления роли у пользователя"""
# Назначаем роли через функции
assign_role_to_user(integration_users[1].id, "reader", integration_community.id)
assign_role_to_user(integration_users[1].id, "author", integration_community.id)
assign_role_to_user(integration_users[1].id, "expert", integration_community.id)
# Удаляем роль
result = remove_role_from_user(integration_users[1].id, "author", integration_community.id)
assert result is True
# Проверяем что роль удалилась
roles = get_user_roles_in_community(integration_users[1].id, integration_community.id)
assert "author" not in roles
assert "reader" in roles
assert "expert" in roles
# Попытка удалить несуществующую роль
result = remove_role_from_user(integration_users[1].id, "admin", integration_community.id)
assert result is False
async def test_get_all_community_members_with_roles(self, db_session, integration_users: list[Author], integration_community: Community):
"""Тест функции получения всех участников сообщества с ролями"""
# Назначаем роли нескольким пользователям через функции
assign_role_to_user(integration_users[0].id, "reader", integration_community.id)
assign_role_to_user(integration_users[0].id, "author", integration_community.id)
assign_role_to_user(integration_users[1].id, "expert", integration_community.id)
assign_role_to_user(integration_users[1].id, "editor", integration_community.id)
assign_role_to_user(integration_users[2].id, "admin", integration_community.id)
# Получаем участников
members = integration_community.get_community_members(with_roles=True)
assert len(members) == 3
# Проверяем структуру данных
for member in members:
assert "author_id" in member
assert "roles" in member
assert "permissions" in member
assert "joined_at" in member
# Проверяем конкретного участника
admin_member = next(m for m in members if m["author_id"] == integration_users[2].id)
assert "admin" in admin_member["roles"]
assert len(admin_member["permissions"]) > 0
def test_bulk_assign_roles(self, db_session, integration_users: list[Author], integration_community: Community):
"""Тест функции массового назначения ролей"""
# Подготавливаем данные для массового назначения
user_role_pairs = [
(integration_users[0].id, "reader"),
(integration_users[1].id, "author"),
(integration_users[2].id, "expert"),
(integration_users[3].id, "editor"),
(integration_users[4].id, "admin"),
]
# Выполняем массовое назначение
result = bulk_assign_roles(user_role_pairs, integration_community.id)
# Проверяем результат
assert result["success"] == 5
assert result["failed"] == 0
# Проверяем что роли назначились
for user_id, expected_role in user_role_pairs:
roles = get_user_roles_in_community(user_id, integration_community.id)
assert expected_role in roles
class TestRoleHierarchy:
"""Тесты иерархии ролей и наследования разрешений"""
async def test_role_inheritance(self, integration_community):
"""Тест наследования разрешений между ролями"""
# Читатель имеет базовые разрешения
reader_perms = set(await get_permissions_for_role("reader", integration_community.id))
# Автор должен иметь все разрешения читателя + свои
author_perms = set(await get_permissions_for_role("author", integration_community.id))
# Проверяем что автор имеет базовые разрешения читателя
basic_read_perms = {"shout:read", "topic:read"}
assert basic_read_perms.issubset(author_perms)
# Админ должен иметь максимальные разрешения
admin_perms = set(await get_permissions_for_role("admin", integration_community.id))
assert len(admin_perms) >= len(author_perms)
assert len(admin_perms) >= len(reader_perms)
async def test_permission_aggregation(self, db_session, integration_users, integration_community):
"""Тест агрегации разрешений от нескольких ролей"""
# Назначаем роли через функции
assign_role_to_user(integration_users[0].id, "reader", integration_community.id)
assign_role_to_user(integration_users[0].id, "author", integration_community.id)
assign_role_to_user(integration_users[0].id, "expert", integration_community.id)
# Получаем объект CommunityAuthor для проверки агрегированных разрешений
from services.db import local_session
with local_session() as session:
ca = CommunityAuthor.find_by_user_and_community(integration_users[0].id, integration_community.id, session)
# Получаем агрегированные разрешения
all_permissions = await ca.get_permissions()
# Проверяем что есть разрешения от всех ролей
reader_perms = await get_permissions_for_role("reader", integration_community.id)
author_perms = await get_permissions_for_role("author", integration_community.id)
expert_perms = await get_permissions_for_role("expert", integration_community.id)
# Все разрешения от отдельных ролей должны быть в общем списке
for perm in reader_perms:
assert perm in all_permissions
for perm in author_perms:
assert perm in all_permissions
for perm in expert_perms:
assert perm in all_permissions
class TestCommunityMethods:
"""Тесты методов Community для работы с ролями"""
def test_community_get_user_roles(self, db_session, integration_users, integration_community):
"""Тест получения ролей пользователя через сообщество"""
# Назначаем роли через функции
assign_role_to_user(integration_users[0].id, "reader", integration_community.id)
assign_role_to_user(integration_users[0].id, "author", integration_community.id)
assign_role_to_user(integration_users[0].id, "expert", integration_community.id)
# Проверяем через метод сообщества
user_roles = integration_community.get_user_roles(integration_users[0].id)
assert "reader" in user_roles
assert "author" in user_roles
assert "expert" in user_roles
# Проверяем для пользователя без ролей
no_roles = integration_community.get_user_roles(integration_users[1].id)
assert no_roles == []
def test_community_has_user_role(self, db_session, integration_users, integration_community):
"""Тест проверки роли пользователя в сообществе"""
# Назначаем роли через функции
assign_role_to_user(integration_users[1].id, "reader", integration_community.id)
assign_role_to_user(integration_users[1].id, "author", integration_community.id)
# Проверяем существующие роли
assert integration_community.has_user_role(integration_users[1].id, "reader") is True
assert integration_community.has_user_role(integration_users[1].id, "author") is True
# Проверяем несуществующие роли
assert integration_community.has_user_role(integration_users[1].id, "admin") is False
def test_community_add_user_role(self, db_session, integration_users, integration_community):
"""Тест добавления роли пользователю через сообщество"""
# Добавляем роль пользователю без записи
integration_community.add_user_role(integration_users[0].id, "reader")
# Проверяем что роль добавилась
roles = integration_community.get_user_roles(integration_users[0].id)
assert "reader" in roles
# Добавляем ещё одну роль
integration_community.add_user_role(integration_users[0].id, "author")
roles = integration_community.get_user_roles(integration_users[0].id)
assert "reader" in roles
assert "author" in roles
def test_community_remove_user_role(self, db_session, integration_users, integration_community):
"""Тест удаления роли у пользователя через сообщество"""
# Назначаем роли через функции
assign_role_to_user(integration_users[1].id, "reader", integration_community.id)
assign_role_to_user(integration_users[1].id, "author", integration_community.id)
assign_role_to_user(integration_users[1].id, "expert", integration_community.id)
# Удаляем роль
integration_community.remove_user_role(integration_users[1].id, "author")
roles = integration_community.get_user_roles(integration_users[1].id)
assert "author" not in roles
assert "reader" in roles
assert "expert" in roles
def test_community_set_user_roles(self, db_session, integration_users, integration_community):
"""Тест установки ролей пользователя через сообщество"""
# Устанавливаем роли пользователю без записи
integration_community.set_user_roles(integration_users[2].id, ["admin", "editor"])
roles = integration_community.get_user_roles(integration_users[2].id)
assert set(roles) == {"admin", "editor"}
# Меняем роли
integration_community.set_user_roles(integration_users[2].id, ["reader"])
roles = integration_community.get_user_roles(integration_users[2].id)
assert roles == ["reader"]
# Очищаем роли
integration_community.set_user_roles(integration_users[2].id, [])
roles = integration_community.get_user_roles(integration_users[2].id)
assert roles == []
async def test_community_get_members(self, db_session, integration_users: list[Author], integration_community: Community):
"""Тест получения участников сообщества"""
# Назначаем роли через функции
assign_role_to_user(integration_users[0].id, "reader", integration_community.id)
assign_role_to_user(integration_users[0].id, "author", integration_community.id)
assign_role_to_user(integration_users[1].id, "expert", integration_community.id)
# Получаем участников без ролей
members = integration_community.get_community_members(with_roles=False)
for member in members:
assert "author_id" in member
assert "joined_at" in member
assert "roles" not in member
# Получаем участников с ролями
members_with_roles = integration_community.get_community_members(with_roles=True)
for member in members_with_roles:
assert "author_id" in member
assert "joined_at" in member
assert "roles" in member
assert "permissions" in member
class TestEdgeCasesIntegration:
"""Тесты граничных случаев интеграции"""
async def test_nonexistent_community(self, integration_users):
"""Тест работы с несуществующим сообществом"""
# Функции должны корректно обрабатывать несуществующие сообщества
roles = get_user_roles_in_community(integration_users[0].id, 99999)
assert roles == []
has_perm = await check_user_permission_in_community(integration_users[0].id, "shout:read", 99999)
assert has_perm is False
async def test_nonexistent_user(self, integration_community):
"""Тест работы с несуществующим пользователем"""
# Функции должны корректно обрабатывать несуществующих пользователей
roles = get_user_roles_in_community(99999, integration_community.id)
assert roles == []
has_perm = await check_user_permission_in_community(99999, "shout:read", integration_community.id)
assert has_perm is False
async def test_empty_permission_check(self, db_session, integration_users, integration_community):
"""Тест проверки пустых разрешений"""
# Создаем пользователя без ролей через прямое создание записи (пустые роли)
ca = CommunityAuthor(community_id=integration_community.id, author_id=integration_users[0].id, roles="")
db_session.add(ca)
db_session.commit()
# Проверяем что нет разрешений
assert ca.has_permission("shout:read") is False
assert ca.has_permission("shout:create") is False
permissions = await ca.get_permissions()
assert len(permissions) == 0
class TestDataIntegrity:
"""Тесты целостности данных"""
def test_joined_at_field(self, db_session, integration_users, integration_community):
"""Тест что поле joined_at корректно заполняется"""
# Назначаем роль через функцию
assign_role_to_user(integration_users[0].id, "reader", integration_community.id)
# Получаем созданную запись
from services.db import local_session
with local_session() as session:
ca = CommunityAuthor.find_by_user_and_community(integration_users[0].id, integration_community.id, session)
# Проверяем что joined_at заполнено
assert ca.joined_at is not None
assert isinstance(ca.joined_at, int)
assert ca.joined_at > 0
def test_roles_field_constraints(self, db_session, integration_users, integration_community):
"""Тест ограничений поля roles"""
# Тест с пустой строкой ролей
ca = CommunityAuthor(community_id=integration_community.id, author_id=integration_users[0].id, roles="")
db_session.add(ca)
db_session.commit()
assert ca.role_list == []
# Тест с None
ca.roles = None
db_session.commit()
assert ca.role_list == []
def test_unique_constraints(self, db_session, integration_users, integration_community):
"""Тест уникальных ограничений"""
# Создаем первую запись через функцию
assign_role_to_user(integration_users[0].id, "reader", integration_community.id)
# Попытка создать дублирующуюся запись должна вызвать ошибку
ca2 = CommunityAuthor(community_id=integration_community.id, author_id=integration_users[0].id, roles="author")
db_session.add(ca2)
with pytest.raises(Exception): # IntegrityError или подобная
db_session.commit()
class TestCommunitySettings:
"""Тесты настроек сообщества для ролей"""
def test_default_roles_management(self, db_session, integration_community):
"""Тест управления дефолтными ролями"""
# Проверяем дефолтные роли по умолчанию
default_roles = integration_community.get_default_roles()
assert "reader" in default_roles
# Устанавливаем новые дефолтные роли
integration_community.set_default_roles(["reader", "author"])
new_default_roles = integration_community.get_default_roles()
assert set(new_default_roles) == {"reader", "author"}
def test_available_roles_management(self, integration_community):
"""Тест управления доступными ролями"""
# Проверяем доступные роли по умолчанию
available_roles = integration_community.get_available_roles()
expected_roles = ["reader", "author", "artist", "expert", "editor", "admin"]
assert set(available_roles) == set(expected_roles)
def test_assign_default_roles(self, db_session, integration_users, integration_community):
"""Тест назначения дефолтных ролей"""
# Устанавливаем дефолтные роли
integration_community.set_default_roles(["reader", "author"])
# Назначаем дефолтные роли пользователю
integration_community.assign_default_roles_to_user(integration_users[0].id)
# Проверяем что роли назначились
roles = integration_community.get_user_roles(integration_users[0].id)
assert set(roles) == {"reader", "author"}

413
tests/test_rbac_system.py Normal file
View File

@@ -0,0 +1,413 @@
"""
Тесты для новой системы RBAC (Role-Based Access Control).
Проверяет работу системы ролей и разрешений на основе CSV хранения
в таблице CommunityAuthor.
"""
import pytest
from auth.orm import Author
from orm.community import Community, CommunityAuthor
from services.rbac import get_role_permissions_for_community, get_permissions_for_role
from orm.reaction import REACTION_KINDS
@pytest.fixture
def test_users(db_session):
"""Создает тестовых пользователей"""
users = []
# Создаем пользователей с ID 1-5
for i in range(1, 6):
user = db_session.query(Author).filter(Author.id == i).first()
if not user:
user = Author(id=i, email=f"user{i}@example.com", name=f"Test User {i}", slug=f"test-user-{i}")
user.set_password("password123")
db_session.add(user)
users.append(user)
db_session.commit()
return users
@pytest.fixture
def test_community(db_session, test_users):
"""Создает тестовое сообщество"""
community = db_session.query(Community).filter(Community.id == 1).first()
if not community:
community = Community(
id=1,
name="Test Community",
slug="test-community",
desc="Test community for RBAC tests",
created_by=test_users[0].id,
)
db_session.add(community)
db_session.commit()
return community
class TestCommunityAuthorRoles:
"""Тесты для управления ролями в CommunityAuthor"""
def test_role_list_property(self, db_session, test_users, test_community):
"""Тест свойства role_list для CSV ролей"""
# Очищаем существующие записи для этого пользователя
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[0].id
).delete()
db_session.commit()
# Создаем запись с ролями
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[0].id, roles="reader,author,expert")
db_session.add(ca)
db_session.commit()
# Проверяем получение списка ролей
assert ca.role_list == ["reader", "author", "expert"]
# Проверяем установку списка ролей
ca.role_list = ["admin", "editor"]
assert ca.roles == "admin,editor"
# Проверяем пустые роли
ca.role_list = []
assert ca.roles is None
assert ca.role_list == []
def test_has_role(self, db_session, test_users, test_community):
"""Тест проверки наличия роли"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[1].id
).delete()
db_session.commit()
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[1].id, roles="reader,author")
db_session.add(ca)
db_session.commit()
# Проверяем существующие роли
assert ca.has_role("reader") is True
assert ca.has_role("author") is True
# Проверяем несуществующие роли
assert ca.has_role("admin") is False
assert ca.has_role("editor") is False
def test_add_role(self, db_session, test_users, test_community):
"""Тест добавления роли"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[2].id
).delete()
db_session.commit()
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[2].id, roles="reader")
db_session.add(ca)
db_session.commit()
# Добавляем новую роль
ca.add_role("author")
assert ca.role_list == ["reader", "author"]
# Попытка добавить существующую роль (не должна дублироваться)
ca.add_role("reader")
assert ca.role_list == ["reader", "author"]
# Добавляем ещё одну роль
ca.add_role("expert")
assert ca.role_list == ["reader", "author", "expert"]
def test_remove_role(self, db_session, test_users, test_community):
"""Тест удаления роли"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[3].id
).delete()
db_session.commit()
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[3].id, roles="reader,author,expert")
db_session.add(ca)
db_session.commit()
# Удаляем роль
ca.remove_role("author")
assert ca.role_list == ["reader", "expert"]
# Попытка удалить несуществующую роль (не должна ломаться)
ca.remove_role("admin")
assert ca.role_list == ["reader", "expert"]
# Удаляем все роли
ca.remove_role("reader")
ca.remove_role("expert")
assert ca.role_list == []
def test_set_roles(self, db_session, test_users, test_community):
"""Тест установки полного списка ролей"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[4].id
).delete()
db_session.commit()
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[4].id, roles="reader")
db_session.add(ca)
db_session.commit()
# Устанавливаем новый список ролей
ca.set_roles(["admin", "editor", "expert"])
assert ca.role_list == ["admin", "editor", "expert"]
# Очищаем роли
ca.set_roles([])
assert ca.role_list == []
class TestPermissionsSystem:
"""Тесты для системы разрешений"""
async def test_get_permissions_for_role(self):
"""Тест получения разрешений для роли"""
community_id = 1 # Используем основное сообщество
# Проверяем базовые роли
reader_perms = await get_permissions_for_role("reader", community_id)
assert "shout:read" in reader_perms
assert "shout:create" not in reader_perms
author_perms = await get_permissions_for_role("author", community_id)
assert "shout:create" in author_perms
assert "draft:create" in author_perms
assert "shout:delete_any" not in author_perms
admin_perms = await get_permissions_for_role("admin", community_id)
assert "author:delete_any" in admin_perms
assert "author:update_any" in admin_perms
# Проверяем несуществующую роль
unknown_perms = await get_permissions_for_role("unknown_role", community_id)
assert unknown_perms == []
async def test_reaction_permissions_generation(self):
"""Тест генерации разрешений для реакций"""
community_id = 1 # Используем основное сообщество
# Проверяем что система генерирует разрешения для реакций
admin_perms = await get_permissions_for_role("admin", community_id)
# Админ должен иметь все разрешения на реакции
assert len(admin_perms) > 0, "Admin should have some permissions"
# Проверяем что есть хотя бы базовые разрешения на реакции у читателей
reader_perms = await get_permissions_for_role("reader", community_id)
assert len(reader_perms) > 0, "Reader should have some permissions"
# Проверяем что у reader есть разрешения на чтение реакций
assert any("reaction:read:" in perm for perm in reader_perms), "Reader should have reaction read permissions"
async def test_community_author_get_permissions(self, db_session, test_users, test_community):
"""Тест получения разрешений через CommunityAuthor"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[0].id
).delete()
db_session.commit()
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[0].id, roles="reader,author")
db_session.add(ca)
db_session.commit()
permissions = await ca.get_permissions()
# Должны быть разрешения от обеих ролей
assert "shout:read" in permissions # От reader
assert "shout:create" in permissions # От author
assert len(permissions) > 0 # Должны быть какие-то разрешения
async def test_community_author_has_permission(self, db_session, test_users, test_community):
"""Тест проверки разрешения через CommunityAuthor"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[1].id
).delete()
db_session.commit()
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[1].id, roles="expert,editor")
db_session.add(ca)
db_session.commit()
# Проверяем разрешения
permissions = await ca.get_permissions()
# Expert имеет разрешения на реакции PROOF/DISPROOF
assert any("reaction:create:PROOF" in perm for perm in permissions)
# Editor имеет разрешения на удаление и обновление шаутов
assert "shout:delete_any" in permissions
assert "shout:update_any" in permissions
class TestClassMethods:
"""Тесты для классовых методов CommunityAuthor"""
async def test_find_by_user_and_community(self, db_session, test_users, test_community):
"""Тест поиска записи CommunityAuthor"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[0].id
).delete()
db_session.commit()
# Создаем запись
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[0].id, roles="reader,author")
db_session.add(ca)
db_session.commit()
# Ищем существующую запись
found = CommunityAuthor.find_by_user_and_community(test_users[0].id, test_community.id, db_session)
assert found is not None
assert found.author_id == test_users[0].id
assert found.community_id == test_community.id
# Ищем несуществующую запись
not_found = CommunityAuthor.find_by_user_and_community(test_users[1].id, test_community.id, db_session)
assert not_found is None
async def test_get_users_with_role(self, db_session, test_users, test_community):
"""Тест получения пользователей с определенной ролью"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(CommunityAuthor.community_id == test_community.id).delete()
db_session.commit()
# Создаем пользователей с разными ролями
cas = [
CommunityAuthor(community_id=test_community.id, author_id=test_users[0].id, roles="reader,author"),
CommunityAuthor(community_id=test_community.id, author_id=test_users[1].id, roles="reader,expert"),
CommunityAuthor(community_id=test_community.id, author_id=test_users[2].id, roles="admin"),
]
for ca in cas:
db_session.add(ca)
db_session.commit()
# Ищем пользователей с ролью reader
readers = CommunityAuthor.get_users_with_role(test_community.id, "reader", db_session)
assert test_users[0].id in readers
assert test_users[1].id in readers
assert test_users[2].id not in readers
# Ищем пользователей с ролью admin
admins = CommunityAuthor.get_users_with_role(test_community.id, "admin", db_session)
assert test_users[2].id in admins
assert test_users[0].id not in admins
class TestEdgeCases:
"""Тесты для граничных случаев"""
async def test_empty_roles_handling(self, db_session, test_users, test_community):
"""Тест обработки пустых ролей"""
# Создаем запись с пустыми ролями
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[0].id, roles="")
db_session.add(ca)
db_session.commit()
assert ca.role_list == []
permissions = await ca.get_permissions()
assert permissions == []
async def test_none_roles_handling(self, db_session, test_users, test_community):
"""Тест обработки NULL ролей"""
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[0].id, roles=None)
db_session.add(ca)
db_session.commit()
assert ca.role_list == []
assert await ca.get_permissions() == []
async def test_whitespace_roles_handling(self, db_session, test_users, test_community):
"""Тест обработки ролей с пробелами"""
ca = CommunityAuthor(
community_id=test_community.id, author_id=test_users[0].id, roles=" reader , author , expert "
)
db_session.add(ca)
db_session.commit()
# Пробелы должны убираться
assert ca.role_list == ["reader", "author", "expert"]
async def test_duplicate_roles_handling(self, db_session, test_users, test_community):
"""Тест обработки дублирующихся ролей"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[0].id
).delete()
db_session.commit()
ca = CommunityAuthor(
community_id=test_community.id, author_id=test_users[0].id, roles="reader,author,reader,expert,author"
)
db_session.add(ca)
db_session.commit()
# При установке через set_roles дубликаты должны убираться
unique_roles = set(["reader", "author", "reader", "expert"])
ca.set_roles(unique_roles)
roles = ca.role_list
# Проверяем что нет дубликатов
assert len(roles) == len(set(roles))
assert "reader" in roles
assert "author" in roles
assert "expert" in roles
async def test_invalid_role(self):
"""Тест получения разрешений для несуществующих ролей"""
community_id = 1 # Используем основное сообщество
# Проверяем что несуществующая роль не ломает систему
perms = await get_permissions_for_role("nonexistent_role", community_id)
assert perms == []
class TestPerformance:
"""Тесты производительности (базовые)"""
async def test_large_role_list_performance(self, db_session, test_users, test_community):
"""Тест производительности с большим количеством ролей"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[0].id
).delete()
db_session.commit()
# Создаем запись с множеством ролей
many_roles = ",".join([f"role_{i}" for i in range(50)]) # Уменьшим количество
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[0].id, roles=many_roles)
db_session.add(ca)
db_session.commit()
# Операции должны работать быстро даже с множеством ролей
role_list = ca.role_list
assert len(role_list) == 50
assert all(role.startswith("role_") for role in role_list)
async def test_permissions_caching_behavior(self, db_session, test_users, test_community):
"""Тест поведения кеширования разрешений"""
# Очищаем существующие записи
db_session.query(CommunityAuthor).filter(
CommunityAuthor.community_id == test_community.id, CommunityAuthor.author_id == test_users[1].id
).delete()
db_session.commit()
ca = CommunityAuthor(community_id=test_community.id, author_id=test_users[1].id, roles="reader,author,expert")
db_session.add(ca)
db_session.commit()
# Многократный вызов get_permissions должен работать стабильно
perms1 = await ca.get_permissions()
perms2 = await ca.get_permissions()
perms3 = await ca.get_permissions()
assert perms1.sort() == perms2.sort() == perms3.sort()
assert len(perms1) > 0

View File

@@ -2,25 +2,15 @@ from datetime import datetime
import pytest
from auth.orm import Author, AuthorRole, Role
from auth.orm import Author
from orm.community import CommunityAuthor
from orm.reaction import ReactionKind
from orm.shout import Shout
from resolvers.reaction import create_reaction
def ensure_test_user_with_roles(db_session):
"""Создает тестового пользователя с ID 1 и назначает ему роли"""
# Создаем роли если их нет
reader_role = db_session.query(Role).filter(Role.id == "reader").first()
if not reader_role:
reader_role = Role(id="reader", name="Читатель")
db_session.add(reader_role)
author_role = db_session.query(Role).filter(Role.id == "author").first()
if not author_role:
author_role = Role(id="author", name="Автор")
db_session.add(author_role)
"""Создает тестового пользователя с ID 1 и назначает ему роли через CSV"""
# Создаем пользователя с ID 1 если его нет
test_user = db_session.query(Author).filter(Author.id == 1).first()
if not test_user:
@@ -29,13 +19,24 @@ def ensure_test_user_with_roles(db_session):
db_session.add(test_user)
db_session.flush()
# Удаляем старые роли и добавляем новые
db_session.query(AuthorRole).filter(AuthorRole.author == 1).delete()
# Создаем связь пользователя с сообществом с ролями через CSV
community_author = (
db_session.query(CommunityAuthor)
.filter(CommunityAuthor.community_id == 1, CommunityAuthor.author_id == 1)
.first()
)
# Добавляем роли
for role_id in ["reader", "author"]:
author_role_link = AuthorRole(community=1, author=1, role=role_id)
db_session.add(author_role_link)
if not community_author:
community_author = CommunityAuthor(
community_id=1,
author_id=1,
roles="reader,author", # Роли через CSV
joined_at=int(datetime.now().timestamp()),
)
db_session.add(community_author)
else:
# Обновляем роли если связь уже существует
community_author.roles = "reader,author"
db_session.commit()
return test_user

View File

@@ -2,43 +2,12 @@ from datetime import datetime
import pytest
from auth.orm import Author, AuthorRole, Role
from auth.orm import Author
from orm.community import CommunityAuthor
from orm.shout import Shout
from resolvers.reader import get_shout
def ensure_test_user_with_roles(db_session):
"""Создает тестового пользователя с ID 1 и назначает ему роли"""
# Создаем роли если их нет
reader_role = db_session.query(Role).filter(Role.id == "reader").first()
if not reader_role:
reader_role = Role(id="reader", name="Читатель")
db_session.add(reader_role)
author_role = db_session.query(Role).filter(Role.id == "author").first()
if not author_role:
author_role = Role(id="author", name="Автор")
db_session.add(author_role)
# Создаем пользователя с ID 1 если его нет
test_user = db_session.query(Author).filter(Author.id == 1).first()
if not test_user:
test_user = Author(id=1, email="test@example.com", name="Test User", slug="test-user")
test_user.set_password("password123")
db_session.add(test_user)
db_session.flush()
# Удаляем старые роли и добавляем новые
db_session.query(AuthorRole).filter(AuthorRole.author == 1).delete()
# Добавляем роли
for role_id in ["reader", "author"]:
author_role_link = AuthorRole(community=1, author=1, role=role_id)
db_session.add(author_role_link)
db_session.commit()
return test_user
class MockInfo:
"""Мок для GraphQL info объекта"""
@@ -85,7 +54,13 @@ class MockName:
@pytest.fixture
def test_shout(db_session):
"""Create test shout with required fields."""
author = ensure_test_user_with_roles(db_session)
author = Author(id=1, email="test@example.com", name="Test User", slug="test-user")
author.set_password("password123")
author.set_email_verified(True)
ca = CommunityAuthor(community_id=1, author_id=author.id, roles="reader,author")
db_session.add(author)
db_session.add(ca)
db_session.commit()
now = int(datetime.now().timestamp())
# Создаем публикацию со всеми обязательными полями

View File

@@ -17,7 +17,8 @@ from pathlib import Path
sys.path.append(str(Path(__file__).parent))
from auth.orm import Author, AuthorRole, Role
from auth.orm import Author
from orm.community import assign_role_to_user
from orm.shout import Shout
from resolvers.editor import unpublish_shout
from services.db import local_session
@@ -27,44 +28,6 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(mess
logger = logging.getLogger(__name__)
def ensure_roles_exist():
"""Создает стандартные роли в БД если их нет"""
with local_session() as session:
# Создаем базовые роли если их нет
roles_to_create = [
("reader", "Читатель"),
("author", "Автор"),
("editor", "Редактор"),
("admin", "Администратор"),
]
for role_id, role_name in roles_to_create:
role = session.query(Role).filter(Role.id == role_id).first()
if not role:
role = Role(id=role_id, name=role_name)
session.add(role)
session.commit()
def add_roles_to_author(author_id: int, roles: list[str]):
"""Добавляет роли пользователю в БД"""
with local_session() as session:
# Удаляем старые роли
session.query(AuthorRole).filter(AuthorRole.author == author_id).delete()
# Добавляем новые роли
for role_id in roles:
author_role = AuthorRole(
community=1, # Основное сообщество
author=author_id,
role=role_id,
)
session.add(author_role)
session.commit()
class MockInfo:
"""Мок для GraphQL info контекста"""
@@ -88,9 +51,6 @@ async def setup_test_data() -> tuple[Author, Shout, Author]:
"""Создаем тестовые данные: автора, публикацию и другого автора"""
logger.info("🔧 Настройка тестовых данных")
# Создаем роли в БД
ensure_roles_exist()
current_time = int(time.time())
with local_session() as session:
@@ -133,8 +93,10 @@ async def setup_test_data() -> tuple[Author, Shout, Author]:
session.commit()
# Добавляем роли пользователям в БД
add_roles_to_author(test_author.id, ["reader", "author"])
add_roles_to_author(other_author.id, ["reader", "author"])
assign_role_to_user(test_author.id, "reader")
assign_role_to_user(test_author.id, "author")
assign_role_to_user(other_author.id, "reader")
assign_role_to_user(other_author.id, "author")
logger.info(
f" ✅ Созданы: автор {test_author.id}, другой автор {other_author.id}, публикация {test_shout.id}"
@@ -191,7 +153,9 @@ async def test_unpublish_by_editor() -> None:
session.commit()
# Добавляем роль "editor" другому автору в БД
add_roles_to_author(other_author.id, ["reader", "author", "editor"])
assign_role_to_user(other_author.id, "reader")
assign_role_to_user(other_author.id, "author")
assign_role_to_user(other_author.id, "editor")
logger.info(" 📝 Тест: Снятие публикации редактором")
info = MockInfo(other_author.id, roles=["reader", "author", "editor"]) # Другой автор с ролью редактора
@@ -243,7 +207,8 @@ async def test_access_denied_scenarios() -> None:
# Тест 2: Не-автор без прав редактора
logger.info(" 📝 Тест 2: Не-автор без прав редактора")
# Убеждаемся что у other_author нет роли editor
add_roles_to_author(other_author.id, ["reader", "author"]) # Только базовые роли
assign_role_to_user(other_author.id, "reader")
assign_role_to_user(other_author.id, "author")
info = MockInfo(other_author.id, roles=["reader", "author"]) # Другой автор без прав редактора
result = await unpublish_shout(None, info, test_shout.id)
@@ -314,28 +279,11 @@ async def cleanup_test_data() -> None:
try:
with local_session() as session:
# Удаляем роли тестовых авторов
test_author = session.query(Author).filter(Author.email == "test_author@example.com").first()
if test_author:
session.query(AuthorRole).filter(AuthorRole.author == test_author.id).delete()
other_author = session.query(Author).filter(Author.email == "other_author@example.com").first()
if other_author:
session.query(AuthorRole).filter(AuthorRole.author == other_author.id).delete()
# Удаляем тестовую публикацию
test_shout = session.query(Shout).filter(Shout.slug == "test-shout-published").first()
if test_shout:
session.delete(test_shout)
# Удаляем тестовых авторов
if test_author:
session.delete(test_author)
if other_author:
session.delete(other_author)
session.commit()
logger.info(" ✅ Тестовые данные очищены")
except Exception as e:
logger.warning(f" ⚠️ Ошибка при очистке: {e}")