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

This commit is contained in:
Aleksandr Meshchryakov
2026-09-13 23:13:47 +02:00
parent c863563851
commit 49cbfd265c
50 changed files with 5021 additions and 452 deletions

View File

@@ -0,0 +1,97 @@
"""Оба публичных входа запускают один импорт и исключают конкурирующий запуск."""
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()