from __future__ import annotations import asyncio import json import subprocess from pathlib import Path from unittest.mock import Mock import pytest from fastapi import HTTPException from openpyxl import Workbook from apps.web import pipeline_backend as backend from apps.web.vue3 import main as web_main def _source_block(source: str, start: str, end: str) -> str: start_index = source.index(start) return source[start_index:source.index(end, start_index)] def _run_node(source: str) -> dict: completed = subprocess.run( ["/usr/bin/node", "-e", source], check=True, capture_output=True, text=True, ) return json.loads(completed.stdout) def test_confirmed_numeric_reference_uses_storage_key_for_local_id_map( tmp_path: Path, monkeypatch, ) -> None: input_dir = tmp_path / "input" workspace = tmp_path / "workspace" final_dir = workspace / "final" logs_dir = workspace / "logs" input_dir.mkdir() final_dir.mkdir(parents=True) logs_dir.mkdir(parents=True) workbook = Workbook() sheet = workbook.active sheet.title = "config" sheet.append(["Species_Code", "Species_Name", "TaxID"]) sheet.append(["Sv", "Setaria viridis", "4556"]) workbook.save(input_dir / "config.xlsx") storage_dir = input_dir / "string_taxon_4555" storage_dir.mkdir() expected_id_map = storage_dir / "string_taxon_4555.pep_cds_idmap.tsv" expected_id_map.write_text( "species_code\tprotein_id\nstring_taxon_4555\tSeita.1\n", encoding="utf-8", ) (storage_dir / "string_taxon_4555.pep.fasta").write_text( ">Seita.1\nMA\n", encoding="utf-8", ) (final_dir / "Sv.HKL.final_members.id").write_text("SvGene1\n", encoding="utf-8") (final_dir / "Sv.HKL.orthologs.txt").write_text( "SvGene1\tSeita.1\t91\tSv\tstring_taxon_4555\n", encoding="utf-8", ) scope_file = final_dir / "Sv.HKL.target-ref.scope.json" scope_file.write_text( json.dumps( { "family": "HKL", "prefix": "Sv.HKL", "scope_id": "HKL.target-Sv.ref-string_taxon_4555.target_plus_reference", "scope_mode": "target_plus_reference", "target_species": ["Sv"], "reference_species": ["string_taxon_4555"], "selected_species": ["Sv", "string_taxon_4555"], } ), encoding="utf-8", ) job = backend.JobInfo( job_id="job_task5_storage", input_dir=str(input_dir), workspace_dir=str(workspace), logs_dir=str(logs_dir), created_at="", ) launched: list[list[str]] = [] monkeypatch.setattr(backend, "can_start_new_task", lambda: (True, "ok")) monkeypatch.setattr( backend, "run_cmd_logged", lambda cmd, *_args, **_kwargs: launched.append(list(cmd)) or 1, ) message, _manifest = backend.run_stage( "ppi", job, "Sv.HKL", scope_file=str(scope_file), ppi_species="4555", normalization_species_label="sit", ) wrong_id_map = input_dir / "4555" / "4555.pep_cds_idmap.tsv" assert str(wrong_id_map) not in message assert launched command = launched[0] assert command[command.index("--species") + 1] == "4555" assert command[command.index("--id-map") + 1] == str(expected_id_map) def test_ppi_owner_fields_survive_execute_and_worker_transport() -> None: from apps.web.vue3.main import ExecuteRequest from tasks import normalize_stage_params request = ExecuteRequest( stage="ppi", job_id="job_task5_transport", prefix="Sv.HKL", ppiReferenceTaxonId="4555", ppiReferenceStorageKey="string_taxon_4555", ppiReferenceSelectionMode="tree_nearest", ppiReferenceUserConfirmed=True, dependencyOwner="ppi", ) params, _profile = normalize_stage_params("ppi", request.dict()) assert params["ppiReferenceTaxonId"] == "4555" assert params["ppiReferenceStorageKey"] == "string_taxon_4555" assert params["ppiReferenceSelectionMode"] == "tree_nearest" assert params["ppiReferenceUserConfirmed"] is True assert params["dependencyOwner"] == "ppi" def test_execute_scope_uses_confirmed_ppi_owner_storage_without_biological_scope_pollution() -> None: request = web_main.ExecuteRequest( stage="ortholog", job_id="job_owner_scope", prefix="Sv.HKL", scopeMode="target_only", targetSpecies=["Sv"], ppiReferenceTaxonId="4555", ppiReferenceStorageKey="string_taxon_4555", ppiReferenceUserConfirmed=True, dependencyOwner="ppi", ) scope_params = { "scopeMode": "target_only", "targetSpecies": ["Sv"], "referenceSpecies": [], "refSpeciesList": [], } technical_refs, ppi_owned_ortholog, needs_fallback = ( web_main._prepare_execute_technical_reference_scope(request, scope_params) ) assert technical_refs == ["string_taxon_4555"] assert ppi_owned_ortholog is True assert needs_fallback is False assert scope_params == { "scopeMode": "target_only", "targetSpecies": ["Sv"], "referenceSpecies": [], "refSpeciesList": [], "comparativeSpecies": [], "scopeFile": "", } def test_execute_scope_keeps_ordinary_ortholog_reference_fallback_enabled() -> None: request = web_main.ExecuteRequest( stage="ortholog", job_id="job_ordinary_scope", prefix="Sv.HKL", scopeMode="target_only", targetSpecies=["Sv"], ) scope_params = { "scopeMode": "target_only", "targetSpecies": ["Sv"], "referenceSpecies": [], "refSpeciesList": [], } technical_refs, ppi_owned_ortholog, needs_fallback = ( web_main._prepare_execute_technical_reference_scope(request, scope_params) ) assert technical_refs == [] assert ppi_owned_ortholog is False assert needs_fallback is True assert scope_params["scopeMode"] == "target_only" def test_execute_scope_kaks_ignores_ppi_owner_only_reference_fields() -> None: request = web_main.ExecuteRequest( stage="kaks", job_id="job_kaks_scope", prefix="Sv.HKL", ppiReferenceTaxonId="4555", ppiReferenceStorageKey="string_taxon_4555", ppiReferenceUserConfirmed=True, dependencyOwner="ppi", ) scope_params = { "scopeMode": "target_plus_reference", "targetSpecies": ["Sv"], "referenceSpecies": [], "refSpeciesList": [], } technical_refs, ppi_owned_ortholog, needs_fallback = ( web_main._prepare_execute_technical_reference_scope(request, scope_params) ) assert technical_refs == [] assert ppi_owned_ortholog is False assert needs_fallback is True @pytest.mark.parametrize( ("confirmed", "storage_key"), [(False, "string_taxon_4555"), (True, "")], ) def test_execute_rejects_incomplete_ppi_owned_ortholog_before_scope_or_enqueue( monkeypatch, confirmed: bool, storage_key: str, ) -> None: scope_called = False def fail_if_scope_is_built(**_kwargs): nonlocal scope_called scope_called = True raise ValueError("scope builder must not run") monkeypatch.setattr(web_main, "_ensure_gene_family_scope_file", fail_if_scope_is_built) request = web_main.ExecuteRequest( stage="ortholog", job_id="job_incomplete_owner", prefix="Sv.HKL", scopeMode="target_plus_reference", targetSpecies=["Sv"], referenceSpecies=["At"], refSpeciesList=["At"], ppiKaksReferenceSpecies=["At"], ppiReferenceSpecies="At", referenceBundleSpecies="At", ppiReferenceTaxonId="4555", ppiReferenceStorageKey=storage_key, ppiReferenceUserConfirmed=confirmed, dependencyOwner="ppi", ) with pytest.raises(HTTPException) as exc_info: asyncio.run(web_main.execute_stage(request)) assert exc_info.value.status_code == 400 assert exc_info.value.detail["code"] == "ppi_owner_reference_incomplete" assert scope_called is False def test_execute_normalizes_polluted_confirmed_ppi_owner_before_scope_creation(monkeypatch) -> None: captured = {} def capture_scope(**kwargs): captured.update(kwargs["params"]) raise ValueError("captured normalized scope") monkeypatch.setattr(web_main, "_ensure_gene_family_scope_file", capture_scope) request = web_main.ExecuteRequest( stage="ortholog", job_id="job_confirmed_owner", prefix="Sv.HKL", scopeMode="target_plus_reference", targetSpecies=["Sv"], referenceSpecies=["At"], refSpeciesList=["At"], ppiKaksReferenceSpecies=["At"], ppiReferenceSpecies="At", referenceBundleSpecies="At", ppiReferenceTaxonId="4555", ppiReferenceStorageKey="string_taxon_4555", ppiReferenceUserConfirmed=True, dependencyOwner="ppi", ) with pytest.raises(HTTPException): asyncio.run(web_main.execute_stage(request)) assert captured["scopeMode"] == "target_only" assert captured["targetSpecies"] == ["Sv"] assert captured["referenceSpecies"] == [] assert captured["refSpeciesList"] == [] assert [*captured["targetSpecies"], *captured["referenceSpecies"]] == ["Sv"] def test_execute_preserves_valid_target_only_scope_identity_for_confirmed_ppi_owner( tmp_path: Path, monkeypatch, ) -> None: from gfa_core.mas import resource_governor from tasks import execute_single_stage_task scope_file = tmp_path / "Sv.HKL.biological.scope.json" scope_file.write_text( json.dumps( { "family": "HKL", "prefix": "Sv.HKL", "scope_id": "fixed-biological-target-only-scope", "scope_mode": "target_only", "target_species": ["Sv"], "reference_species": [], "comparative_species": ["Os"], "selected_species": ["Sv"], "analysis": {"Family": "HKL", "context_marker": "preserve-me"}, } ), encoding="utf-8", ) scope_called = False def fail_if_scope_is_rebuilt(**_kwargs): nonlocal scope_called scope_called = True raise AssertionError("a validated existing owner scope must be reused") monkeypatch.setattr(web_main, "_ensure_gene_family_scope_file", fail_if_scope_is_rebuilt) isolated_apply_async = Mock() isolated_apply_async.return_value.id = "isolated-valid-owner-scope" monkeypatch.setattr(execute_single_stage_task, "apply_async", isolated_apply_async) monkeypatch.setattr(web_main, "_ensure_celery_queue_health", lambda *_args, **_kwargs: ({"available": True}, None)) monkeypatch.setattr(resource_governor, "record_task", lambda **_kwargs: {}) request = web_main.ExecuteRequest( stage="ortholog", job_id="job_owner_existing_scope", prefix="Sv.HKL", scopeFile=str(scope_file), scopeMode="target_plus_reference", targetSpecies=["WrongTarget"], referenceSpecies=["At"], refSpeciesList=["At"], comparativeSpecies=["Zm"], ppiKaksReferenceSpecies=["At"], ppiReferenceSpecies="At", referenceBundleSpecies="At", ppiReferenceTaxonId="4555", ppiReferenceStorageKey="string_taxon_4555", ppiReferenceSelectionMode="tree_nearest", ppiReferenceUserConfirmed=True, dependencyOwner="ppi", ) asyncio.run(web_main.execute_stage(request)) assert scope_called is False submitted_params = isolated_apply_async.call_args.kwargs["kwargs"]["params"] assert submitted_params["scopeFile"] == str(scope_file) assert submitted_params["stageContext"]["scope_id"] == "fixed-biological-target-only-scope" assert submitted_params["scopeMode"] == "target_only" assert submitted_params["targetSpecies"] == ["Sv"] assert submitted_params["referenceSpecies"] == [] assert submitted_params["refSpeciesList"] == [] assert submitted_params["comparativeSpecies"] == ["Os"] assert submitted_params["ppiReferenceTaxonId"] == "4555" assert submitted_params["ppiReferenceStorageKey"] == "string_taxon_4555" assert submitted_params["ppiReferenceSelectionMode"] == "tree_nearest" assert submitted_params["ppiReferenceUserConfirmed"] is True assert submitted_params["dependencyOwner"] == "ppi" assert submitted_params["ppiKaksReferenceSpecies"] == [] assert submitted_params["ppiReferenceSpecies"] == "" assert submitted_params["referenceBundleSpecies"] == "" @pytest.mark.parametrize( ("scope_payload", "expected_code"), [ ("{not-json", "ppi_owner_scope_invalid"), ( json.dumps( { "family": "HKL", "scope_id": "wrong-mode-scope", "scope_mode": "target_plus_reference", "target_species": ["Sv"], "reference_species": ["At"], "selected_species": ["Sv", "At"], } ), "ppi_owner_scope_mode_incompatible", ), ], ) def test_execute_fails_closed_for_invalid_ppi_owner_scope_file( tmp_path: Path, monkeypatch, scope_payload: str, expected_code: str, ) -> None: from tasks import execute_single_stage_task scope_file = tmp_path / "owner.scope.json" scope_file.write_text(scope_payload, encoding="utf-8") scope_called = False def fail_if_scope_is_built(**_kwargs): nonlocal scope_called scope_called = True raise AssertionError("invalid owner scope must fail before scope construction") monkeypatch.setattr(web_main, "_ensure_gene_family_scope_file", fail_if_scope_is_built) isolated_apply_async = Mock() isolated_apply_async.return_value.id = "must-not-submit-invalid-owner-scope" monkeypatch.setattr(execute_single_stage_task, "apply_async", isolated_apply_async) request = web_main.ExecuteRequest( stage="ortholog", job_id="job_owner_invalid_scope", prefix="Sv.HKL", scopeFile=str(scope_file), targetSpecies=["Sv"], ppiReferenceTaxonId="4555", ppiReferenceStorageKey="string_taxon_4555", ppiReferenceUserConfirmed=True, dependencyOwner="ppi", ) with pytest.raises(HTTPException) as exc_info: asyncio.run(web_main.execute_stage(request)) assert exc_info.value.status_code == 400 assert exc_info.value.detail["code"] == expected_code assert scope_called is False isolated_apply_async.assert_not_called() def test_ppi_owner_fields_are_forwarded_without_becoming_global_reference_scope() -> None: main_source = Path("apps/web/vue3/main.py").read_text(encoding="utf-8") stage_tools_source = Path("gfa_core/mas/tools/stage_tools.py").read_text(encoding="utf-8") technical_start = main_source.index("technical_reference_species: list[str] = []") technical_end = main_source.index("gene_family_identity_stages =", technical_start) technical_block = main_source[technical_start:technical_end] for field in ( "ppiReferenceTaxonId", "ppiReferenceStorageKey", "ppiReferenceSelectionMode", "ppiReferenceUserConfirmed", "dependencyOwner", ): assert f'"{field}": getattr(req' in main_source or f'"{field}": bool(getattr(req' in main_source assert field in stage_tools_source assert field not in technical_block def test_frontend_requires_confirmation_and_keeps_ppi_owner_state_isolated() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") assert "const confirmNearestPpiReferenceCandidate = async" in html assert "resolution.candidates" in html assert ".map((candidate, index) =>" in html assert "ppiReferenceTaxonId" in html assert "ppiReferenceStorageKey" in html assert "ppiReferenceSelectionMode" in html assert "ppiReferenceUserConfirmed" in html assert "dependencyOwner" in html assert "preflight.resolution?.status === 'confirmation_required'" in html assert "preflight = await runPpiSpeciesResolutionPreflight" in html assert "const applyPpiOwnerSelectionToBatch =" in html def test_confirmation_applies_owner_then_repeats_preflight() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") owner_block = _source_block( html, "const ensurePpiKaksDependencyOwner = async", "const ensureOrthologProducerForPpiKaks = async", ) assert "preflight.resolution?.status === 'confirmation_required'" in owner_block assert "await confirmNearestPpiReferenceCandidate(preflight.resolution)" in owner_block assert "applyPpiOwnerSelectionToBatch(ownerSelection" in owner_block assert owner_block.count("preflight = await runPpiSpeciesResolutionPreflight") >= 2 def test_ppi_owner_selection_never_mutates_shared_reference_scope() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") selection_block = _source_block( html, "const applyPpiOwnerSelectionToBatch =", "const releaseDirectTargetPpiFromOrtholog =", ) for field in ( "ppiReferenceTaxonId", "ppiReferenceStorageKey", "ppiReferenceSelectionMode", "ppiReferenceUserConfirmed", "dependencyOwner", ): assert field in selection_block assert "getStageId(stage) !== 'ppi'" in selection_block assert "ppiKaksReferenceSpecies" not in selection_block assert "referenceSpecies:" not in selection_block assert "refSpeciesList:" not in selection_block assert "scopeMode:" not in selection_block def test_owner_request_reads_selection_and_keeps_kaks_independent() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") request_block = _source_block( html, "const buildPpiKaksDependencyRequest =", "const attachOrthologDependencyToOwners =", ) owner_block = _source_block( html, "const ensurePpiKaksDependencyOwner = async", "const ensureOrthologProducerForPpiKaks = async", ) for field in ( "ppiReferenceTaxonId", "ppiReferenceStorageKey", "ppiReferenceSelectionMode", "ppiReferenceUserConfirmed", "dependencyOwner", ): assert field in request_block assert "const requestedStages = [ownerStageId]" in owner_block assert "dependencyOwner: ownerStageId" in request_block assert "ownerStageId === 'ppi'" in request_block def test_download_completion_reapplies_owner_without_shared_merge() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") monitor_block = _source_block( html, "const monitorPpiReferenceBundleDownload =", "const ppiHasNearReferenceBeforeArabidopsis =", ) owner_block = _source_block( html, "const ensurePpiKaksDependencyOwner = async", "const ensureOrthologProducerForPpiKaks = async", ) assert "applyPpiOwnerSelectionToBatch(ownerSelection" in monitor_block assert "requestedStages.includes('kaks')" in monitor_block assert "mergePpiReferenceSpeciesIntoBatch(refCode, ['kaks'])" in monitor_block assert "mergePpiReferenceSpeciesIntoBatch(refCode, requestedStages)" not in monitor_block assert "applyPpiOwnerSelectionToBatch(ownerSelection" in owner_block def test_arabidopsis_fallback_is_unselected_until_explicit_confirmation() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") fallback_request = _source_block( html, "const requestPpiDownloadFallbackDecision =", "const clearPpiKaksReferencePause =", ) assert "defaultDecision: 'skip'" in fallback_request assert "confirmParams.ppiKaksFallbackDecision: 'skip'" not in html assert "ppiKaksFallbackDecision: 'skip'" in html assert "confirmParams.ppiKaksFallbackDecision = 'skip'" in html def test_final_ppi_request_dynamically_preserves_owner_and_project_scope() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") helper = _source_block( html, "const applyPpiOwnerFieldsToRequest =", "const applyGeneFamilyContextToRequest =", ) result = _run_node( helper + """ const request = { referenceSpecies: ['ProjectRef'], refSpeciesList: ['ProjectRef'], scopeMode: 'target_plus_reference', ppiKaksReferenceSpecies: ['KaKsRef'], ppiReferenceSpecies: 'KaKsRef', referenceBundleSpecies: 'KaKsRef' }; const batch = { ppiReferenceTaxonId: '4555', ppiReferenceStorageKey: 'string_taxon_4555', ppiReferenceSelectionMode: 'tree_nearest', ppiReferenceUserConfirmed: true, dependencyOwner: 'ppi' }; console.log(JSON.stringify(applyPpiOwnerFieldsToRequest(request, 'ppi', {}, batch, {}))); """ ) assert result["ppiReferenceTaxonId"] == "4555" assert result["ppiReferenceStorageKey"] == "string_taxon_4555" assert result["ppiReferenceSelectionMode"] == "tree_nearest" assert result["ppiReferenceUserConfirmed"] is True assert result["dependencyOwner"] == "ppi" assert result["referenceSpecies"] == ["ProjectRef"] assert result["refSpeciesList"] == ["ProjectRef"] assert result["scopeMode"] == "target_plus_reference" for forbidden in ("ppiKaksReferenceSpecies", "ppiReferenceSpecies", "referenceBundleSpecies"): assert forbidden not in result def test_joint_batch_ppi_request_dynamically_ignores_kaks_reference() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") helper = _source_block( html, "const applyPpiOwnerFieldsToRequest =", "const applyGeneFamilyContextToRequest =", ) result = _run_node( helper + """ const request = { referenceSpecies: ['ProjectRef'], refSpeciesList: ['ProjectRef'] }; const batch = { ppiKaksReferenceSpecies: ['KaKsRef'], ppiReferenceSpecies: 'KaKsRef', referenceBundleSpecies: 'KaKsRef', ppiReferenceTaxonId: '4555', ppiReferenceStorageKey: 'string_taxon_4555', ppiReferenceSelectionMode: 'tree_nearest', ppiReferenceUserConfirmed: true, dependencyOwner: 'ppi' }; console.log(JSON.stringify(applyPpiOwnerFieldsToRequest(request, 'ppi', {}, batch, {}))); """ ) assert result["referenceSpecies"] == ["ProjectRef"] assert "KaKsRef" not in json.dumps(result) assert result["ppiReferenceTaxonId"] == "4555" def test_reference_ready_orchestration_dynamically_inserts_ortholog_before_resume() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") helper = _source_block( html, "const orchestratePpiOwnerAfterReferenceReady =", "const monitorPpiReferenceBundleDownload =", ) result = _run_node( helper + """ (async () => { const calls = []; const ok = await orchestratePpiOwnerAfterReferenceReady({ requestBody: { stage: 'ppi' }, requestedStages: ['ppi'], runPreflight: async () => ({ allowed: true, resolution: { status: 'needs_ortholog' } }), ensureOrtholog: async () => { calls.push('ortholog'); return true; }, resume: async () => { calls.push('resume'); } }); console.log(JSON.stringify({ ok, calls })); })(); """ ) assert result == {"ok": True, "calls": ["ortholog", "resume"]} def test_ppi_owned_ortholog_uses_owner_storage_without_polluting_final_ppi_request() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") producer_helper = _source_block( html, "const buildPpiOwnedOrthologStage =", "const attachOrthologDependencyToOwners =", ) request_helper = _source_block( html, "const applyPpiOwnerFieldsToRequest =", "const applyGeneFamilyContextToRequest =", ) ortholog_request_helper = _source_block( html, "const applyPpiOwnedOrthologFieldsToRequest =", "const applyPpiOwnerFieldsToRequest =", ) direct_builder = _source_block( html, "const finalizeExecuteStageDirectRequest =", "const executeStageDirect = async", ) result = _run_node( producer_helper + ortholog_request_helper + request_helper + direct_builder + """ const request = { referenceSpecies: ['At'], refSpeciesList: ['At'], scopeMode: 'target_plus_reference', ppiKaksReferenceSpecies: ['KaKsRef'], ppiReferenceSpecies: 'KaKsRef', referenceBundleSpecies: 'KaKsRef' }; const owner = { ppiReferenceTaxonId: '4555', ppiReferenceStorageKey: 'string_taxon_4555', ppiReferenceSelectionMode: 'tree_nearest', ppiReferenceUserConfirmed: true, dependencyOwner: 'ppi' }; const ortholog = buildPpiOwnedOrthologStage(request, owner, ['ppi']); const finalOrtholog = applyPpiOwnedOrthologFieldsToRequest( {...ortholog.params, ppiKaksReferenceSpecies: ['KaKsRef'], referenceSpecies: ['KaKsRef']}, 'ortholog', ortholog.params ); const directOrtholog = finalizeExecuteStageDirectRequest( {stage: 'ortholog', referenceSpecies: ['KaKsRef'], ppiKaksReferenceSpecies: ['KaKsRef']}, 'ortholog', ortholog.params ); const ppi = applyPpiOwnerFieldsToRequest({...request}, 'ppi', {}, owner, {}); console.log(JSON.stringify({ortholog, finalOrtholog, directOrtholog, ppi})); """ ) assert result["ortholog"]["params"]["ppiReferenceTaxonId"] == "4555" expected_owner = { "ppiReferenceTaxonId": "4555", "ppiReferenceStorageKey": "string_taxon_4555", "ppiReferenceSelectionMode": "tree_nearest", "ppiReferenceUserConfirmed": True, "dependencyOwner": "ppi", } for payload in (result["ortholog"]["params"], result["finalOrtholog"], result["directOrtholog"]): for field, expected in expected_owner.items(): assert payload[field] == expected for forbidden in ( "referenceSpecies", "refSpeciesList", "scopeMode", "ppiKaksReferenceSpecies", "ppiReferenceSpecies", "referenceBundleSpecies", ): assert forbidden not in payload assert result["ppi"]["referenceSpecies"] == ["At"] assert result["ppi"]["refSpeciesList"] == ["At"] for forbidden in ("ppiKaksReferenceSpecies", "ppiReferenceSpecies", "referenceBundleSpecies"): assert forbidden not in result["ppi"] def test_active_scheduler_executes_inserted_ppi_owned_ortholog_before_resume() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") helper = _source_block( html, "const orchestratePpiOwnerAfterReferenceReady =", "const monitorPpiReferenceBundleDownload =", ) result = _run_node( helper + """ (async () => { const calls = []; const producer = { id: 'ortholog', params: { ppiReferenceStorageKey: 'string_taxon_4555' } }; const ok = await orchestratePpiOwnerAfterReferenceReady({ requestBody: { stage: 'ppi' }, requestedStages: ['ppi'], isBatchExecuting: true, runPreflight: async () => { calls.push('preflight'); return {allowed: true, resolution: {status: 'needs_ortholog'}}; }, ensureOrtholog: async () => { calls.push('ensure'); return {ready: true, producerStage: producer}; }, executeProducer: async stage => { calls.push(`execute:${stage.id}`); return true; }, resume: async () => { calls.push('resume'); } }); console.log(JSON.stringify({ok, calls})); })(); """ ) assert result == { "ok": True, "calls": ["preflight", "ensure", "execute:ortholog", "resume"], } def test_orchestrator_waits_for_identify_and_does_not_resume_on_producer_failure() -> None: html = Path("apps/web/vue3/index.html").read_text(encoding="utf-8") helper = _source_block( html, "const orchestratePpiOwnerAfterReferenceReady =", "const monitorPpiReferenceBundleDownload =", ) result = _run_node( helper + """ (async () => { const calls = []; let identify = 'running'; const base = { requestBody: {stage: 'ppi'}, requestedStages: ['ppi'], runPreflight: async () => ({allowed: true, resolution: {status: 'needs_ortholog'}}), dependencyStatus: () => identify, waitForDependency: async () => { calls.push('wait:identify'); identify = 'completed'; return true; }, ensureOrtholog: async () => ({ready: true, producerStage: {id: 'ortholog'}}), resume: async () => { calls.push('resume'); } }; const failed = await orchestratePpiOwnerAfterReferenceReady({ ...base, executeProducer: async () => { calls.push('execute:failed'); return false; } }); const succeeded = await orchestratePpiOwnerAfterReferenceReady({ ...base, executeProducer: async () => { calls.push('execute:success'); return true; } }); console.log(JSON.stringify({failed, succeeded, calls})); })(); """ ) assert result == { "failed": False, "succeeded": True, "calls": ["wait:identify", "execute:failed", "execute:success", "resume"], }