"""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