"""Оба публичных входа запускают один импорт и исключают конкурирующий запуск.""" from importlib import import_module from unittest.mock import patch import pytest from apps.core.models import BackgroundJob from core.celery import app as celery_app from django.urls import reverse from rest_framework.test import APIClient from tests.apps.user.factories import UserFactory SOURCES = [ ("budget-process-registry", "budget_ubpandnubp", "parse_budget_registry"), ( "sme-support-recipients-registry", "fns_sme_support_recipients", "parse_sme_support_recipients", ), ] @pytest.mark.django_db def test_openapi_exposes_refresh_conflict_and_pollable_task(): client = APIClient() client.force_authenticate(UserFactory.create_user(is_staff=True)) response = client.get(reverse("schema-swagger-ui"), {"format": "openapi"}) assert response.status_code == 200 schema = response.data path = next(path for path in schema["paths"] if "/parsers/run/" in path) responses = schema["paths"][path]["post"]["responses"] assert "409" in responses assert responses["202"]["schema"]["$ref"].endswith("/ParserRunEnvelope") assert "task_id" in schema["definitions"]["ParserRunResponse"]["properties"] card_path = next( path for path in schema["paths"] if "/sources/{slug}/refresh/" in path ) assert "409" in schema["paths"][card_path]["post"]["responses"] @pytest.mark.django_db @pytest.mark.parametrize("slug,source,task", SOURCES) @pytest.mark.parametrize("first", ["card", "parser"]) def test_refresh_is_pollable_and_other_entrypoint_conflicts(slug, source, task, first): client = APIClient() client.force_authenticate(UserFactory.create_user(is_staff=True)) urls = { "card": f"/api/v1/sources/{slug}/refresh/", "parser": f"/api/v1/parsers/run/{source}/", } second = "parser" if first == "card" else "card" import_module("apps.parsers.tasks") registered_task = celery_app.tasks[f"parsers.{source}.refresh"] with patch.object(registered_task, "apply_async") as dispatch: response = client.post(urls[first], {"params": {}}, format="json") assert response.status_code == 202, response.data payload = response.data.get("data", response.data) job = BackgroundJob.objects.get(task_id=payload["task_id"]) assert payload["status"] == "queued" assert dispatch.call_args.kwargs["task_id"] == job.task_id assert dispatch.call_args.kwargs["kwargs"] == {"requested_by_id": job.user_id} assert job.meta["refresh_task_ids"] == [job.task_id] assert client.get(f"/api/v1/jobs/{job.task_id}/").status_code == 200 duplicate = client.post(urls[second], {}, format="json") assert duplicate.status_code == 409, duplicate.data assert BackgroundJob.objects.count() == 1 assert dispatch.call_count == 1 job.complete() next_run = client.post(urls[second], {}, format="json") assert next_run.status_code == 202 assert BackgroundJob.objects.count() == 2 @pytest.mark.django_db @pytest.mark.parametrize("slug,source,task", SOURCES) def test_refresh_requires_administrator_and_rejects_loader_parameters( slug, source, task ): client = APIClient() client.force_authenticate(UserFactory.create_user()) import_module("apps.parsers.tasks") registered_task = celery_app.tasks[f"parsers.{source}.refresh"] with patch.object(registered_task, "apply_async") as dispatch: for url in [ f"/api/v1/sources/{slug}/refresh/", f"/api/v1/parsers/run/{source}/", ]: assert client.post(url, {}, format="json").status_code == 403 client.force_authenticate(UserFactory.create_user(is_staff=True)) response = client.post( f"/api/v1/sources/{slug}/refresh/", {"params": {"url": "https://example.invalid"}}, format="json", ) assert response.status_code == 400 dispatch.assert_not_called()