"""
API test suite for mutation_pipeline.

Tests the unified pipeline entry point across six mutations spanning
different genes, chromosomes, and expected analysis outcomes.

Run with:  pytest tests/test_mutation_pipeline.py -v
"""

import pytest
import pandas as pd

from geney import mutation_pipeline, mutation_pipeline_batch, MutationResult
from geney.results import SplicingResult, TISResult, FoldingResult, TranscriptionResult


# ── Test mutations ──────────────────────────────────────────────────────────

MUTATIONS = [
    "ECH1:19:38831664:A:C",
    "WBP4:13:41076038:G:C",
    "PRKACB:1:84191312:G:C",
    "TCF25:16:89895209:T:G",
    "TNPO3:7:128990171:G:A",
    "KRAS:12:25227343:G:T",
]

COMPOUND_MUTATION = "KRAS:12:25227343:G:T|KRAS:12:25227344:A:T"


# ── Session-scoped result cache ─────────────────────────────────────────────
# Each mutation is run once per session and shared across all test classes.

@pytest.fixture(scope="session", params=MUTATIONS, ids=lambda m: m.split(":")[0])
def full_result(request):
    """Run mutation_pipeline once per mutation, cache for the session."""
    return request.param, mutation_pipeline(request.param, run_transcription=False)


@pytest.fixture(scope="session")
def kras_result():
    """Cached KRAS result with all analyses enabled (except transcription)."""
    return mutation_pipeline("KRAS:12:25227343:G:T", run_transcription=False)


# ── 1. Return-type checks ──────────────────────────────────────────────────

class TestReturnType:
    """mutation_pipeline always returns a MutationResult namedtuple."""

    def test_returns_namedtuple(self, full_result):
        _, r = full_result
        assert isinstance(r, MutationResult)

    def test_has_all_fields(self, full_result):
        _, r = full_result
        assert set(r._fields) == {"splicing", "tis", "folding", "transcription", "transcript", "mutation_context"}

    def test_splicing_is_splicing_result(self, full_result):
        _, r = full_result
        assert isinstance(r.splicing, SplicingResult)

    def test_tis_is_dict_or_none(self, full_result):
        _, r = full_result
        assert r.tis is None or isinstance(r.tis, TISResult)

    def test_folding_is_dict_or_none(self, full_result):
        _, r = full_result
        assert r.folding is None or isinstance(r.folding, FoldingResult)

    def test_transcription_is_dict_or_none(self, full_result):
        _, r = full_result
        assert r.transcription is None or isinstance(r.transcription, TranscriptionResult)


# ── 2. Transcript & mutation context ───────────────────────────────────────

class TestTranscript:
    """The returned transcript should be a fully-built mutated object."""

    def test_transcript_not_none(self, full_result):
        _, r = full_result
        assert r.transcript is not None

    def test_has_pre_mrna(self, full_result):
        _, r = full_result
        assert hasattr(r.transcript, "pre_mrna")
        assert r.transcript.pre_mrna is not None

    def test_has_mature_mrna(self, full_result):
        _, r = full_result
        assert hasattr(r.transcript, "mature_mrna")
        assert r.transcript.mature_mrna is not None
        assert len(r.transcript.mature_mrna.seq) > 0

    def test_has_protein(self, full_result):
        _, r = full_result
        assert hasattr(r.transcript, "protein")
        assert isinstance(r.transcript.protein, str)
        assert len(r.transcript.protein) > 0

    def test_has_transcript_id(self, full_result):
        _, r = full_result
        assert getattr(r.transcript, "transcript_id", None) is not None


class TestMutationContext:
    """Mutation context should be a SeqMat clone around the mutation position."""

    def test_mutation_context_not_none(self, full_result):
        _, r = full_result
        assert r.mutation_context is not None

    def test_mutation_context_has_seq(self, full_result):
        _, r = full_result
        assert hasattr(r.mutation_context, "seq")
        assert len(r.mutation_context.seq) > 0

    def test_mutation_context_has_index(self, full_result):
        _, r = full_result
        assert hasattr(r.mutation_context, "index")
        assert len(r.mutation_context.index) > 0

    def test_mutation_context_default_size(self, full_result):
        """Default context_bp=1000 -> up to ~2000 bp."""
        _, r = full_result
        assert len(r.mutation_context.seq) <= 2001

    def test_custom_context_size(self):
        r = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            context_bp=500,
            run_splicing=False, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        assert len(r.mutation_context.seq) <= 1001


# ── 3. Splicing analysis ───────────────────────────────────────────────────

class TestSplicing:
    """Verify splicing / oncosplice results."""

    def test_splicing_metadata(self, full_result):
        mut_id, r = full_result
        if r.splicing.empty:
            pytest.skip(f"No splicing isoforms for {mut_id}")
        assert r.splicing.mut_id == mut_id
        gene = mut_id.split(":")[0]
        assert r.splicing.gene == gene
        assert r.splicing.transcript_id is not None
        assert r.splicing.splicing_engine != ""
        assert r.splicing.central_position > 0

    def test_splicing_has_oncosplice_score(self, full_result):
        mut_id, r = full_result
        if r.splicing.empty:
            pytest.skip(f"No splicing isoforms for {mut_id}")
        assert "oncosplice_score" in r.splicing.isoforms.columns or "summary" in r.splicing.isoforms.columns

    def test_splicing_score(self, full_result):
        mut_id, r = full_result
        if r.splicing.empty:
            pytest.skip(f"No splicing isoforms for {mut_id}")
        assert isinstance(r.splicing.score, float)
        assert r.splicing.score >= 0.0

    def test_splicing_has_conservation_vector(self, full_result):
        mut_id, r = full_result
        if r.splicing.empty:
            pytest.skip(f"No splicing isoforms for {mut_id}")
        vec = r.splicing.conservation_vector
        assert vec is not None
        assert hasattr(vec, "__len__") and len(vec) > 0

    def test_splicing_has_reference_sequences(self, full_result):
        mut_id, r = full_result
        if r.splicing.empty:
            pytest.skip(f"No splicing isoforms for {mut_id}")
        assert isinstance(r.splicing.reference_protein, str)
        assert len(r.splicing.reference_protein) > 0
        assert isinstance(r.splicing.reference_mrna, str)
        assert len(r.splicing.reference_mrna) > 0

    def test_splicing_has_aligned_proteins(self, full_result):
        mut_id, r = full_result
        if r.splicing.empty:
            pytest.skip(f"No splicing isoforms for {mut_id}")
        assert "aligned_reference_protein" in r.splicing.isoforms.columns
        assert "aligned_variant_protein" in r.splicing.isoforms.columns

    def test_aligned_proteins_are_nonempty_strings(self, full_result):
        mut_id, r = full_result
        if r.splicing.empty:
            pytest.skip(f"No splicing isoforms for {mut_id}")
        row = r.splicing.isoforms.iloc[0]
        assert isinstance(row["aligned_reference_protein"], str)
        assert isinstance(row["aligned_variant_protein"], str)
        assert len(row["aligned_reference_protein"]) > 0
        assert len(row["aligned_variant_protein"]) > 0

    def test_aligned_proteins_same_length(self, full_result):
        """After alignment, ref and var should be equal length (gap-padded)."""
        mut_id, r = full_result
        if r.splicing.empty:
            pytest.skip(f"No splicing isoforms for {mut_id}")
        for _, row in r.splicing.isoforms.iterrows():
            assert len(row["aligned_reference_protein"]) == len(row["aligned_variant_protein"])

    def test_to_dataframe_roundtrip(self, full_result):
        """to_dataframe() should reconstruct the flat format."""
        mut_id, r = full_result
        if r.splicing.empty:
            pytest.skip(f"No splicing isoforms for {mut_id}")
        df = r.splicing.to_dataframe()
        assert isinstance(df, pd.DataFrame)
        assert "mut_id" in df.columns
        assert "gene" in df.columns
        assert "conservation_vector" in df.columns

    def test_splicing_disabled_returns_empty(self):
        r = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            run_splicing=False, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        assert r.splicing.empty


# ── 4. TIS analysis ────────────────────────────────────────────────────────

class TestTIS:
    """Verify TIS result dict structure."""

    def test_tis_has_status(self, full_result):
        _, r = full_result
        if r.tis is None:
            pytest.skip("TIS disabled")
        assert "status" in r.tis
        assert r.tis["status"] in {"completed", "skipped", "error"}

    def test_completed_tis_has_metrics(self, full_result):
        _, r = full_result
        if r.tis is None or r.tis.get("status") != "completed":
            pytest.skip("TIS not completed")
        expected_keys = {
            "canonical_prob_ref", "canonical_prob_var", "canonical_prob_change",
            "tis_shift_score", "initiation_intact", "num_alternative_tis", "summary",
        }
        assert expected_keys.issubset(r.tis.keys())

    def test_completed_tis_probabilities_valid(self, full_result):
        _, r = full_result
        if r.tis is None or r.tis.get("status") != "completed":
            pytest.skip("TIS not completed")
        for key in ("canonical_prob_ref", "canonical_prob_var"):
            val = r.tis[key]
            assert isinstance(val, (int, float))
            assert 0.0 <= val <= 1.0

    def test_skipped_tis_has_reason(self, full_result):
        _, r = full_result
        if r.tis is None or r.tis.get("status") != "skipped":
            pytest.skip("TIS not skipped")
        assert "reason" in r.tis

    def test_completed_tis_has_gained_alternatives(self, full_result):
        _, r = full_result
        if r.tis is None or r.tis.get("status") != "completed":
            pytest.skip("TIS not completed")
        assert "gained_alternative_tis" in r.tis
        assert isinstance(r.tis["gained_alternative_tis"], list)

    def test_gained_alternatives_have_required_fields(self, full_result):
        _, r = full_result
        if r.tis is None or r.tis.get("status") != "completed":
            pytest.skip("TIS not completed")
        required = {"position", "location", "in_frame", "var_probability", "prob_change"}
        for alt in r.tis["gained_alternative_tis"]:
            assert required.issubset(alt.keys()), f"Missing keys in {alt.keys()}"

    def test_gained_alternatives_all_positive_change(self, full_result):
        _, r = full_result
        if r.tis is None or r.tis.get("status") != "completed":
            pytest.skip("TIS not completed")
        for alt in r.tis["gained_alternative_tis"]:
            assert alt["prob_change"] > 0

    def test_completed_tis_has_atg_sites(self, full_result):
        _, r = full_result
        if r.tis is None or r.tis.get("status") != "completed":
            pytest.skip("TIS not completed")
        assert "atg_sites" in r.tis
        sites = r.tis["atg_sites"]
        assert isinstance(sites, list)
        assert len(sites) > 0  # at least the canonical ATG

    def test_atg_sites_have_required_fields(self, full_result):
        _, r = full_result
        if r.tis is None or r.tis.get("status") != "completed":
            pytest.skip("TIS not completed")
        required = {"position", "location", "in_frame", "ref_probability", "var_probability", "prob_change"}
        for site in r.tis["atg_sites"]:
            assert required.issubset(site.keys()), f"Missing keys in ATG site: {site.keys()}"

    def test_atg_sites_include_canonical(self, full_result):
        _, r = full_result
        if r.tis is None or r.tis.get("status") != "completed":
            pytest.skip("TIS not completed")
        locations = [s["location"] for s in r.tis["atg_sites"]]
        assert "canonical" in locations

    def test_gained_alternatives_sorted_by_change(self, full_result):
        """Gained alternatives should be sorted largest prob_change first."""
        _, r = full_result
        if r.tis is None or r.tis.get("status") != "completed":
            pytest.skip("TIS not completed")
        gains = r.tis["gained_alternative_tis"]
        if len(gains) >= 2:
            changes = [a["prob_change"] for a in gains]
            assert changes == sorted(changes, reverse=True)

    def test_tis_disabled_returns_none(self):
        r = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            run_tis=False, run_splicing=False, run_folding=False,
            run_transcription=False,
        )
        assert r.tis is None


# ── 5. Folding analysis ────────────────────────────────────────────────────

class TestFolding:
    """Verify folding result dict structure."""

    def test_folding_has_status(self, full_result):
        _, r = full_result
        if r.folding is None:
            pytest.skip("Folding disabled")
        assert "status" in r.folding

    def test_completed_folding_has_delta_mfe(self, full_result):
        _, r = full_result
        if r.folding is None or r.folding.get("status") != "completed":
            pytest.skip("Folding not completed")
        assert "delta_mfe" in r.folding
        assert "mutations" in r.folding
        assert isinstance(r.folding["mutations"], list)

    def test_completed_folding_mutations_structure(self, full_result):
        _, r = full_result
        if r.folding is None or r.folding.get("status") != "completed":
            pytest.skip("Folding not completed")
        for entry in r.folding["mutations"]:
            assert "status" in entry
            assert "genomic_position" in entry

    def test_completed_folding_has_window_detail(self, full_result):
        """Each ok mutation entry should have ref/var structure and window."""
        _, r = full_result
        if r.folding is None or r.folding.get("status") != "completed":
            pytest.skip("Folding not completed")
        ok_entries = [e for e in r.folding["mutations"] if e["status"] == "ok"]
        if not ok_entries:
            pytest.skip("No ok folding entries")
        expected = {"ref_window", "var_window", "ref_structure", "var_structure",
                    "mfe_ref", "mfe_var", "mutation_offset"}
        for entry in ok_entries:
            assert expected.issubset(entry.keys()), f"Missing keys: {expected - entry.keys()}"

    def test_folding_structures_are_dot_bracket(self, full_result):
        _, r = full_result
        if r.folding is None or r.folding.get("status") != "completed":
            pytest.skip("Folding not completed")
        ok_entries = [e for e in r.folding["mutations"] if e["status"] == "ok"]
        if not ok_entries:
            pytest.skip("No ok folding entries")
        dot_bracket_chars = set(".()")
        for entry in ok_entries:
            assert set(entry["ref_structure"]).issubset(dot_bracket_chars)
            assert set(entry["var_structure"]).issubset(dot_bracket_chars)

    def test_folding_window_length_matches(self, full_result):
        _, r = full_result
        if r.folding is None or r.folding.get("status") != "completed":
            pytest.skip("Folding not completed")
        ok_entries = [e for e in r.folding["mutations"] if e["status"] == "ok"]
        if not ok_entries:
            pytest.skip("No ok folding entries")
        for entry in ok_entries:
            wlen = len(entry["ref_window"])
            assert len(entry["var_window"]) == wlen
            assert len(entry["ref_structure"]) == wlen
            assert len(entry["var_structure"]) == wlen

    def test_folding_mfe_are_numeric(self, full_result):
        _, r = full_result
        if r.folding is None or r.folding.get("status") != "completed":
            pytest.skip("Folding not completed")
        ok_entries = [e for e in r.folding["mutations"] if e["status"] == "ok"]
        if not ok_entries:
            pytest.skip("No ok folding entries")
        for entry in ok_entries:
            assert isinstance(entry["mfe_ref"], (int, float))
            assert isinstance(entry["mfe_var"], (int, float))
            assert entry["mfe_ref"] <= 0  # MFE should be negative or zero
            assert entry["mfe_var"] <= 0

    def test_folding_disabled_returns_none(self):
        r = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            run_folding=False, run_splicing=False, run_tis=False,
            run_transcription=False,
        )
        assert r.folding is None


# ── 6. Selective execution ──────────────────────────────────────────────────

class TestSelectiveExecution:
    """Toggling run_splicing / run_tis / run_folding / run_transcription independently."""

    def test_splicing_only(self):
        r = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            run_splicing=True, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        assert isinstance(r.splicing, SplicingResult)
        assert r.tis is None
        assert r.folding is None
        assert r.transcription is None
        assert r.transcript is not None

    def test_tis_only(self):
        r = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            run_splicing=False, run_tis=True, run_folding=False,
            run_transcription=False,
        )
        assert r.splicing.empty
        assert r.tis is not None
        assert r.folding is None
        assert r.transcription is None

    def test_folding_only(self):
        r = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            run_splicing=False, run_tis=False, run_folding=True,
            run_transcription=False,
        )
        assert r.splicing.empty
        assert r.tis is None
        assert r.folding is not None
        assert r.transcription is None

    def test_all_disabled(self):
        r = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            run_splicing=False, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        assert r.splicing.empty
        assert r.tis is None
        assert r.folding is None
        assert r.transcription is None
        assert r.transcript is not None
        assert r.mutation_context is not None

    def test_all_enabled(self, kras_result):
        r = kras_result
        assert isinstance(r.splicing, SplicingResult)
        assert r.tis is not None
        assert r.folding is not None

    def test_transcription_disabled_returns_none(self):
        r = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            run_splicing=False, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        assert r.transcription is None


# ── 7. Gene-load timeout ───────────────────────────────────────────────────

class TestTimeout:
    """gene_load_timeout parameter."""

    def test_unreachable_timeout_raises(self):
        with pytest.raises(TimeoutError):
            mutation_pipeline("KRAS:12:25227343:G:T", gene_load_timeout=0.0001)

    def test_generous_timeout_succeeds(self):
        r = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            gene_load_timeout=120,
            run_splicing=False, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        assert r.transcript is not None


# ── 8. Cross-gene consistency ───────────────────────────────────────────────

class TestCrossGene:
    """All six test mutations should produce valid results."""

    @pytest.mark.parametrize("mut_id", MUTATIONS, ids=lambda m: m.split(":")[0])
    def test_pipeline_does_not_raise(self, mut_id):
        r = mutation_pipeline(mut_id, run_splicing=False, run_tis=False, run_folding=False, run_transcription=False)
        assert isinstance(r, MutationResult)
        assert r.transcript is not None

    @pytest.mark.parametrize("mut_id", MUTATIONS, ids=lambda m: m.split(":")[0])
    def test_gene_name_roundtrip(self, mut_id):
        gene = mut_id.split(":")[0]
        r = mutation_pipeline(mut_id, run_splicing=False, run_tis=False, run_folding=False, run_transcription=False)
        tid = getattr(r.transcript, "transcript_id", "")
        assert tid, f"No transcript_id for {gene}"


# ── 9. Edge cases ───────────────────────────────────────────────────────────

class TestEdgeCases:
    """Invalid input, nonexistent genes, out-of-range positions, compounds."""

    # -- malformed mutation IDs -----------------------------------------------

    def test_empty_string_raises(self):
        with pytest.raises((ValueError, AssertionError)):
            mutation_pipeline("")

    def test_garbage_string_raises(self):
        with pytest.raises((ValueError, AssertionError)):
            mutation_pipeline("not-a-mutation")

    def test_missing_fields_raises(self):
        with pytest.raises((ValueError, AssertionError)):
            mutation_pipeline("KRAS:12:25227343")

    def test_non_numeric_position_raises(self):
        with pytest.raises((ValueError, AssertionError)):
            mutation_pipeline("KRAS:12:abc:G:T")

    def test_invalid_allele_chars_raises(self):
        with pytest.raises((ValueError, AssertionError)):
            mutation_pipeline("KRAS:12:25227343:Z:Q")

    # -- nonexistent gene → graceful empty result -----------------------------

    def test_nonexistent_gene_returns_empty(self):
        r = mutation_pipeline("FAKEGENE999:1:100000:A:T")
        assert isinstance(r, MutationResult)
        assert r.splicing.empty
        assert r.tis is None
        assert r.folding is None
        assert r.transcription is None
        assert r.transcript is None
        assert r.mutation_context is None

    # -- position outside all transcripts → graceful empty result -------------

    def test_position_outside_transcripts(self):
        """Real gene, but position far outside any transcript."""
        r = mutation_pipeline("KRAS:12:1:A:T")
        assert isinstance(r, MutationResult)
        assert r.splicing.empty
        assert r.transcript is None

    # -- compound / epistatic mutations ---------------------------------------

    def test_compound_mutation_returns_result(self):
        r = mutation_pipeline(
            COMPOUND_MUTATION,
            run_splicing=False, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        assert isinstance(r, MutationResult)
        assert r.transcript is not None

    def test_compound_mutation_protein_differs_from_single(self):
        """Compound should produce a different mutated transcript than single."""
        single = mutation_pipeline(
            "KRAS:12:25227343:G:T",
            run_splicing=False, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        compound = mutation_pipeline(
            COMPOUND_MUTATION,
            run_splicing=False, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        # Both should succeed; proteins may or may not differ depending on
        # whether the second mutation is in a coding region, but both should
        # be valid non-empty strings.
        assert len(single.transcript.protein) > 0
        assert len(compound.transcript.protein) > 0

    def test_compound_mutation_context_built(self):
        r = mutation_pipeline(
            COMPOUND_MUTATION,
            run_splicing=False, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        assert r.mutation_context is not None
        assert len(r.mutation_context.seq) > 0


# ── 10. Batch pipeline ────────────────────────────────────────────────────────

BATCH_MUTATIONS = [
    "KRAS:12:25227343:G:T",
    "ECH1:19:38831664:A:C",
    "WBP4:13:41076038:G:C",
]


@pytest.fixture(scope="session")
def batch_results():
    """Run batch pipeline once for the session."""
    return mutation_pipeline_batch(BATCH_MUTATIONS, run_transcription=False)


@pytest.fixture(scope="session")
def serial_results():
    """Run serial pipeline once per mutation for the session."""
    return [mutation_pipeline(m, run_transcription=False) for m in BATCH_MUTATIONS]


class TestBatchPipeline:
    """mutation_pipeline_batch should return identical results to serial."""

    def test_returns_list(self, batch_results):
        assert isinstance(batch_results, list)
        assert len(batch_results) == len(BATCH_MUTATIONS)

    def test_all_mutation_results(self, batch_results):
        for r in batch_results:
            assert isinstance(r, MutationResult)

    def test_splicing_matches_serial(self, batch_results, serial_results):
        for b, s in zip(batch_results, serial_results):
            assert len(b.splicing) == len(s.splicing)
            if not b.splicing.empty:
                assert b.splicing.mut_id == s.splicing.mut_id
                assert b.splicing.gene == s.splicing.gene
                assert b.splicing.transcript_id == s.splicing.transcript_id

    def test_tis_status_matches_serial(self, batch_results, serial_results):
        for b, s in zip(batch_results, serial_results):
            if s.tis is None:
                assert b.tis is None
            else:
                assert b.tis is not None
                assert b.tis.get("status") == s.tis.get("status")

    def test_tis_metrics_match_serial(self, batch_results, serial_results):
        for b, s in zip(batch_results, serial_results):
            if s.tis is None or s.tis.get("status") != "completed":
                continue
            assert b.tis is not None
            for key in ("canonical_prob_ref", "canonical_prob_var",
                        "tis_shift_score", "initiation_intact",
                        "num_alternative_tis"):
                assert b.tis[key] == s.tis[key], f"TIS mismatch on {key}"

    def test_folding_matches_serial(self, batch_results, serial_results):
        for b, s in zip(batch_results, serial_results):
            if s.folding is None:
                assert b.folding is None
            else:
                assert b.folding is not None
                assert b.folding.get("status") == s.folding.get("status")
                if s.folding.get("status") == "completed":
                    assert b.folding.get("delta_mfe") == s.folding.get("delta_mfe")

    def test_transcript_not_none(self, batch_results):
        for r in batch_results:
            assert r.transcript is not None

    def test_mutation_context_not_none(self, batch_results):
        for r in batch_results:
            assert r.mutation_context is not None

    def test_empty_input(self):
        assert mutation_pipeline_batch([]) == []

    def test_nonexistent_gene(self):
        results = mutation_pipeline_batch(["FAKEGENE999:1:100000:A:T"])
        assert len(results) == 1
        r = results[0]
        assert r.splicing.empty
        assert r.transcript is None

    def test_selective_execution(self):
        results = mutation_pipeline_batch(
            BATCH_MUTATIONS[:1],
            run_splicing=False, run_tis=False, run_folding=False,
            run_transcription=False,
        )
        assert len(results) == 1
        r = results[0]
        assert r.splicing.empty
        assert r.tis is None
        assert r.folding is None
        assert r.transcription is None
        assert r.transcript is not None
