455 lines
17 KiB
Python
455 lines
17 KiB
Python
"""
|
||
Тесты для Inventory - transfer, write_off, QR-коды, инвентарные номера.
|
||
"""
|
||
import pytest
|
||
from rest_framework import status
|
||
|
||
from procurement.models import (
|
||
Equipment,
|
||
EquipmentCategory,
|
||
EquipmentTransferLog,
|
||
MaintenanceRecord,
|
||
)
|
||
from procurement.constants import EquipmentStatus
|
||
from procurement.services import InventoryNumberGenerator, QRCodeGenerator
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestInventoryNumberGenerator:
|
||
"""Тесты для генератора инвентарных номеров."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(self, department_factory, user_factory, link_factory):
|
||
"""Настройка тестовых данных."""
|
||
self.department = department_factory(name='IT Inventory Test')
|
||
self.user = user_factory(staff=True,
|
||
email='inventory_user@test.com',
|
||
first_name='Test',
|
||
last_name='Inventory',
|
||
)
|
||
link_factory(self.user, self.department, is_active=True)
|
||
self.category = EquipmentCategory.objects.create(
|
||
name='Computers',
|
||
description='Компьютеры и ноутбуки',
|
||
)
|
||
|
||
def test_generate_first_number(self):
|
||
"""Тест: генерация первого инвентарного номера за год."""
|
||
from django.utils import timezone
|
||
year = timezone.now().year
|
||
|
||
number = InventoryNumberGenerator.generate()
|
||
|
||
assert number == f'INV-{year}-0001'
|
||
|
||
def test_generate_sequential_numbers(self):
|
||
"""Тест: последовательная генерация номеров."""
|
||
from django.utils import timezone
|
||
year = timezone.now().year
|
||
|
||
# Создаём оборудование с первым номером
|
||
Equipment.objects.create(
|
||
name='Laptop 1',
|
||
inventory_number=f'INV-{year}-0005',
|
||
category=self.category,
|
||
department=self.department,
|
||
responsible_person=self.user,
|
||
status=EquipmentStatus.IN_USE,
|
||
purchase_date='2025-01-01',
|
||
purchase_cost='10000.00',
|
||
)
|
||
|
||
number = InventoryNumberGenerator.generate()
|
||
|
||
assert number == f'INV-{year}-0006'
|
||
|
||
def test_validate_correct_format(self):
|
||
"""Тест: валидация правильного формата."""
|
||
assert InventoryNumberGenerator.validate('INV-2025-0001') is True
|
||
assert InventoryNumberGenerator.validate('INV-2024-9999') is True
|
||
|
||
def test_validate_incorrect_format(self):
|
||
"""Тест: валидация неправильного формата."""
|
||
assert InventoryNumberGenerator.validate('INV-2025') is False
|
||
assert InventoryNumberGenerator.validate('ABC-2025-0001') is False
|
||
assert InventoryNumberGenerator.validate('') is False
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestQRCodeGenerator:
|
||
"""Тесты для генератора QR-кодов."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(self, department_factory, user_factory, link_factory):
|
||
"""Настройка тестовых данных."""
|
||
self.department = department_factory(name='IT QR Test')
|
||
self.user = user_factory(staff=True,
|
||
email='qr_user@test.com',
|
||
first_name='Test',
|
||
last_name='QR',
|
||
)
|
||
link_factory(self.user, self.department, is_active=True)
|
||
self.category = EquipmentCategory.objects.create(
|
||
name='Monitors',
|
||
description='Мониторы',
|
||
)
|
||
self.equipment = Equipment.objects.create(
|
||
name='Monitor Dell 27',
|
||
inventory_number='INV-2025-0100',
|
||
category=self.category,
|
||
department=self.department,
|
||
responsible_person=self.user,
|
||
status=EquipmentStatus.IN_USE,
|
||
purchase_date='2025-01-01',
|
||
purchase_cost='10000.00',
|
||
)
|
||
|
||
def test_generate_qr_code(self):
|
||
"""Тест: генерация QR-кода для оборудования."""
|
||
qr_file = QRCodeGenerator.generate_for_equipment(self.equipment)
|
||
|
||
assert qr_file is not None
|
||
assert qr_file.name.endswith('.png')
|
||
# Файл должен содержать PNG данные
|
||
content = qr_file.read()
|
||
assert len(content) > 0
|
||
# PNG начинается с signature
|
||
assert content[:4] == b'\x89PNG'
|
||
|
||
def test_qr_code_path(self):
|
||
"""Тест: путь к файлу QR-кода."""
|
||
path = QRCodeGenerator.get_qr_code_path('INV-2025-0100')
|
||
|
||
assert 'INV-2025-0100.png' in path
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestEquipmentTransferEndpoint:
|
||
"""Тесты для /equipment/{id}/transfer/ endpoint."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(
|
||
self, api_client, department_factory, user_factory, link_factory
|
||
):
|
||
"""Настройка тестовых данных."""
|
||
self.client = api_client
|
||
self.dept1 = department_factory(name='IT Dept')
|
||
self.dept2 = department_factory(name='HR Dept')
|
||
|
||
self.user1 = user_factory(staff=True,
|
||
email='transfer_user1@test.com',
|
||
first_name='User',
|
||
last_name='One',
|
||
)
|
||
self.user2 = user_factory(
|
||
email='transfer_user2@test.com',
|
||
first_name='User',
|
||
last_name='Two',
|
||
)
|
||
link_factory(self.user1, self.dept1, is_active=True)
|
||
link_factory(self.user2, self.dept2, is_active=True)
|
||
|
||
self.category = EquipmentCategory.objects.create(
|
||
name='Laptops',
|
||
description='Ноутбуки',
|
||
)
|
||
self.equipment = Equipment.objects.create(
|
||
name='MacBook Pro',
|
||
inventory_number='INV-2025-0200',
|
||
category=self.category,
|
||
department=self.dept1,
|
||
responsible_person=self.user1,
|
||
status=EquipmentStatus.IN_USE,
|
||
purchase_date='2025-01-01',
|
||
purchase_cost='10000.00',
|
||
)
|
||
|
||
def test_transfer_to_another_department(self):
|
||
"""Тест: перевод оборудования в другой отдел."""
|
||
self.client.force_authenticate(user=self.user1)
|
||
|
||
response = self.client.post(
|
||
f'/api/v1/procurement/equipment/{self.equipment.id}/transfer/',
|
||
{
|
||
'to_department': self.dept2.id,
|
||
'reason': 'Переезд сотрудника',
|
||
}
|
||
)
|
||
|
||
assert response.status_code == status.HTTP_200_OK
|
||
assert response.data['status'] == 'transferred'
|
||
|
||
# Проверяем обновление оборудования
|
||
self.equipment.refresh_from_db()
|
||
assert self.equipment.department == self.dept2
|
||
|
||
# Проверяем лог перевода
|
||
log = EquipmentTransferLog.objects.filter(
|
||
equipment=self.equipment
|
||
).first()
|
||
assert log is not None
|
||
assert log.from_department == self.dept1
|
||
assert log.to_department == self.dept2
|
||
assert log.reason == 'Переезд сотрудника'
|
||
|
||
def test_transfer_to_another_person(self):
|
||
"""Тест: перевод оборудования другому пользователю."""
|
||
self.client.force_authenticate(user=self.user1)
|
||
|
||
response = self.client.post(
|
||
f'/api/v1/procurement/equipment/{self.equipment.id}/transfer/',
|
||
{
|
||
'to_person': self.user2.id,
|
||
'reason': 'Смена ответственного',
|
||
}
|
||
)
|
||
|
||
assert response.status_code == status.HTTP_200_OK
|
||
|
||
self.equipment.refresh_from_db()
|
||
assert self.equipment.responsible_person == self.user2
|
||
|
||
def test_transfer_requires_target(self):
|
||
"""Тест: нужно указать отдел или пользователя."""
|
||
self.client.force_authenticate(user=self.user1)
|
||
|
||
response = self.client.post(
|
||
f'/api/v1/procurement/equipment/{self.equipment.id}/transfer/',
|
||
{'reason': 'Без цели'},
|
||
)
|
||
|
||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestEquipmentWriteOffEndpoint:
|
||
"""Тесты для /equipment/{id}/write_off/ endpoint."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(
|
||
self, api_client, department_factory, user_factory, link_factory
|
||
):
|
||
"""Настройка тестовых данных."""
|
||
self.client = api_client
|
||
self.department = department_factory(name='IT WriteOff')
|
||
self.user = user_factory(staff=True,
|
||
email='writeoff_user@test.com',
|
||
first_name='Test',
|
||
last_name='WriteOff',
|
||
)
|
||
link_factory(self.user, self.department, is_active=True)
|
||
|
||
self.category = EquipmentCategory.objects.create(
|
||
name='Printers',
|
||
description='Принтеры',
|
||
)
|
||
self.equipment = Equipment.objects.create(
|
||
name='HP LaserJet',
|
||
inventory_number='INV-2025-0300',
|
||
category=self.category,
|
||
department=self.department,
|
||
responsible_person=self.user,
|
||
status=EquipmentStatus.IN_USE,
|
||
purchase_date='2025-01-01',
|
||
purchase_cost='10000.00',
|
||
)
|
||
|
||
def test_write_off_equipment(self):
|
||
"""Тест: списание оборудования."""
|
||
self.client.force_authenticate(user=self.user)
|
||
|
||
response = self.client.post(
|
||
f'/api/v1/procurement/equipment/{self.equipment.id}/write_off/',
|
||
{'reason': 'Износ'}
|
||
)
|
||
|
||
assert response.status_code == status.HTTP_200_OK
|
||
assert response.data['status'] == 'written_off'
|
||
|
||
self.equipment.refresh_from_db()
|
||
assert self.equipment.status == EquipmentStatus.RETIRED
|
||
assert 'Износ' in self.equipment.notes
|
||
|
||
def test_cannot_write_off_twice(self):
|
||
"""Тест: нельзя списать уже списанное оборудование."""
|
||
self.equipment.status = EquipmentStatus.RETIRED
|
||
self.equipment.save()
|
||
|
||
self.client.force_authenticate(user=self.user)
|
||
|
||
response = self.client.post(
|
||
f'/api/v1/procurement/equipment/{self.equipment.id}/write_off/',
|
||
{'reason': 'Повторно'}
|
||
)
|
||
|
||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestEquipmentMaintenanceEndpoint:
|
||
"""Тесты для /equipment/{id}/add_maintenance/ endpoint."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(
|
||
self, api_client, department_factory, user_factory, link_factory
|
||
):
|
||
"""Настройка тестовых данных."""
|
||
self.client = api_client
|
||
self.department = department_factory(name='IT Maintenance')
|
||
self.user = user_factory(staff=True,
|
||
email='maintenance_user@test.com',
|
||
first_name='Test',
|
||
last_name='Maintenance',
|
||
)
|
||
link_factory(self.user, self.department, is_active=True)
|
||
|
||
self.category = EquipmentCategory.objects.create(
|
||
name='Servers',
|
||
description='Серверы',
|
||
)
|
||
self.equipment = Equipment.objects.create(
|
||
name='Dell PowerEdge',
|
||
inventory_number='INV-2025-0400',
|
||
category=self.category,
|
||
department=self.department,
|
||
responsible_person=self.user,
|
||
status=EquipmentStatus.IN_USE,
|
||
purchase_date='2025-01-01',
|
||
purchase_cost='10000.00',
|
||
)
|
||
|
||
def test_add_maintenance_record(self):
|
||
"""Тест: добавление записи об обслуживании."""
|
||
self.client.force_authenticate(user=self.user)
|
||
|
||
response = self.client.post(
|
||
f'/api/v1/procurement/equipment/{self.equipment.id}/add_maintenance/',
|
||
{
|
||
'type': 'repair',
|
||
'description': 'Замена блока питания',
|
||
'cost': '5000.00',
|
||
}
|
||
)
|
||
|
||
assert response.status_code == status.HTTP_201_CREATED
|
||
assert 'maintenance_id' in response.data
|
||
|
||
# Проверяем создание записи
|
||
record = MaintenanceRecord.objects.get(id=response.data['maintenance_id'])
|
||
assert record.equipment == self.equipment
|
||
assert record.description == 'Замена блока питания'
|
||
assert record.performed_by == self.user
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestEquipmentQRCodeEndpoint:
|
||
"""Тесты для /equipment/{id}/qr_code/ endpoint."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(
|
||
self, api_client, department_factory, user_factory, link_factory
|
||
):
|
||
"""Настройка тестовых данных."""
|
||
self.client = api_client
|
||
self.department = department_factory(name='IT QR Code')
|
||
self.user = user_factory(staff=True,
|
||
email='qrcode_user@test.com',
|
||
first_name='Test',
|
||
last_name='QRCode',
|
||
)
|
||
link_factory(self.user, self.department, is_active=True)
|
||
|
||
self.category = EquipmentCategory.objects.create(
|
||
name='Networking',
|
||
description='Сетевое оборудование',
|
||
)
|
||
self.equipment = Equipment.objects.create(
|
||
name='Cisco Router',
|
||
inventory_number='INV-2025-0500',
|
||
category=self.category,
|
||
department=self.department,
|
||
responsible_person=self.user,
|
||
status=EquipmentStatus.IN_USE,
|
||
purchase_date='2025-01-01',
|
||
purchase_cost='10000.00',
|
||
)
|
||
|
||
def test_get_qr_code(self):
|
||
"""Тест: получение QR-кода для оборудования."""
|
||
self.client.force_authenticate(user=self.user)
|
||
|
||
response = self.client.get(
|
||
f'/api/v1/procurement/equipment/{self.equipment.id}/qr_code/'
|
||
)
|
||
|
||
assert response.status_code == status.HTTP_200_OK
|
||
assert response['Content-Type'] == 'image/png'
|
||
# PNG signature
|
||
assert response.content[:4] == b'\x89PNG'
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestEquipmentTransferHistoryEndpoint:
|
||
"""Тесты для /equipment/{id}/transfer_history/ endpoint."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(
|
||
self, api_client, department_factory, user_factory, link_factory
|
||
):
|
||
"""Настройка тестовых данных."""
|
||
self.client = api_client
|
||
self.dept1 = department_factory(name='IT History 1')
|
||
self.dept2 = department_factory(name='IT History 2')
|
||
|
||
self.user1 = user_factory(staff=True,
|
||
email='history_user1@test.com',
|
||
first_name='User',
|
||
last_name='History1',
|
||
)
|
||
self.user2 = user_factory(
|
||
email='history_user2@test.com',
|
||
first_name='User',
|
||
last_name='History2',
|
||
)
|
||
link_factory(self.user1, self.dept1, is_active=True)
|
||
|
||
self.category = EquipmentCategory.objects.create(
|
||
name='Phones',
|
||
description='Телефоны',
|
||
)
|
||
self.equipment = Equipment.objects.create(
|
||
name='iPhone 15',
|
||
inventory_number='INV-2025-0600',
|
||
category=self.category,
|
||
department=self.dept1,
|
||
responsible_person=self.user1,
|
||
status=EquipmentStatus.IN_USE,
|
||
purchase_date='2025-01-01',
|
||
purchase_cost='10000.00',
|
||
)
|
||
|
||
# Создаём историю переводов
|
||
EquipmentTransferLog.objects.create(
|
||
equipment=self.equipment,
|
||
from_department=self.dept1,
|
||
to_department=self.dept2,
|
||
from_person=self.user1,
|
||
to_person=self.user2,
|
||
reason='Первый перевод',
|
||
created_by=self.user1,
|
||
)
|
||
|
||
def test_get_transfer_history(self):
|
||
"""Тест: получение истории переводов."""
|
||
self.client.force_authenticate(user=self.user1)
|
||
|
||
response = self.client.get(
|
||
f'/api/v1/procurement/equipment/{self.equipment.id}/transfer_history/'
|
||
)
|
||
|
||
assert response.status_code == status.HTTP_200_OK
|
||
assert len(response.data) == 1
|
||
assert response.data[0]['reason'] == 'Первый перевод'
|
||
assert 'from_department' in response.data[0]
|
||
assert 'to_department' in response.data[0]
|