"""The serverless track: the API starts a Cloud Run job instead of publishing to Pub/Sub.""" import uuid from typing import Any import pytest from httpx import AsyncClient from app.config import settings from app.services import events class FakeOperation: def __init__(self, execution: str) -> None: self.metadata = type("Meta", (), {"name": execution})() class FakeJobsClient: def __init__(self, result: Any = None) -> None: self.result = result self.requests: list[dict] = [] def run_job(self, request: dict) -> FakeOperation: self.requests.append(request) if isinstance(self.result, Exception): raise self.result return self.result async def new_case(client: AsyncClient) -> str: r = await client.post( "/api/cases", json={"name": f"s-{uuid.uuid4()}", "vcf_uri": "gs://bucket/x.vcf.gz", "assembly": "GRCh37"}, ) assert r.status_code == 201, r.text return r.json()["id"] @pytest.fixture def cloudrun(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(settings, "pubsub_topic", None) monkeypatch.setattr(settings, "cloudrun_job", "rarelens-nextflow") monkeypatch.setattr(settings, "gcp_project", "my-proj") monkeypatch.setattr(settings, "gcp_region", "europe-west2") @pytest.mark.usefixtures("db", "cloudrun") async def test_annotate_executes_the_job_with_pipeline_arguments( client: AsyncClient, monkeypatch: pytest.MonkeyPatch ) -> None: jobs = FakeJobsClient(FakeOperation("projects/p/locations/l/executions/rarelens-nextflow-abc12")) monkeypatch.setattr(events, "_jobs_client", lambda: jobs) case_id = await new_case(client) r = await client.post(f"/api/cases/{case_id}/annotate") assert r.status_code == 202 job = r.json() assert job["status"] == "running" assert job["workflow_ref"] == "cloudrun:rarelens-nextflow-abc12" [request] = jobs.requests assert request["name"] == "projects/my-proj/locations/europe-west2/jobs/rarelens-nextflow" [override] = request["overrides"]["container_overrides"] args = override["args"] assert args[:4] == ["run", "/pipeline/main.nf", "-profile", "gcp"] assert args[args.index("--job_id") + 1] == job["id"] assert args[args.index("--assembly") + 1] == "GRCh37" assert args[args.index("--vcf") + 1] == "gs://bucket/x.vcf.gz" @pytest.mark.usefixtures("db", "cloudrun") async def test_a_failed_execution_call_marks_the_job_failed( client: AsyncClient, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setattr( events, "_jobs_client", lambda: FakeJobsClient(RuntimeError("403 permission denied")) ) case_id = await new_case(client) job = (await client.post(f"/api/cases/{case_id}/annotate")).json() assert job["status"] == "failed" assert "403 permission denied" in job["log"] @pytest.mark.usefixtures("db", "cloudrun") async def test_cloud_run_job_takes_precedence_over_pubsub( client: AsyncClient, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setattr(settings, "pubsub_topic", "vcf-uploaded") jobs = FakeJobsClient(FakeOperation("projects/p/locations/l/executions/x-1")) monkeypatch.setattr(events, "_jobs_client", lambda: jobs) monkeypatch.setattr( events, "_publisher", lambda: pytest.fail("Pub/Sub must not be used in the serverless track") ) case_id = await new_case(client) assert (await client.post(f"/api/cases/{case_id}/annotate")).json()["status"] == "running" assert len(jobs.requests) == 1