"""Тесты агрегации активностей Clockify перед переносом в Redmine.""" from datetime import datetime from unittest import TestCase from unittest.mock import Mock from classes import Activity from services.tracking import TrackingService, aggregate_activities class TrackingServiceTestCase(TestCase): """Проверяет объединение и сохранение активностей Clockify.""" def create_activity(self, activity_id: str, description: str, **changes) -> Activity: """Создаёт активность с типовыми значениями для теста.""" values = { "id": activity_id, "author_id": 1, "vetro_project_id": "vetro-project", "task_id": 51906, "tag_id": "tag", "description": description, "project_id": "project", "time_spent": 0.5, "date_start": datetime(2026, 7, 14, 9), "date_end": datetime(2026, 7, 14, 9, 30), } values.update(changes) return Activity(**values) def test_fully_matching_activities_are_merged(self): """Полностью одинаковые активности образуют одну запись без повтора описания.""" activities = [ self.create_activity("first", "Доработки"), self.create_activity( "second", "Доработки", date_start=datetime(2026, 7, 14, 10), date_end=datetime(2026, 7, 14, 10, 30), ), ] result = aggregate_activities(activities) self.assertEqual(len(result), 1) self.assertEqual(result[0].time_spent, 1) self.assertEqual(result[0].description, "Доработки") self.assertEqual(result[0].source_activities, tuple(activities)) def test_descriptions_are_joined_for_otherwise_matching_activities(self): """Разные описания одинаковых активностей объединяются через точку.""" result = aggregate_activities( [ self.create_activity("first", "Доработки"), self.create_activity("second", "Влил в тест.", time_spent=0.25), self.create_activity("third", "Доработки", time_spent=0.25), ] ) self.assertEqual(len(result), 1) self.assertEqual(result[0].time_spent, 1) self.assertEqual(result[0].description, "Доработки. Влил в тест") def test_activities_for_different_dates_are_not_merged(self): """Активности за разные даты остаются отдельными записями Redmine.""" result = aggregate_activities( [ self.create_activity("first", "Доработки"), self.create_activity( "second", "Доработки", date_start=datetime(2026, 7, 15, 9), date_end=datetime(2026, 7, 15, 9, 30), ), ] ) self.assertEqual(len(result), 2) def test_all_source_activities_are_marked_as_tracked(self): """После успешной отправки сохраняются все исходные таймеры Clockify.""" activities = [ self.create_activity("first", "Доработки"), self.create_activity("second", "Влил в тест"), ] aggregated_activity = aggregate_activities(activities)[0] db = Mock() service = TrackingService( clock=Mock(), red=Mock(), db=db, is_debug=False, ) service._save_tracked_activity(aggregated_activity, 42) self.assertTrue(all(activity.is_tracked for activity in activities)) db.insert_activities.assert_called_once_with(activities) db.insert_time_entry.assert_called_once_with( aggregated_activity, activities, 42, ) def test_activities_for_different_tasks_are_not_merged(self): """Активности разных задач остаются отдельными записями Redmine.""" result = aggregate_activities( [ self.create_activity("first", "Доработки"), self.create_activity("second", "Доработки", task_id=60130), ] ) self.assertEqual(len(result), 2)