1"""The controller routes read only configs/ (and the caller's own uploads), and fail in a sentence.
3``config_path`` and ``controller_config_path`` were opened as given: POST
4/api/control/simulate-from-config {config_path: "<anywhere>/design.json"} opened a file outside the
5app, any user could run another user's upload (output/user_configs/<them>/x.yaml) through the
6controller past design sharing, and the 500 carried a full traceback with server paths.
9from __future__
import annotations
16sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18pytest.importorskip(
"fastapi", reason=
"API tests need fastapi")
19pytest.importorskip(
"httpx", reason=
"fastapi TestClient needs httpx")
21from fastapi.testclient
import TestClient
26H = {
"X-Auth-Email":
"control-paths@berkeley.edu"}
35 """Record every path the route asks the loader to open; refuse to actually solve."""
38 def load(path, *a, **k):
39 seen.append(str(path))
42 monkeypatch.setattr(control_router,
"load_engine_config", load)
43 monkeypatch.setattr(control_router,
"load_controller_config", load)
49 return TestClient(backend_main.app, raise_server_exceptions=
False)
52@pytest.mark.parametrize("route", ["/api/control/simulate-from-config"])
54 other = tmp_path /
"otheruser" /
"design.yaml"
56 other.write_text(
"chamber: 1\n")
57 for path
in (str(other),
"../output/user_configs/someone-else/design.yaml",
"configs/../../../etc/passwd"):
58 r = client.post(route, json={
"config_path": path}, headers=H)
59 assert r.status_code == 400, (path, r.status_code, r.text[:300])
60 assert opened == [], f
"the route opened {opened}"
64 r = client.post(
"/api/control/simulate-from-config", json={
"config_path":
"configs/default.yaml"}, headers=H)
65 assert opened
and opened[0].endswith(os.path.join(
"configs",
"default.yaml")), opened
66 assert r.status_code == 500
and "Traceback" not in r.text
and "File \\\"" not in r.text, r.text[:400]
70 r = client.post(
"/api/control/init", json={
"controller_config_path":
"../../etc/hosts.yaml"}, headers=H)
71 assert r.status_code == 400, r.text[:300]
76 r = client.post(
"/api/control/simulate-from-config-stream", json={
"config_path": str(tmp_path /
"x.yaml")}, headers=H)
77 assert r.status_code == 200
78 assert "must name a .yaml file under configs/" in r.text
and opened == []
85 raise RuntimeError(
"the solve fell over")
87 monkeypatch.setattr(runner_mod,
"PintleEngineRunner", boom)
88 r = client.post(
"/api/control/simulate-from-config", json={}, headers=H)
89 assert r.status_code == 500
90 assert "the solve fell over" in r.text
and "Traceback" not in r.text, r.text[:400]
test_the_stream_refuses_the_path_in_its_first_event(client, opened, tmp_path)
test_a_path_outside_configs_is_refused_unopened(client, opened, tmp_path, route)
test_a_failure_is_a_sentence_not_a_traceback(client, monkeypatch)
test_a_controller_config_outside_configs_is_refused(client, opened)
test_a_config_under_configs_is_read(client, opened)