syntheogenesis / tests /test_clone_sim.py
Tengo Gzirishvili
Plasmid Studio M3 + M4: sequence editor + cloning simulation
ec0167c
Raw
History Blame Contribute Delete
4.57 kB
"""Unit tests for the cloning simulator (dee/core/cloning.py)."""
import pytest
from dee.core import cloning as C
# ── Gibson / overlap assembly ───────────────────────────────────────────────
def test_gibson_native_overlaps_circular():
# three fragments sharing 20-bp overlaps, arranged head-to-tail in a circle.
o1 = "ACGTACGTACGTACGTACGT" # A↔B overlap
o2 = "TTTTGGGGCCCCAAAATTTT" # B↔C overlap
o3 = "GGGGCCCCAAAATTTTGGGG" # C↔A overlap
A = o3 + "AAAAAAAAAAAA" + o1
B = o1 + "CCCCCCCCCCCC" + o2
Cc = o2 + "GGGGGGGGGGGG" + o3
out = C.gibson([{"name": "A", "seq": A}, {"name": "B", "seq": B}, {"name": "C", "seq": Cc}])
assert out["ok"] is True
assert out["topology"] == "circular"
assert all(j["kind"] == "native" for j in out["junctions"])
assert out["length"] == len(A) + len(B) + len(Cc) - 60 # minus three 20-bp overlaps
def test_gibson_auto_orders_fragments():
o1 = "ACGTACGTACGTACGTACGT"
o2 = "TTTTGGGGCCCCAAAATTTT"
A = "AAAAAAAA" + o1
B = o1 + "CCCCCCCC" + o2
Cc = o2 + "GGGGGGGG"
out = C.gibson([{"name": "C", "seq": Cc}, {"name": "A", "seq": A}, {"name": "B", "seq": B}],
circular=False)
assert out["ok"] is True
assert out["order"] == ["A", "B", "C"]
assert out["length"] == len(A) + len(B) + len(Cc) - 40
def test_gibson_designs_overlaps_and_primers_for_bare_fragments():
A = "ATGGTGAGCAAGGGCGAGGAGCTGTTCACC"
B = "GACGAGCTGTACAAGTAACTCGAGCTAGCAT"
out = C.gibson([{"name": "A", "seq": A}, {"name": "B", "seq": B}], circular=False)
assert out["ok"] is True
assert any(j["kind"] == "designed" for j in out["junctions"])
assert out["length"] == len(A) + len(B)
prim = {p["fragment"]: p for p in out["primers"]}
assert prim["B"]["forward_tail"] > 0
assert prim["B"]["forward"].startswith(A[-25:])
def test_gibson_needs_two_fragments():
assert C.gibson([{"name": "x", "seq": "ACGT"}])["ok"] is False
# ── Golden Gate ──────────────────────────────────────────────────────────────
def _bsa_part(left_oh, body, right_oh):
return "GAATT" + "GGTCTC" + "T" + left_oh + body + right_oh + "T" + "GAGACC" + "TTAAG"
def test_excise_part_reads_overhangs():
p = C._excise_part(_bsa_part("AAAA", "GCTAGCGGGG", "CCCC"), "BsaI")
assert p["left_oh"] == "AAAA"
assert p["right_oh"] == "CCCC"
assert p["inner"] == "AAAA" + "GCTAGCGGGG" + "CCCC"
def test_golden_gate_assembles_circle():
p1 = _bsa_part("AAAA", "GCTAGCAAAA", "CCCC")
p2 = _bsa_part("CCCC", "TTGGCCAATT", "AAAA")
out = C.golden_gate([{"name": "P1", "seq": p1}, {"name": "P2", "seq": p2}], enzyme="BsaI")
assert out["ok"] is True
assert out["topology"] == "circular"
assert out["length"] == (4 + len("GCTAGCAAAA")) + (4 + len("TTGGCCAATT"))
assert out["order"] == ["P1", "P2"]
def test_golden_gate_rejects_mismatched_overhangs():
p1 = _bsa_part("AAAA", "GCTAGCAAAA", "CCCC")
p2 = _bsa_part("GGGG", "TTGGCCAATT", "AAAA")
out = C.golden_gate([{"name": "P1", "seq": p1}, {"name": "P2", "seq": p2}], enzyme="BsaI")
assert out["ok"] is False
def test_golden_gate_rejects_non_cassette():
out = C.golden_gate([{"name": "P1", "seq": "ACGTACGTACGT"}, {"name": "P2", "seq": "TTTTGGGG"}])
assert out["ok"] is False
# ── restriction cut-and-ligate ──────────────────────────────────────────────
def test_restriction_clone_single_enzyme():
vector = "AAAACCCC" + "GAATTC" + "TTTTGGGGCCCCAAAA"
insert = "CC" + "GAATTC" + "ATGCATGCATGCATGC" + "GAATTC" + "GG"
out = C.restriction_clone(vector, insert, "EcoRI", vector_topology="circular")
assert out["ok"] is True
assert out["topology"] == "circular"
assert "ATGCATGCATGCATGC" in out["assembled"]
assert out["length"] > len(vector)
def test_restriction_clone_requires_unique_vector_site():
vector = "GAATTCAAAAGAATTC"
insert = "GAATTCATGCGAATTC"
out = C.restriction_clone(vector, insert, "EcoRI", vector_topology="linear")
assert out["ok"] is False
def test_restriction_clone_requires_flanked_insert():
vector = "AAAAGAATTCTTTT"
insert = "ATGCATGCATGC"
out = C.restriction_clone(vector, insert, "EcoRI")
assert out["ok"] is False