feat(registries): add SME and budget imports and fix source API workflows
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 9m37s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 4m18s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m48s
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 9m37s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 4m18s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m48s
This commit is contained in:
78
tests/apps/core/test_job_control_race.py
Normal file
78
tests/apps/core/test_job_control_race.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""Отмена через API не перезаписывает завершившуюся в это время задачу."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from apps.core.models import BackgroundJob, JobStatus
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from tests.apps.user.factories import UserFactory
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("terminal", ["success", "failure"])
|
||||
def test_revoke_preserves_worker_terminal_result(terminal):
|
||||
user = UserFactory.create_user()
|
||||
client = APIClient()
|
||||
client.force_authenticate(user)
|
||||
job = BackgroundJob.objects.create(
|
||||
task_id=f"revoke-race-{terminal}", task_name="test.task", user_id=user.pk
|
||||
)
|
||||
job.mark_started()
|
||||
job.update_progress(65, "Обработка")
|
||||
expected = {}
|
||||
|
||||
def finish_during_revoke(*args, **kwargs):
|
||||
worker_job = BackgroundJob.objects.get(pk=job.pk)
|
||||
if terminal == "success":
|
||||
worker_job.complete({"saved": 5})
|
||||
else:
|
||||
worker_job.fail("Ошибка источника", "worker traceback")
|
||||
expected.update(
|
||||
status=worker_job.status,
|
||||
progress=worker_job.progress,
|
||||
progress_message=worker_job.progress_message,
|
||||
result=worker_job.result,
|
||||
error=worker_job.error,
|
||||
traceback=worker_job.traceback,
|
||||
completed_at=worker_job.completed_at,
|
||||
)
|
||||
|
||||
url = reverse("api_v1:jobs:job-control", kwargs={"task_id": job.task_id})
|
||||
with patch("celery.current_app.control.revoke", side_effect=finish_during_revoke):
|
||||
response = client.post(url, {"action": "revoke"}, format="json")
|
||||
|
||||
assert response.status_code == 200
|
||||
job.refresh_from_db()
|
||||
assert {field: getattr(job, field) for field in expected} == expected
|
||||
assert response.data["status"] == ("success" if terminal == "success" else "error")
|
||||
assert response.data["progress"] == expected["progress"]
|
||||
assert response.data["message"] == expected["progress_message"]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_revoke_unfinished_job_records_completion_and_user_message():
|
||||
user = UserFactory.create_user()
|
||||
client = APIClient()
|
||||
client.force_authenticate(user)
|
||||
job = BackgroundJob.objects.create(
|
||||
task_id="revoke-active", task_name="test.task", user_id=user.pk
|
||||
)
|
||||
job.mark_started()
|
||||
job.update_progress(65, "Обработка")
|
||||
url = reverse("api_v1:jobs:job-control", kwargs={"task_id": job.task_id})
|
||||
|
||||
with patch("celery.current_app.control.revoke") as revoke:
|
||||
response = client.post(
|
||||
url, {"action": "revoke", "terminate": True}, format="json"
|
||||
)
|
||||
|
||||
revoke.assert_called_once_with(job.task_id, terminate=True)
|
||||
assert response.status_code == 200
|
||||
job.refresh_from_db()
|
||||
assert job.status == JobStatus.REVOKED
|
||||
assert job.progress == 65
|
||||
assert job.progress_message == "Задача отозвана пользователем"
|
||||
assert job.completed_at is not None
|
||||
assert response.data["message"] == job.progress_message
|
||||
58
tests/apps/core/test_job_progress.py
Normal file
58
tests/apps/core/test_job_progress.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Регрессии для запоздалых и повторных событий фоновой задачи."""
|
||||
|
||||
import pytest
|
||||
from apps.core.models import BackgroundJob, JobStatus
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_stale_progress_never_reduces_persisted_value():
|
||||
job = BackgroundJob.objects.create(task_id="monotonic", task_name="test.task")
|
||||
stale = BackgroundJob.objects.get(pk=job.pk)
|
||||
job.update_progress(75, "Новая стадия")
|
||||
stale.update_progress(25, "Старая стадия")
|
||||
job.refresh_from_db()
|
||||
assert (job.progress, job.progress_message) == (75, "Новая стадия")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("terminal", ["complete", "fail", "revoke"])
|
||||
def test_terminal_job_ignores_late_events(terminal):
|
||||
job = BackgroundJob.objects.create(task_id=terminal, task_name="test.task")
|
||||
stale = BackgroundJob.objects.get(pk=job.pk)
|
||||
job.update_progress(65, "Обработка")
|
||||
if terminal == "complete":
|
||||
job.complete({"saved": 2})
|
||||
elif terminal == "fail":
|
||||
job.fail("Ошибка источника")
|
||||
else:
|
||||
job.revoke()
|
||||
expected = (job.status, job.progress, job.completed_at, job.result, job.error)
|
||||
stale.mark_started()
|
||||
stale.mark_retry()
|
||||
stale.update_progress(5, "Запоздалое событие")
|
||||
stale.complete({"saved": 0})
|
||||
stale.fail("Поздняя ошибка")
|
||||
stale.revoke()
|
||||
job.refresh_from_db()
|
||||
assert (
|
||||
job.status,
|
||||
job.progress,
|
||||
job.completed_at,
|
||||
job.result,
|
||||
job.error,
|
||||
) == expected
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_retry_preserves_progress_and_success_finishes_at_100():
|
||||
job = BackgroundJob.objects.create(task_id="retry", task_name="test.task")
|
||||
job.mark_started()
|
||||
started_at = job.started_at
|
||||
job.update_progress(40)
|
||||
job.mark_retry()
|
||||
job.mark_started()
|
||||
job.update_progress(0)
|
||||
assert job.progress == 40
|
||||
assert job.started_at == started_at
|
||||
job.complete()
|
||||
assert (job.status, job.progress) == (JobStatus.SUCCESS, 100)
|
||||
Reference in New Issue
Block a user