Corp/backend/tests/test_schedule_permissions.py

511 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# backend/tests/test_schedule_permissions.py
"""
Тесты для системы прав доступа календарей django-scheduler.
"""
import pytest
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model
from rest_framework.test import APIClient
from rest_framework import status
from schedule.models import Calendar, CalendarRelation, Event, Rule
User = get_user_model()
@pytest.fixture
def api_client():
"""API клиент для тестов."""
return APIClient()
@pytest.fixture
def user1(db):
"""Первый тестовый пользователь."""
return User.objects.create_user(
username='user1',
email='user1@example.com',
password='testpass123',
first_name='User',
last_name='One',
phone_number='+79991234567' # Правильное имя параметра
)
@pytest.fixture
def user2(db):
"""Второй тестовый пользователь."""
return User.objects.create_user(
username='user2',
email='user2@example.com',
password='testpass123',
first_name='User',
last_name='Two',
phone_number='+79991234568'
)
@pytest.fixture
def user3(db):
"""Третий тестовый пользователь."""
return User.objects.create_user(
username='user3',
email='user3@example.com',
password='testpass123',
first_name='User',
last_name='Three',
phone_number='+79991234569'
)
@pytest.mark.django_db
class TestCalendarPermissions:
"""Тесты прав доступа к календарям."""
def test_create_calendar_creates_owner_relation(self, api_client, user1):
"""При создании календаря автоматически создается owner relation."""
api_client.force_authenticate(user=user1)
response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Test Calendar',
})
assert response.status_code == status.HTTP_201_CREATED
calendar_id = response.data['id']
# Проверяем, что создался CalendarRelation с owner
ct = ContentType.objects.get_for_model(User)
relation = CalendarRelation.objects.get(
calendar_id=calendar_id,
content_type=ct,
object_id=user1.id
)
assert relation.distinction == 'owner'
assert relation.inheritable is True
def test_get_calendars_shows_only_accessible(self, api_client, user1, user2):
"""Пользователь видит только свои календари."""
api_client.force_authenticate(user=user1)
# user1 создает календарь
response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'User1 Calendar',
})
assert response.status_code == status.HTTP_201_CREATED
# user2 создает свой календарь
api_client.force_authenticate(user=user2)
response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'User2 Calendar',
})
assert response.status_code == status.HTTP_201_CREATED
# user1 видит только свой календарь
api_client.force_authenticate(user=user1)
response = api_client.get('/api/v1/schedule/calendars/')
assert response.status_code == status.HTTP_200_OK
if isinstance(response.data, dict) and 'results' in response.data:
calendars = response.data['results']
else:
calendars = response.data
assert len(calendars) == 1
assert calendars[0]['name'] == 'User1 Calendar'
def test_add_participant_to_calendar(self, api_client, user1, user2):
"""Owner может добавить участника к календарю."""
api_client.force_authenticate(user=user1)
# Создаем календарь
response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Shared Calendar',
})
calendar_id = response.data['id']
# Добавляем user2 как viewer
response = api_client.post(
f'/api/v1/schedule/calendars/{calendar_id}/add-participant/',
{'user_id': user2.id, 'role': 'viewer'}
)
assert response.status_code == status.HTTP_201_CREATED
assert response.data['user']['id'] == user2.id
assert response.data['distinction'] == 'viewer'
# Теперь user2 должен видеть календарь
api_client.force_authenticate(user=user2)
response = api_client.get('/api/v1/schedule/calendars/')
if isinstance(response.data, dict) and 'results' in response.data:
calendars = response.data['results']
else:
calendars = response.data
assert len(calendars) == 1
assert calendars[0]['id'] == calendar_id
def test_non_owner_cannot_add_participant(self, api_client, user1, user2, user3):
"""Не-owner не может добавлять участников."""
api_client.force_authenticate(user=user1)
# user1 создает календарь
response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Test Calendar',
})
calendar_id = response.data['id']
# Добавляем user2 как editor
api_client.post(
f'/api/v1/schedule/calendars/{calendar_id}/add-participant/',
{'user_id': user2.id, 'role': 'editor'}
)
# user2 пытается добавить user3 - должно быть запрещено
api_client.force_authenticate(user=user2)
response = api_client.post(
f'/api/v1/schedule/calendars/{calendar_id}/add-participant/',
{'user_id': user3.id, 'role': 'viewer'}
)
assert response.status_code == status.HTTP_403_FORBIDDEN
def test_remove_participant_from_calendar(self, api_client, user1, user2):
"""Owner может удалить участника из календаря."""
api_client.force_authenticate(user=user1)
# Создаем календарь и добавляем user2
response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Test Calendar',
})
calendar_id = response.data['id']
api_client.post(
f'/api/v1/schedule/calendars/{calendar_id}/add-participant/',
{'user_id': user2.id, 'role': 'viewer'}
)
# Удаляем user2
response = api_client.delete(
f'/api/v1/schedule/calendars/{calendar_id}/remove-participant/{user2.id}/'
)
assert response.status_code == status.HTTP_204_NO_CONTENT
# user2 больше не видит календарь
api_client.force_authenticate(user=user2)
response = api_client.get('/api/v1/schedule/calendars/')
if isinstance(response.data, dict) and 'results' in response.data:
calendars = response.data['results']
else:
calendars = response.data
assert len(calendars) == 0
def test_cannot_remove_owner(self, api_client, user1):
"""Нельзя удалить owner из календаря."""
api_client.force_authenticate(user=user1)
# Создаем календарь
response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Test Calendar',
})
calendar_id = response.data['id']
# Пытаемся удалить себя (owner)
response = api_client.delete(
f'/api/v1/schedule/calendars/{calendar_id}/remove-participant/{user1.id}/'
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
def test_list_participants(self, api_client, user1, user2, user3):
"""Получение списка участников календаря."""
api_client.force_authenticate(user=user1)
# Создаем календарь
response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Test Calendar',
})
calendar_id = response.data['id']
# Добавляем участников
api_client.post(
f'/api/v1/schedule/calendars/{calendar_id}/add-participant/',
{'user_id': user2.id, 'role': 'editor'}
)
api_client.post(
f'/api/v1/schedule/calendars/{calendar_id}/add-participant/',
{'user_id': user3.id, 'role': 'viewer'}
)
# Получаем список участников
response = api_client.get(
f'/api/v1/schedule/calendars/{calendar_id}/participants/'
)
assert response.status_code == status.HTTP_200_OK
assert len(response.data) == 3 # owner + 2 участника
# Проверяем роли
roles = {p['user']['id']: p['distinction'] for p in response.data}
assert roles[user1.id] == 'owner'
assert roles[user2.id] == 'editor'
assert roles[user3.id] == 'viewer'
@pytest.mark.django_db
class TestRecurringEvents:
"""Тесты повторяющихся событий."""
def test_create_rule(self, api_client, user1):
"""Создание правила повторения."""
api_client.force_authenticate(user=user1)
response = api_client.post('/api/v1/schedule/rules/', {
'name': 'Weekly',
'description': 'Every week on Monday',
'frequency': 'WEEKLY',
'params': 'byweekday:0' # Monday
})
assert response.status_code == status.HTTP_201_CREATED
assert response.data['frequency'] == 'WEEKLY'
assert response.data['frequency_display'] == 'Еженедельно' # Django локализация
def test_create_recurring_event(self, api_client, user1):
"""Создание события с повторением."""
api_client.force_authenticate(user=user1)
# Создаем календарь
cal_response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Test Calendar',
})
calendar_id = cal_response.data['id']
# Создаем правило
rule_response = api_client.post('/api/v1/schedule/rules/', {
'name': 'Daily',
'description': 'Every day',
'frequency': 'DAILY',
'params': ''
})
rule_id = rule_response.data['id']
# Создаем событие с правилом
from datetime import datetime, timedelta
from django.utils import timezone
start = timezone.now()
end = start + timedelta(hours=1)
end_recurring = start + timedelta(days=30)
response = api_client.post('/api/v1/schedule/events/', {
'title': 'Daily Meeting',
'description': 'Team standup',
'start': start.isoformat(),
'end': end.isoformat(),
'calendar': calendar_id,
'rule': rule_id,
'end_recurring_period': end_recurring.isoformat(),
})
assert response.status_code == status.HTTP_201_CREATED
assert response.data['rule'] == rule_id
assert response.data['end_recurring_period'] is not None
def test_get_occurrences(self, api_client, user1):
"""Получение материализованных вхождений для recurring события."""
api_client.force_authenticate(user=user1)
# Создаем календарь
cal_response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Test Calendar',
})
calendar_id = cal_response.data['id']
# Создаем правило - еженедельно
rule_response = api_client.post('/api/v1/schedule/rules/', {
'name': 'Weekly',
'description': 'Every week',
'frequency': 'WEEKLY',
'params': ''
})
rule_id = rule_response.data['id']
# Создаем событие
from datetime import datetime, timedelta
from django.utils import timezone
start = timezone.now()
end = start + timedelta(hours=1)
end_recurring = start + timedelta(weeks=4)
api_client.post('/api/v1/schedule/events/', {
'title': 'Weekly Meeting',
'start': start.isoformat(),
'end': end.isoformat(),
'calendar': calendar_id,
'rule': rule_id,
'end_recurring_period': end_recurring.isoformat(),
})
# Получаем occurrences
response = api_client.get(
'/api/v1/schedule/events/occurrences/',
{
'calendar': calendar_id,
'start': start.isoformat(),
'end': (start + timedelta(weeks=5)).isoformat(),
}
)
assert response.status_code == status.HTTP_200_OK
# Должно быть ~4 вхождения (еженедельно в течение 4 недель)
assert len(response.data) >= 4
@pytest.mark.django_db
class TestiCalendarExport:
"""Тесты экспорта в iCalendar формат."""
def test_export_calendar_to_ics(self, api_client, user1):
"""Экспорт календаря в .ics файл."""
api_client.force_authenticate(user=user1)
# Создаем календарь
cal_response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Test Calendar',
})
calendar_id = cal_response.data['id']
# Добавляем событие
from datetime import datetime, timedelta
from django.utils import timezone
start = timezone.now()
end = start + timedelta(hours=2)
api_client.post('/api/v1/schedule/events/', {
'title': 'Test Event',
'description': 'Test Description',
'start': start.isoformat(),
'end': end.isoformat(),
'calendar': calendar_id,
})
# Экспортируем календарь
response = api_client.get(
f'/api/v1/schedule/calendars/{calendar_id}/export-ical/'
)
assert response.status_code == status.HTTP_200_OK
assert response['Content-Type'] == 'text/calendar'
assert 'Content-Disposition' in response
assert '.ics' in response['Content-Disposition']
# Проверяем содержимое
content = response.content.decode('utf-8')
assert 'BEGIN:VCALENDAR' in content
assert 'BEGIN:VEVENT' in content
assert 'Test Event' in content
assert 'END:VEVENT' in content
assert 'END:VCALENDAR' in content
@pytest.mark.django_db
class TestEventRelations:
"""Тесты участников событий."""
def test_add_participant_to_event(self, api_client, user1, user2):
"""Добавление участника к событию."""
api_client.force_authenticate(user=user1)
# Создаем календарь
cal_response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Test Calendar',
})
calendar_id = cal_response.data['id']
# Создаем событие
from datetime import datetime, timedelta
from django.utils import timezone
start = timezone.now()
end = start + timedelta(hours=1)
event_response = api_client.post('/api/v1/schedule/events/', {
'title': 'Meeting',
'start': start.isoformat(),
'end': end.isoformat(),
'calendar': calendar_id,
})
event_id = event_response.data['id']
# Добавляем user2 как участника
ct = ContentType.objects.get_for_model(User)
response = api_client.post('/api/v1/schedule/relations/', {
'event': event_id,
'content_type': ct.id,
'object_id': user2.id,
'distinction': 'attendee',
})
assert response.status_code == status.HTTP_201_CREATED
assert response.data['event'] == event_id
def test_list_event_participants(self, api_client, user1, user2, user3):
"""Получение списка участников события."""
api_client.force_authenticate(user=user1)
# Создаем календарь и событие
cal_response = api_client.post('/api/v1/schedule/calendars/', {
'name': 'Test Calendar',
})
calendar_id = cal_response.data['id']
from datetime import datetime, timedelta
from django.utils import timezone
start = timezone.now()
end = start + timedelta(hours=1)
event_response = api_client.post('/api/v1/schedule/events/', {
'title': 'Team Meeting',
'start': start.isoformat(),
'end': end.isoformat(),
'calendar': calendar_id,
})
event_id = event_response.data['id']
# Добавляем участников
ct = ContentType.objects.get_for_model(User)
api_client.post('/api/v1/schedule/relations/', {
'event': event_id,
'content_type': ct.id,
'object_id': user2.id,
'distinction': 'attendee',
})
api_client.post('/api/v1/schedule/relations/', {
'event': event_id,
'content_type': ct.id,
'object_id': user3.id,
'distinction': 'attendee',
})
# Получаем список участников
response = api_client.get(
'/api/v1/schedule/relations/',
{'event': event_id}
)
assert response.status_code == status.HTTP_200_OK
if isinstance(response.data, dict) and 'results' in response.data:
relations = response.data['results']
else:
relations = response.data
assert len(relations) == 2