1"""Per-user live-state isolation for the Engine Design backend.
3Two users connected at once must not share the live config, the optimizer job
4state, or the controller. Identity is the ``X-Auth-Email`` header (Caddy in prod,
5absent in dev -> ``local``). These tests drive the real dependency and endpoint
6coroutines directly -- no HTTP client needed -- so they exercise exactly what
7Caddy's forwarded header feeds into the app.
9See backend/session.py for the mechanism.
12from __future__
import annotations
19from fastapi
import HTTPException
20from starlette.requests
import Request
22from backend
import session
as session_mod
23from backend.session import SessionRegistry, get_session, JOB_SEMAPHORE, MAX_CONCURRENT_JOBS
30 """A minimal ASGI request carrying (or omitting) the auth header."""
31 headers = [(b
"x-auth-email", email.encode())]
if email
else []
44 return asyncio.run(coro)
51 a = get_session(
_request(
"alice@berkeley.edu"))
52 b = get_session(
_request(
"bob@berkeley.edu"))
54 assert a.user != b.user
58 a1 = get_session(
_request(
"carol@berkeley.edu"))
59 a2 = get_session(
_request(
"carol@berkeley.edu"))
64 """No Caddy in dev -> no header -> everyone is `local`, one shared session,
65 with the default config loaded (the old single-user behaviour)."""
69 assert s1.user ==
"local"
70 assert s1.app_state.has_config()
75 monkeypatch.setattr(session_mod,
"SESSION_TTL_SECONDS", 100)
76 first = reg.get(
"dave@berkeley.edu")
78 first.last_used = time.monotonic() - 1000
79 second = reg.get(
"dave@berkeley.edu")
80 assert second
is not first
84 assert MAX_CONCURRENT_JOBS == 3
85 assert JOB_SEMAPHORE._value == 3
92 a = get_session(
_request(
"erin@berkeley.edu"))
93 b = get_session(
_request(
"frank@berkeley.edu"))
95 base_b =
_run(config_router.get_config(session=b))[
"config"][
"design_requirements"][
"target_thrust"]
96 target = base_b + 137.0
98 _run(config_router.update_config(
99 {
"design_requirements": {
"target_thrust": target}}, session=a
102 a_thrust =
_run(config_router.get_config(session=a))[
"config"][
"design_requirements"][
"target_thrust"]
103 b_thrust =
_run(config_router.get_config(session=b))[
"config"][
"design_requirements"][
"target_thrust"]
105 assert a_thrust == pytest.approx(target)
106 assert b_thrust == pytest.approx(base_b)
107 assert a_thrust != pytest.approx(b_thrust)
114 a = get_session(
_request(
"gina@berkeley.edu"))
115 b = get_session(
_request(
"hank@berkeley.edu"))
118 a.optimizer.optimization_status[
"running"] =
True
119 a.optimizer.optimization_status[
"results"] = {
"objective": 1.23}
121 assert _run(optimizer_router.get_layer1_status(session=a))[
"running"]
is True
122 assert _run(optimizer_router.get_layer1_status(session=b))[
"running"]
is False
125 assert _run(optimizer_router.get_layer1_results(session=a))[
"results"] == {
"objective": 1.23}
126 with pytest.raises(HTTPException)
as exc:
127 _run(optimizer_router.get_layer1_results(session=b))
128 assert exc.value.status_code == 404
132 a = get_session(
_request(
"iris@berkeley.edu"))
133 b = get_session(
_request(
"jack@berkeley.edu"))
135 a.optimizer.optimization_status[
"running"] =
True
136 b.optimizer.optimization_status[
"running"] =
True
137 a.optimizer.stop_event = threading.Event()
138 b.optimizer.stop_event = threading.Event()
140 _run(optimizer_router.stop_layer1(session=a))
143 assert a.optimizer.stop_event.is_set()
144 assert b.optimizer.stop_event
is not a.optimizer.stop_event
145 assert not b.optimizer.stop_event.is_set()
146 assert b.optimizer.optimization_status[
"running"]
is True
153 a = get_session(
_request(
"kim@berkeley.edu"))
154 b = get_session(
_request(
"liam@berkeley.edu"))
156 _run(control_router.init_controller(control_router.ControllerInitRequest(), session=a))
158 assert _run(control_router.get_controller_status(session=a))[
"initialized"]
is True
160 assert _run(control_router.get_controller_status(session=b))[
"initialized"]
is False
161 assert b.controller.controller
is None
Request _request(str|None email)
test_same_user_reuses_one_session()
test_stop_only_affects_the_callers_run()
test_controller_is_per_user()
test_optimizer_status_and_results_are_per_user()
test_dev_fallback_is_a_single_local_session()
test_ttl_eviction_recreates_a_fresh_session(monkeypatch)
test_one_users_config_edit_does_not_touch_another()
test_distinct_users_get_distinct_sessions()