320 lines
11 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.

"""
Тесты для API модуля закупок.
"""
from decimal import Decimal
import pytest
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from employees.models import Department, Employee
from procurement.constants import ProcurementStatus, UrgencyLevel
from procurement.models import (
Budget,
Equipment,
EquipmentCategory,
ProcurementRequest,
Supplier,
)
@pytest.fixture
def api_client():
"""API клиент."""
return APIClient()
@pytest.fixture
def user(db):
"""Обычный пользователь."""
return Employee.objects.create_user(
email="user@example.com",
password="testpass123",
phone_number="+79991111111",
first_name="Иван",
last_name="Иванов",
send_activation_email=False,
)
@pytest.fixture
def staff_user(db):
"""Staff пользователь."""
return Employee.objects.create_user(
email="staff@example.com",
password="testpass123",
phone_number="+79992222222",
first_name="Админ",
last_name="Админов",
is_staff=True,
send_activation_email=False,
)
@pytest.fixture
def department(db):
"""Тестовый отдел."""
return Department.objects.create(
name="Тестовый отдел",
description="Для тестов"
)
@pytest.fixture
def procurement_request(db, department, user):
"""Тестовая заявка."""
return ProcurementRequest.objects.create(
title="Тестовая заявка",
description="Описание",
department=department,
requestor=user,
status=ProcurementStatus.DRAFT,
urgency=UrgencyLevel.MEDIUM,
)
@pytest.mark.django_db
class TestProcurementRequestAPI:
"""Тесты API заявок на закупку."""
def test_list_requests_unauthorized(self, api_client):
"""Тест: неавторизованный доступ запрещен."""
url = reverse('api:v1:procurement:procurementrequest-list')
response = api_client.get(url)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_list_requests_authorized(
self, api_client, user, procurement_request
):
"""Тест: авторизованный пользователь видит свои заявки."""
api_client.force_authenticate(user=user)
url = reverse('api:v1:procurement:procurementrequest-list')
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert len(response.data['results']) == 1
assert response.data['results'][0]['title'] == "Тестовая заявка"
def test_create_request(
self, api_client, user, department, link_factory
):
"""Тест: создание заявки."""
# Связываем пользователя с отделом (необходимо для создания заявки)
link_factory(user, department, is_active=True)
api_client.force_authenticate(user=user)
url = reverse('api:v1:procurement:procurementrequest-list')
data = {
'title': 'Новая заявка',
'description': 'Нужны компьютеры',
'department': department.id,
'urgency': UrgencyLevel.HIGH,
'items': [
{
'name': 'Ноутбук',
'quantity': 2,
'unit': 'шт',
'estimated_unit_price': '50000.00',
}
]
}
response = api_client.post(url, data, format='json')
assert response.status_code == status.HTTP_201_CREATED
assert response.data['title'] == 'Новая заявка'
assert response.data['requestor'] == user.id
def test_retrieve_request(
self, api_client, user, procurement_request
):
"""Тест: получение детальной информации о заявке."""
api_client.force_authenticate(user=user)
url = reverse(
'api:v1:procurement:procurementrequest-detail',
kwargs={'pk': procurement_request.id}
)
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert response.data['id'] == procurement_request.id
assert response.data['title'] == "Тестовая заявка"
assert 'total_cost' in response.data
assert 'required_approval_priorities' in response.data
assert 'is_editable' in response.data
def test_update_own_request_draft(
self, api_client, user, procurement_request
):
"""Тест: обновление своей заявки в статусе DRAFT."""
api_client.force_authenticate(user=user)
url = reverse(
'api:v1:procurement:procurementrequest-detail',
kwargs={'pk': procurement_request.id}
)
data = {'title': 'Обновленная заявка'}
response = api_client.patch(url, data, format='json')
assert response.status_code == status.HTTP_200_OK
assert response.data['title'] == 'Обновленная заявка'
@pytest.mark.django_db
class TestEquipmentAPI:
"""Тесты API оборудования."""
def test_list_equipment(
self, api_client, user, department
):
"""Тест: список оборудования."""
api_client.force_authenticate(user=user)
# Создаем категорию и оборудование
category = EquipmentCategory.objects.create(name="Компьютеры")
Equipment.objects.create(
name="Ноутбук Dell",
inventory_number="INV-2025-001",
category=category,
department=department,
purchase_date="2025-01-01",
purchase_cost=Decimal("80000.00"),
)
url = reverse('api:v1:procurement:equipment-list')
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert len(response.data['results']) == 1
def test_create_equipment_staff_only(
self, api_client, user, staff_user, department
):
"""Тест: создание оборудования только для staff."""
category = EquipmentCategory.objects.create(name="Техника")
# Обычный пользователь не может создать
api_client.force_authenticate(user=user)
url = reverse('api:v1:procurement:equipment-list')
data = {
'name': 'Принтер HP',
'inventory_number': 'INV-2025-002',
'category': category.id,
'department': department.id,
'purchase_date': '2025-01-01',
'purchase_cost': '20000.00',
}
response = api_client.post(url, data, format='json')
assert response.status_code == status.HTTP_403_FORBIDDEN
# Staff может создать
api_client.force_authenticate(user=staff_user)
response = api_client.post(url, data, format='json')
assert response.status_code == status.HTTP_201_CREATED
@pytest.mark.django_db
class TestBudgetAPI:
"""Тесты API бюджетов."""
def test_list_budgets_staff(
self, api_client, staff_user, department
):
"""Тест: список бюджетов для staff."""
api_client.force_authenticate(user=staff_user)
Budget.objects.create(
department=department,
year=2025,
quarter=4,
allocated_amount=Decimal("500000.00"),
)
url = reverse('api:v1:procurement:budget-list')
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert len(response.data['results']) == 1
def test_list_budgets_regular_user(
self, api_client, user
):
"""Тест: обычный пользователь не видит бюджеты (нет прав)."""
api_client.force_authenticate(user=user)
url = reverse('api:v1:procurement:budget-list')
response = api_client.get(url)
# Обычный пользователь не имеет доступа к бюджетам
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.django_db
class TestSupplierAPI:
"""Тесты API поставщиков."""
def test_list_suppliers(self, api_client, user):
"""Тест: список поставщиков."""
api_client.force_authenticate(user=user)
Supplier.objects.create(
name="ООО Тестовый поставщик",
rating=Decimal("4.50"),
)
url = reverse('api:v1:procurement:supplier-list')
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert len(response.data['results']) == 1
def test_top_rated_suppliers(self, api_client, user):
"""Тест: лучшие поставщики."""
api_client.force_authenticate(user=user)
Supplier.objects.create(
name="Отличный поставщик",
rating=Decimal("4.80"),
is_active=True,
)
Supplier.objects.create(
name="Плохой поставщик",
rating=Decimal("2.50"),
is_active=True,
)
url = reverse('api:v1:procurement:supplier-top-rated')
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
# Должен вернуть только с рейтингом >= 4.0
assert len(response.data) == 1
assert response.data[0]['name'] == "Отличный поставщик"
@pytest.mark.django_db
class TestEquipmentCategoryAPI:
"""Тесты API категорий оборудования."""
def test_category_tree(self, api_client, user):
"""Тест: дерево категорий."""
api_client.force_authenticate(user=user)
parent = EquipmentCategory.objects.create(name="Компьютеры")
EquipmentCategory.objects.create(
name="Ноутбуки",
parent=parent
)
url = reverse('api:v1:procurement:equipmentcategory-tree')
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
# Должен вернуть только корневые категории
assert len(response.data) == 1
assert response.data[0]['name'] == "Компьютеры"