from __future__ import annotations from pathlib import Path from tempfile import TemporaryDirectory from types import SimpleNamespace 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: with override_settings(BACKUP_EXPORT_DIRECTORY=tmp_dir): existing = Path(tmp_dir) / "backup.zip" existing.write_bytes(b"existing") with patch("apps.backups.tasks.uuid.uuid4") as uuid_mock: 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: with 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, ): with patch( "apps.backups.tasks.BackgroundJobService.create_job", return_value=background_job, ) as create_job_mock: with 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, ): with patch( "apps.backups.tasks.BackupExportService.build_backup_archive", side_effect=RuntimeError("boom"), ): with patch("apps.backups.tasks.logger.exception") as logger_mock: with 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")