Files
ACORE/apps/tasks/tests.py
T
lokses 03019898b0 reports: КЕП-подпись PDF отчётов; задачи: загрузка вложений.
Добавлена офлайн/EndUser-подпись «ознакомлен и принял в работу» для документов отчётов и файловые вложения к задачам через Nextcloud.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 21:13:28 +03:00

266 lines
10 KiB
Python

import datetime
from django.test import TestCase
from django.utils import timezone
from apps.employees.models import Employee
from apps.tasks.models import Task
def make_employee(username):
# sync-флаги выключены: тестам не нужны сигналы AD/Mattermost
emp = Employee.objects.create_user(
username=username, first_name='T', last_name='User',
hire_date=datetime.date(2020, 1, 1),
mattermost_sync_enabled=False, ad_sync_enabled=False,
)
# Любая группа = сотрудник видит открытые разделы (например /tasks/).
from django.contrib.auth.models import Group
emp.groups.add(Group.objects.get_or_create(name='Сотрудники (тест)')[0])
return emp
class TaskVisibilityTests(TestCase):
"""TaskQuerySet.for_user — задачу видят автор, відповідальний,
співвиконавець и спостерігач; посторонний — нет."""
@classmethod
def setUpTestData(cls):
cls.creator = make_employee('creator')
cls.responsible = make_employee('responsible')
cls.co = make_employee('co')
cls.observer = make_employee('observer')
cls.outsider = make_employee('outsider')
cls.task = Task.objects.create(title='T', creator=cls.creator)
cls.task.responsible.add(cls.responsible)
cls.task.co_executors.add(cls.co)
cls.task.observers.add(cls.observer)
def test_for_user_includes_all_roles(self):
for user in (self.creator, self.responsible, self.co, self.observer):
self.assertIn(self.task, Task.objects.for_user(user), msg=user.username)
def test_for_user_excludes_outsider(self):
self.assertNotIn(self.task, Task.objects.for_user(self.outsider))
def test_for_user_distinct(self):
# один и тот же пользователь в двух ролях → одна строка, не дубль
self.task.co_executors.add(self.responsible)
self.assertEqual(Task.objects.for_user(self.responsible).count(), 1)
class TaskParticipantTests(TestCase):
"""is_participant — статус меняют автор/відповідальний/співвиконавець,
спостерігач и посторонний — нет."""
@classmethod
def setUpTestData(cls):
cls.creator = make_employee('creator')
cls.responsible = make_employee('responsible')
cls.co = make_employee('co')
cls.observer = make_employee('observer')
cls.outsider = make_employee('outsider')
cls.task = Task.objects.create(title='T', creator=cls.creator)
cls.task.responsible.add(cls.responsible)
cls.task.co_executors.add(cls.co)
cls.task.observers.add(cls.observer)
def test_participants_can_change_status(self):
self.assertTrue(self.task.is_participant(self.creator))
self.assertTrue(self.task.is_participant(self.responsible))
self.assertTrue(self.task.is_participant(self.co))
def test_observer_and_outsider_cannot(self):
self.assertFalse(self.task.is_participant(self.observer))
self.assertFalse(self.task.is_participant(self.outsider))
class TaskStatusTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = make_employee('u')
def _task(self, **kw):
return Task.objects.create(title='t', creator=self.user, **kw)
def test_overdue_when_past_due_and_open(self):
t = self._task(due_date=timezone.localdate() - datetime.timedelta(days=1))
self.assertTrue(t.is_overdue)
def test_not_overdue_when_done(self):
t = self._task(status='done',
due_date=timezone.localdate() - datetime.timedelta(days=1))
self.assertFalse(t.is_overdue)
def test_not_overdue_without_due_date(self):
self.assertFalse(self._task().is_overdue)
def test_set_status_done_sets_completed_at(self):
t = self._task()
t.set_status('done')
self.assertEqual(t.status, 'done')
self.assertIsNotNone(t.completed_at)
def test_set_status_non_done_clears_completed_at(self):
t = self._task(status='done', completed_at=timezone.now())
t.set_status('in_progress')
self.assertIsNone(t.completed_at)
class TaskChecklistViewTests(TestCase):
"""Відмітка пунктів чек-листа в деталі задачі — тільки учасник."""
@classmethod
def setUpTestData(cls):
from apps.tasks.models import TaskChecklistItem
cls.creator = make_employee('cl_creator')
cls.executor = make_employee('cl_exec')
cls.outsider = make_employee('cl_out')
cls.task = Task.objects.create(title='T', creator=cls.creator)
cls.task.co_executors.add(cls.executor)
cls.item = TaskChecklistItem.objects.create(task=cls.task, label='Пункт', order=0)
def _toggle(self, user):
from django.test import Client
c = Client()
c.force_login(user)
return c.post(f'/tasks/{self.task.pk}/',
{'action': 'checklist_toggle', 'item_id': self.item.pk},
HTTP_HX_REQUEST='true')
def test_participant_can_toggle(self):
self._toggle(self.executor)
self.item.refresh_from_db()
self.assertTrue(self.item.is_done)
self.assertEqual(self.item.done_by, self.executor)
def test_outsider_cannot_toggle(self):
resp = self._toggle(self.outsider)
self.assertEqual(resp.status_code, 404)
self.item.refresh_from_db()
self.assertFalse(self.item.is_done)
class TaskAttachmentTests(TestCase):
"""Загрузка / скачивание / удаление вложений задачи."""
@classmethod
def setUpTestData(cls):
cls.creator = make_employee('att_creator')
cls.responsible = make_employee('att_resp')
cls.outsider = make_employee('att_out')
cls.task = Task.objects.create(title='T', creator=cls.creator)
cls.task.responsible.add(cls.responsible)
def test_unique_stored_name_adds_suffix(self):
from apps.tasks.attachments import unique_stored_name
from apps.tasks.models import TaskAttachment
TaskAttachment.objects.create(
task=self.task, original_name='a.pdf', stored_name='a.pdf',
uploaded_by=self.creator,
)
self.assertEqual(unique_stored_name(self.task.pk, 'a.pdf'), 'a-2.pdf')
def test_participant_can_upload(self):
from unittest.mock import patch
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import Client
from apps.tasks.models import TaskAttachment
c = Client()
c.force_login(self.responsible)
f = SimpleUploadedFile('plan.pdf', b'%PDF-1.4', content_type='application/pdf')
with patch('apps.nextcloud_connector.webdav_service.NextcloudWebDAVService') as MockDAV:
MockDAV.return_value.upload.return_value = None
resp = c.post(
f'/tasks/{self.task.pk}/attachments/upload/',
{'file': f},
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
att = TaskAttachment.objects.get(task=self.task)
self.assertEqual(att.original_name, 'plan.pdf')
self.assertEqual(att.uploaded_by, self.responsible)
MockDAV.return_value.upload.assert_called_once()
path = MockDAV.return_value.upload.call_args[0][0]
self.assertEqual(path, f'07_Задачи/{self.task.pk}/plan.pdf')
def test_outsider_cannot_upload(self):
from unittest.mock import patch
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import Client
c = Client()
c.force_login(self.outsider)
f = SimpleUploadedFile('x.pdf', b'data', content_type='application/pdf')
with patch('apps.nextcloud_connector.webdav_service.NextcloudWebDAVService') as MockDAV:
resp = c.post(
f'/tasks/{self.task.pk}/attachments/upload/',
{'file': f},
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 404)
MockDAV.assert_not_called()
def test_uploader_can_delete(self):
from unittest.mock import patch
from django.test import Client
from apps.tasks.models import TaskAttachment
att = TaskAttachment.objects.create(
task=self.task, original_name='a.pdf', stored_name='a.pdf',
size=10, uploaded_by=self.responsible,
)
c = Client()
c.force_login(self.responsible)
with patch('apps.nextcloud_connector.webdav_service.NextcloudWebDAVService') as MockDAV:
MockDAV.return_value.delete.return_value = None
resp = c.post(
f'/tasks/{self.task.pk}/attachments/{att.pk}/delete/',
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
self.assertFalse(TaskAttachment.objects.filter(pk=att.pk).exists())
def test_outsider_cannot_delete(self):
from django.test import Client
from apps.tasks.models import TaskAttachment
att = TaskAttachment.objects.create(
task=self.task, original_name='a.pdf', stored_name='a.pdf',
size=10, uploaded_by=self.creator,
)
c = Client()
c.force_login(self.outsider)
resp = c.post(f'/tasks/{self.task.pk}/attachments/{att.pk}/delete/')
self.assertEqual(resp.status_code, 404)
self.assertTrue(TaskAttachment.objects.filter(pk=att.pk).exists())
def test_download_for_participant(self):
from unittest.mock import patch
from django.test import Client
from apps.tasks.models import TaskAttachment
att = TaskAttachment.objects.create(
task=self.task, original_name='a.pdf', stored_name='a.pdf',
content_type='application/pdf', size=4, uploaded_by=self.creator,
)
c = Client()
c.force_login(self.responsible)
with patch('apps.nextcloud_connector.webdav_service.NextcloudWebDAVService') as MockDAV:
MockDAV.return_value.download.return_value = (b'%PDF', 'application/pdf')
resp = c.get(f'/tasks/{self.task.pk}/attachments/{att.pk}/download/')
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content, b'%PDF')
self.assertIn('attachment', resp['Content-Disposition'])