from __future__ import annotations from pathlib import Path from tempfile import TemporaryDirectory from unittest.mock import MagicMock, patch from apps.backups.models import BackupExportJob from apps.backups.services import BackupArtifact from apps.backups.tasks import _resolve_backup_target_path, generate_backup_for_date from django.test import TestCase, override_settings from tests.apps.user.factories import UserFactory class BackupTasksTest(TestCase): def test_resolve_backup_target_path_creates_directory_and_renames_existing_file( self, ): with TemporaryDirectory() as tmp_dir, override_settings( BACKUP_EXPORT_DIRECTORY=tmp_dir ), patch("apps.backups.tasks.uuid.uuid4") as uuid_mock: existing = Path(tmp_dir) / "backup.zip" existing.write_bytes(b"existing") uuid_mock.return_value.hex = "deadbeefcafebabe" target = _resolve_backup_target_path("backup.zip") self.assertEqual(target.name, "backup_deadbeef.zip") def test_generate_backup_for_date_returns_skipped_when_job_is_missing(self): generate_backup_for_date.push_request(id="task-missing") try: result = generate_backup_for_date.run(job_id=999999) finally: generate_backup_for_date.pop_request() self.assertEqual(result, {"status": "skipped", "reason": "job_not_found"}) def test_generate_backup_for_date_builds_archive_and_updates_job(self): user = UserFactory.create_user() job = BackupExportJob.objects.create( actual_date=user.date_joined.date(), requested_by=user, status=BackupExportJob.Status.PENDING, ) background_job = MagicMock() artifact = BackupArtifact( archive_bytes=b"zip-bytes", archive_filename="backup.zip", bin_filename="backup.bin", checksum_filename="backup.zip.sha256", checksum_sha256="a" * 64, organizations_count=5, actual_date=job.actual_date, ) with TemporaryDirectory() as tmp_dir, override_settings( BACKUP_EXPORT_DIRECTORY=tmp_dir ): generate_backup_for_date.push_request(id="task-success") try: with patch( "apps.backups.tasks.BackgroundJobService.get_by_task_id_or_none", return_value=None, ), patch( "apps.backups.tasks.BackgroundJobService.create_job", return_value=background_job, ) as create_job_mock, patch( "apps.backups.tasks.BackupExportService.build_backup_archive", return_value=artifact, ) as build_mock: result = generate_backup_for_date.run(job_id=job.id) finally: generate_backup_for_date.pop_request() job.refresh_from_db() self.assertEqual(job.status, BackupExportJob.Status.SUCCESS) self.assertEqual(job.task_id, "task-success") self.assertEqual(job.archive_filename, "backup.zip") self.assertEqual(job.checksum_filename, "backup.zip.sha256") self.assertEqual(job.organizations_count, 5) self.assertTrue(Path(job.archive_path).is_file()) self.assertEqual(result["status"], "success") self.assertEqual(result["archive_filename"], "backup.zip") create_job_mock.assert_called_once() build_mock.assert_called_once_with(actual_date=job.actual_date) background_job.mark_started.assert_called_once_with() background_job.update_progress.assert_any_call(10, "Подготовка backup-данных") background_job.update_progress.assert_any_call(70, "Запись архива на диск") background_job.complete.assert_called_once_with(result=result) def test_generate_backup_for_date_marks_failure(self): user = UserFactory.create_user() job = BackupExportJob.objects.create( actual_date=user.date_joined.date(), requested_by=user, status=BackupExportJob.Status.PENDING, ) background_job = MagicMock() generate_backup_for_date.push_request(id="task-failure") try: with patch( "apps.backups.tasks.BackgroundJobService.get_by_task_id_or_none", return_value=background_job, ), patch( "apps.backups.tasks.BackupExportService.build_backup_archive", side_effect=RuntimeError("boom"), ), patch( "apps.backups.tasks.logger.exception" ) as logger_mock, self.assertRaisesMessage(RuntimeError, "boom"): generate_backup_for_date.run(job_id=job.id) finally: generate_backup_for_date.pop_request() logger_mock.assert_called_once() background_job.fail.assert_called_once_with(error="boom") job.refresh_from_db() self.assertEqual(job.status, BackupExportJob.Status.FAILURE) self.assertEqual(job.error, "boom")