syntheogenesis / tests /test_plasmid_api.py
Tengo Gzirishvili
Adversarial audit of Plasmid Studio: fix XSS, DoS, and input-validation gaps
d67ba8e
Raw
History Blame Contribute Delete
4.42 kB
"""Endpoint tests for Plasmid Studio (Flask test client; auth stubbed, no DB).
Covers the sign-in gate + import / restriction / digest / export wiring."""
import types
import pytest
from dee import server
SEQ = ("TAATACGACTCACTATAG" + "GAATTC" + "AAAAGGGGAAAAGGGGAAAA"
+ "GAATTC" + "CCCCTTTTCCCCTTTTCCCC")
@pytest.fixture
def client():
app = server.create_app()
app.config.update(TESTING=True)
return app.test_client()
def _signed_in(monkeypatch):
monkeypatch.setattr(server._auth, "get_auth",
lambda: types.SimpleNamespace(anonymous=False, user_id="u1",
email="x@y.z", plan="free"))
def _anon(monkeypatch):
monkeypatch.setattr(server._auth, "get_auth",
lambda: types.SimpleNamespace(anonymous=True, user_id=None,
email=None, plan="anon"))
def test_import_requires_signin(client, monkeypatch):
_anon(monkeypatch)
r = client.post("/api/plasmid/import", json={"text": SEQ})
assert r.status_code == 403
assert r.get_json()["kind"] == "signin_required"
def test_import_raw_dna_annotates(client, monkeypatch):
_signed_in(monkeypatch)
r = client.post("/api/plasmid/import", json={"text": SEQ})
assert r.status_code == 200
p = r.get_json()["plasmid"]
assert p["length"] == len(SEQ)
assert p["topology"] == "circular"
assert any(f["name"] == "T7 promoter" for f in p["features"])
def test_import_bad_input_400(client, monkeypatch):
_signed_in(monkeypatch)
r = client.post("/api/plasmid/import", json={"text": "this is not dna @@@"})
assert r.status_code == 400
assert "error" in r.get_json()
def test_restriction(client, monkeypatch):
_signed_in(monkeypatch)
r = client.post("/api/plasmid/restriction",
json={"sequence": SEQ, "topology": "circular", "enzymes": ["EcoRI", "BamHI"]})
assert r.status_code == 200
a = r.get_json()["analysis"]
assert "EcoRI" in a["double_cutters"]
assert "BamHI" in a["non_cutters"]
def test_digest_fragments(client, monkeypatch):
_signed_in(monkeypatch)
r = client.post("/api/plasmid/digest",
json={"sequence": SEQ, "topology": "circular", "enzymes": ["EcoRI"]})
assert r.status_code == 200
d = r.get_json()["digest"]
assert d["n_fragments"] == 2
assert sum(d["fragments"]) == len(SEQ)
def test_digest_requires_enzyme(client, monkeypatch):
_signed_in(monkeypatch)
r = client.post("/api/plasmid/digest",
json={"sequence": SEQ, "topology": "circular", "enzymes": []})
assert r.status_code == 400
def test_import_rejects_oversize_text(client, monkeypatch):
_signed_in(monkeypatch)
from dee.core import plasmid as PL
monkeypatch.setattr(PL, "MAX_INPUT_TEXT", 2000)
r = client.post("/api/plasmid/import", json={"text": "A" * 5000})
assert r.status_code == 400
def test_export_sanitizes_malicious_features(client, monkeypatch):
_signed_in(monkeypatch)
# injection-laden colour + out-of-range coords + a flood of features:
# must not crash, must produce a valid GenBank (sanitized server-side).
feats = [{"name": "evil", "type": "cds", "start": 0, "end": 18,
"color": '#fff" onload="alert(1)'}]
feats += [{"name": "f", "type": "misc", "start": 0, "end": 3} for _ in range(20000)]
r = client.post("/api/plasmid/export", json={
"sequence": SEQ, "topology": "circular", "name": "pX", "features": feats, "format": "genbank"})
assert r.status_code == 200
body = r.data.decode("utf-8", "replace")
assert "LOCUS" in body
assert "onload=" not in body # injection scrubbed out of the export
def test_export_genbank_and_fasta(client, monkeypatch):
_signed_in(monkeypatch)
gb = client.post("/api/plasmid/export", json={
"sequence": SEQ, "topology": "circular", "name": "pTEST",
"features": [{"name": "T7 promoter", "type": "promoter", "start": 0, "end": 18, "strand": 1}],
"format": "genbank"})
assert gb.status_code == 200
assert b"LOCUS" in gb.data and b"promoter" in gb.data
fa = client.post("/api/plasmid/export",
json={"sequence": SEQ, "topology": "circular", "name": "pTEST", "format": "fasta"})
assert fa.status_code == 200
assert fa.data.startswith(b">")