117 lines
4.1 KiB
Python
117 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from gfa_core.wgs_scientific_identity import (
|
|
WgsIdentityError,
|
|
build_wgs_analysis_id,
|
|
build_wgs_reference_id,
|
|
build_wgs_sample_set_id,
|
|
sha256_file,
|
|
)
|
|
|
|
|
|
def test_analysis_identity_changes_with_method():
|
|
args = {
|
|
"stage": "wgs_variant_filter",
|
|
"upstream_sha256s": ["a" * 64],
|
|
"parameters": {"qd": 2.0},
|
|
"tool_versions": {"gatk": "4.6", "bcftools": "1.20"},
|
|
}
|
|
assert build_wgs_analysis_id(**args, method="gatk") != build_wgs_analysis_id(
|
|
**args, method="bcftools"
|
|
)
|
|
|
|
|
|
def test_wgs_sample_set_identity_uses_fastq_contents(tmp_path):
|
|
r1 = tmp_path / "S1_R1.fastq.gz"
|
|
r2 = tmp_path / "S1_R2.fastq.gz"
|
|
r1.write_bytes(b"r1-v1")
|
|
r2.write_bytes(b"r2-v1")
|
|
samples = build_wgs_sample_set_id([{"sample_id": "S1", "r1": str(r1), "r2": str(r2)}])
|
|
assert samples.startswith("wgs-samples:")
|
|
assert len(sha256_file(r1)) == 64
|
|
r1.write_bytes(b"r1-v2")
|
|
assert samples != build_wgs_sample_set_id([{"sample_id": "S1", "r1": str(r1), "r2": str(r2)}])
|
|
|
|
|
|
def test_wgs_identity_prefixes_are_canonical_and_validate_sha256s():
|
|
reference = build_wgs_reference_id("a" * 64)
|
|
assert reference.startswith("wgs-reference:")
|
|
with pytest.raises(WgsIdentityError, match="SHA256"):
|
|
build_wgs_analysis_id(
|
|
stage="wgs_qc",
|
|
upstream_sha256s=["not-a-sha"],
|
|
parameters={},
|
|
tool_versions={},
|
|
method="fastqc",
|
|
)
|
|
|
|
|
|
def test_wgs_sample_set_identity_rejects_parser_bypassing_invalid_records(tmp_path):
|
|
r1 = tmp_path / "S1_R1.fastq"
|
|
r2 = tmp_path / "S1_R2.fastq"
|
|
r1.write_bytes(b"r1")
|
|
r2.write_bytes(b"r2")
|
|
valid = {"sample_id": "S1", "r1": str(r1), "r2": str(r2)}
|
|
invalid_records = [
|
|
{**valid, "sample_id": "unsafe/id"},
|
|
{**valid, "r2": str(r1)},
|
|
{**valid, "r2": ""},
|
|
[valid, valid],
|
|
]
|
|
for invalid in invalid_records:
|
|
with pytest.raises(WgsIdentityError):
|
|
build_wgs_sample_set_id(invalid if isinstance(invalid, list) else [invalid])
|
|
with pytest.raises(WgsIdentityError, match="unique"):
|
|
build_wgs_sample_set_id([valid, valid])
|
|
|
|
|
|
def test_wgs_analysis_identity_canonicalizes_upstream_hash_order_and_requires_tool_evidence():
|
|
args = {
|
|
"stage": "wgs_variant_filter",
|
|
"parameters": {"qd": 2.0},
|
|
"tool_versions": {"gatk": "4.6"},
|
|
"method": "gatk",
|
|
}
|
|
assert build_wgs_analysis_id(**args, upstream_sha256s=["a" * 64, "b" * 64]) == build_wgs_analysis_id(
|
|
**args, upstream_sha256s=["b" * 64, "a" * 64]
|
|
)
|
|
for tool_versions in ({}, {"": "4.6"}, {"gatk": ""}):
|
|
with pytest.raises(WgsIdentityError, match="tool"):
|
|
build_wgs_analysis_id(**{**args, "tool_versions": tool_versions}, upstream_sha256s=["a" * 64])
|
|
|
|
|
|
@pytest.mark.parametrize("invalid_name", ["S1_R1.txt", "S1_R1.empty.fastq", "reads", "missing.fastq"])
|
|
def test_wgs_sample_set_identity_rejects_noncanonical_fastq_paths(tmp_path, invalid_name):
|
|
r2 = tmp_path / "S1_R2.fastq"
|
|
r2.write_bytes(b"r2")
|
|
r1 = tmp_path / invalid_name
|
|
if invalid_name.endswith(".txt"):
|
|
r1.write_bytes(b"r1")
|
|
elif invalid_name.endswith(".empty.fastq"):
|
|
r1.write_bytes(b"")
|
|
elif invalid_name == "reads":
|
|
r1.mkdir()
|
|
with pytest.raises(WgsIdentityError):
|
|
build_wgs_sample_set_id([{"sample_id": "S1", "r1": str(r1), "r2": str(r2)}])
|
|
|
|
|
|
def test_wgs_sample_set_identity_rejects_lexical_alias_mates(tmp_path):
|
|
r1 = tmp_path / "S1_R1.fastq"
|
|
r1.write_bytes(b"r1")
|
|
alias_dir = tmp_path / "alias"
|
|
alias_dir.mkdir()
|
|
lexical_alias = alias_dir / ".." / "S1_R1.fastq"
|
|
with pytest.raises(WgsIdentityError, match="different"):
|
|
build_wgs_sample_set_id([{"sample_id": "S1", "r1": str(r1), "r2": str(lexical_alias)}])
|
|
|
|
|
|
def test_wgs_sample_set_identity_rejects_symlink_alias_mates(tmp_path):
|
|
r1 = tmp_path / "S1_R1.fastq"
|
|
r1.write_bytes(b"r1")
|
|
symlink = tmp_path / "S1_R2.fastq"
|
|
symlink.symlink_to(r1)
|
|
with pytest.raises(WgsIdentityError, match="different"):
|
|
build_wgs_sample_set_id([{"sample_id": "S1", "r1": str(r1), "r2": str(symlink)}])
|