from __future__ import annotations from pathlib import Path import pytest PROJECT_ROOT = Path(__file__).resolve().parents[2] CONTRACTS = { "wgs_qc": ("wgs_sample_config", "clean_reads_r1", "clean_reads_r2", "fastp_html", "fastp_json"), "wgs_index": ("reference_fasta", "reference_fai", "reference_dict", "bwa_mem2_index_component"), "wgs_align": ("clean_reads_r1", "clean_reads_r2", "reference_fasta", "reference_fai", "reference_dict", "bwa_mem2_index_component", "sorted_bam", "sorted_bam_index", "align_log"), "wgs_markdup": ("sorted_bam", "sorted_bam_index", "markdup_bam", "markdup_bam_index", "markdup_metrics", "markdup_log"), "wgs_bam_qc": ("markdup_bam", "markdup_bam_index", "reference_fai", "bam_flagstat", "bam_depth_summary", "bam_qc_summary", "bam_qc_report"), "wgs_haplotype_calling": ("markdup_bam", "markdup_bam_index", "bam_qc_summary", "reference_fasta", "reference_fai", "reference_dict", "gvcf", "gvcf_index", "haplotype_log"), "wgs_gvcf_combine": ("gvcf", "gvcf_index", "reference_fasta", "reference_fai", "reference_dict", "combined_gvcf", "combined_gvcf_index", "combine_log"), "wgs_joint_genotyping": ("combined_gvcf", "combined_gvcf_index", "reference_fasta", "reference_fai", "reference_dict", "joint_vcf", "joint_vcf_index", "joint_summary", "joint_log"), "wgs_variant_select": ("joint_vcf", "joint_vcf_index", "selected_snp_vcf", "selected_snp_vcf_index", "selected_indel_vcf", "selected_indel_vcf_index", "variant_select_report"), "wgs_variant_filter": ("selected_snp_vcf", "selected_snp_vcf_index", "selected_indel_vcf", "selected_indel_vcf_index", "filtered_snp_vcf", "filtered_snp_vcf_index", "pass_snp_vcf", "pass_snp_vcf_index", "filtered_indel_vcf", "filtered_indel_vcf_index", "pass_indel_vcf", "pass_indel_vcf_index", "variant_filter_report"), "wgs_impute": ("pass_snp_vcf", "pass_snp_vcf_index", "imputed_snp_vcf", "imputed_snp_vcf_index", "impute_report", "impute_log"), "wgs_maf_missing_filter": ("imputed_snp_vcf", "imputed_snp_vcf_index", "final_snp_vcf", "final_snp_vcf_index", "maf_missing_report", "maf_missing_stats"), } CANONICAL_FILENAME_TOKENS = { "wgs_qc": ".clean.R1.fastq.gz", "wgs_index": ".reference.fasta.fai", "wgs_align": ".sorted.bam", "wgs_markdup": ".markdup.bam", "wgs_bam_qc": ".flagstat.txt", "wgs_haplotype_calling": ".g.vcf.gz", "wgs_gvcf_combine": ".combined.g.vcf.gz", "wgs_joint_genotyping": ".joint.vcf.gz", "wgs_variant_select": ".selected.snp.vcf.gz", "wgs_variant_filter": ".filtered.snp.vcf.gz", "wgs_impute": ".imputed.snp.vcf.gz", "wgs_maf_missing_filter": ".final.snp.vcf.gz", } def _section(text: str, heading: str) -> str: return text.split(f"## {heading}\n", 1)[1].split("\n## ", 1)[0] @pytest.mark.parametrize("stage,roles", CONTRACTS.items()) def test_wgs_contract_locks_roles_filenames_indices_identities_and_gates(stage: str, roles: tuple[str, ...]) -> None: contract = PROJECT_ROOT / "gfa_core" / "schemas" / "stages" / f"{stage}_contract_v1.md" text = contract.read_text(encoding="utf-8") for phrase in ( f"Stage: `{stage}`", "## Consumes", "## Provides", "## Canonical filenames", "## Required indices", "## Scientific identities", "## Parameter snapshot", "status=success", "artifacts={}", ): assert phrase in text for role in roles: assert f"`{role}`" in text assert CANONICAL_FILENAME_TOKENS[stage] in _section(text, "Canonical filenames") failure = _section(text, "Success and failure evidence") assert ".attempts/" in failure assert "atomically publish" in failure assert "old success" in failure assert "artifacts={}" in failure def test_wgs_index_contract_locks_exact_bwa_mem2_components() -> None: text = (PROJECT_ROOT / "gfa_core/schemas/stages/wgs_index_contract_v1.md").read_text(encoding="utf-8") indices = _section(text, "Required indices") for component in (".0123", ".amb", ".ann", ".bwt.2bit.64", ".pac"): assert component in indices def test_wgs_haplotype_contract_requires_successful_bam_qc_gate() -> None: text = (PROJECT_ROOT / "gfa_core/schemas/stages/wgs_haplotype_calling_contract_v1.md").read_text(encoding="utf-8") assert "successful `wgs_bam_qc`" in _section(text, "Consumes") @pytest.mark.parametrize("stage", ["wgs_variant_select", "wgs_variant_filter"]) def test_variant_contracts_lock_confirmed_gatk_to_bcftools_fallback(stage: str) -> None: text = (PROJECT_ROOT / "gfa_core" / "schemas" / "stages" / f"{stage}_contract_v1.md").read_text(encoding="utf-8") assert "method=gatk" in _section(text, "Parameter snapshot") failure = _section(text, "Success and failure evidence") assert "Only after GATK failure" in failure assert "user confirmation" in failure assert "method=bcftools" in failure