fixtureqa 0.3.3__tar.gz → 0.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fixtureqa-0.3.3/fixtureqa.egg-info → fixtureqa-0.3.5}/PKG-INFO +1 -1
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/sessions.py +18 -12
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/fix_builder.py +2 -2
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_payload.py +14 -8
- {fixtureqa-0.3.3 → fixtureqa-0.3.5/fixtureqa.egg-info}/PKG-INFO +1 -1
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/pyproject.toml +1 -1
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_perf_payload.py +27 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_sessions.py +17 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/LICENSE +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/README.md +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/__init__.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/__main__.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/__init__.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/app.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/connection_manager.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/deps.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/__init__.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/admin.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/auth.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/branding.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/custom_tags.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/fix_spec.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/messages.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/perf.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/scenarios.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/setup.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/spec_overlay.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/templates.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/ws.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/schemas.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/config/__init__.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/__init__.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/atomic_io.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/auth.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/config_store.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/custom_tag_store.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/db_migrations.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/events.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/fix_application.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/fix_parser.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/fix_spec_parser.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/fix_tags.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/housekeeping.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/inbound.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/message_log.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/message_store.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/models.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_engine.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_models.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_stats.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_store.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_writer.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/scenario_runner.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/scenario_store.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/session.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/session_manager.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/spec_overlay_store.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/template_store.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/user_store.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/venue_responses.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/fix_specs/FIX42.xml +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/fix_specs/FIX44.xml +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/server.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/assets/index-BYDmHEr1.js +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/assets/index-D9vW5wFo.css +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/favicon.svg +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/index.html +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/ui/__init__.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixtureqa.egg-info/SOURCES.txt +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixtureqa.egg-info/dependency_links.txt +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixtureqa.egg-info/entry_points.txt +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixtureqa.egg-info/requires.txt +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixtureqa.egg-info/top_level.txt +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/setup.cfg +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_atomic_io.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_auth.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_connection_manager.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_db_migrations.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_fix_builder.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_health.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_inbound.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_inbound_validation.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_message_store.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_perf_api.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_perf_engine.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_perf_models.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_perf_rehydrate.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_scenarios.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_session_lifecycle.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_session_manager_concurrency.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_templates.py +0 -0
- {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_ws.py +0 -0
|
@@ -71,15 +71,20 @@ def _check_ownership(sm: SessionManager, session_id: str, user: User) -> Session
|
|
|
71
71
|
return cfg
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
def _owner_store(us: UserStore, user: User) -> Optional[UserStore]:
|
|
75
|
+
"""Only admin responses carry owner_username (the UI owner badge / Mine filter)."""
|
|
76
|
+
return us if user.is_admin else None
|
|
77
|
+
|
|
78
|
+
|
|
74
79
|
@router.get("/", response_model=list[SessionResponse])
|
|
75
80
|
def list_sessions(sm: SM, us: US, user: CurrentUser):
|
|
76
81
|
configs = sm.list_sessions_for_user(user.uid, is_admin=user.is_admin)
|
|
77
|
-
user_store = us
|
|
82
|
+
user_store = _owner_store(us, user)
|
|
78
83
|
return [_session_response(sm, cfg.session_id, user_store) for cfg in configs]
|
|
79
84
|
|
|
80
85
|
|
|
81
86
|
@router.post("/", response_model=SessionResponse, status_code=201)
|
|
82
|
-
async def create_session(body: SessionConfigRequest, sm: SM, user: CurrentUser):
|
|
87
|
+
async def create_session(body: SessionConfigRequest, sm: SM, us: US, user: CurrentUser):
|
|
83
88
|
try:
|
|
84
89
|
ct = ConnectionType(body.connection_type)
|
|
85
90
|
except ValueError:
|
|
@@ -127,11 +132,11 @@ async def create_session(body: SessionConfigRequest, sm: SM, user: CurrentUser):
|
|
|
127
132
|
except Exception as e:
|
|
128
133
|
raise HTTPException(status_code=500, detail=f"Session added but failed to start: {e}")
|
|
129
134
|
|
|
130
|
-
return _session_response(sm, session_id)
|
|
135
|
+
return _session_response(sm, session_id, _owner_store(us, user))
|
|
131
136
|
|
|
132
137
|
|
|
133
138
|
@router.post("/import", response_model=list[SessionResponse], status_code=201)
|
|
134
|
-
def import_sessions(body: list[SessionConfigRequest], sm: SM, user: CurrentUser):
|
|
139
|
+
def import_sessions(body: list[SessionConfigRequest], sm: SM, us: US, user: CurrentUser):
|
|
135
140
|
"""
|
|
136
141
|
Bulk-create sessions from an exported config file.
|
|
137
142
|
Each entry is processed independently; invalid entries are skipped.
|
|
@@ -169,20 +174,20 @@ def import_sessions(body: list[SessionConfigRequest], sm: SM, user: CurrentUser)
|
|
|
169
174
|
owner_uid=user.uid,
|
|
170
175
|
)
|
|
171
176
|
session_id = sm.add_session(cfg)
|
|
172
|
-
created.append(_session_response(sm, session_id))
|
|
177
|
+
created.append(_session_response(sm, session_id, _owner_store(us, user)))
|
|
173
178
|
except Exception:
|
|
174
179
|
pass # skip invalid / conflicting entries
|
|
175
180
|
return created
|
|
176
181
|
|
|
177
182
|
|
|
178
183
|
@router.get("/{session_id}", response_model=SessionResponse)
|
|
179
|
-
def get_session(session_id: str, sm: SM, user: CurrentUser):
|
|
184
|
+
def get_session(session_id: str, sm: SM, us: US, user: CurrentUser):
|
|
180
185
|
_check_ownership(sm, session_id, user)
|
|
181
|
-
return _session_response(sm, session_id)
|
|
186
|
+
return _session_response(sm, session_id, _owner_store(us, user))
|
|
182
187
|
|
|
183
188
|
|
|
184
189
|
@router.patch("/{session_id}", response_model=SessionResponse)
|
|
185
|
-
def update_session(session_id: str, body: UpdateSessionRequest, sm: SM, user: CurrentUser):
|
|
190
|
+
def update_session(session_id: str, body: UpdateSessionRequest, sm: SM, us: US, user: CurrentUser):
|
|
186
191
|
_check_ownership(sm, session_id, user)
|
|
187
192
|
try:
|
|
188
193
|
sm.update_session(session_id, body)
|
|
@@ -190,7 +195,7 @@ def update_session(session_id: str, body: UpdateSessionRequest, sm: SM, user: Cu
|
|
|
190
195
|
raise HTTPException(status_code=409, detail=str(e))
|
|
191
196
|
except (ValueError, KeyError) as e:
|
|
192
197
|
raise HTTPException(status_code=422, detail=str(e))
|
|
193
|
-
return _session_response(sm, session_id)
|
|
198
|
+
return _session_response(sm, session_id, _owner_store(us, user))
|
|
194
199
|
|
|
195
200
|
|
|
196
201
|
@router.delete("/{session_id}", status_code=204)
|
|
@@ -203,25 +208,26 @@ async def remove_session(session_id: str, sm: SM, user: CurrentUser):
|
|
|
203
208
|
|
|
204
209
|
|
|
205
210
|
@router.post("/{session_id}/start", response_model=SessionResponse)
|
|
206
|
-
async def start_session(session_id: str, sm: SM, user: CurrentUser):
|
|
211
|
+
async def start_session(session_id: str, sm: SM, us: US, user: CurrentUser):
|
|
207
212
|
_check_ownership(sm, session_id, user)
|
|
208
213
|
try:
|
|
209
214
|
await sm.start_session(session_id)
|
|
210
215
|
except RuntimeError as e:
|
|
211
216
|
raise HTTPException(status_code=409, detail=str(e))
|
|
212
|
-
return _session_response(sm, session_id)
|
|
217
|
+
return _session_response(sm, session_id, _owner_store(us, user))
|
|
213
218
|
|
|
214
219
|
|
|
215
220
|
@router.post("/{session_id}/stop", response_model=SessionResponse)
|
|
216
221
|
async def stop_session(
|
|
217
222
|
session_id: str,
|
|
218
223
|
sm: SM,
|
|
224
|
+
us: US,
|
|
219
225
|
user: CurrentUser,
|
|
220
226
|
force: bool = Query(default=False),
|
|
221
227
|
):
|
|
222
228
|
_check_ownership(sm, session_id, user)
|
|
223
229
|
await sm.stop_session(session_id, force=force)
|
|
224
|
-
return _session_response(sm, session_id)
|
|
230
|
+
return _session_response(sm, session_id, _owner_store(us, user))
|
|
225
231
|
|
|
226
232
|
|
|
227
233
|
@router.post("/{session_id}/send")
|
|
@@ -23,7 +23,7 @@ from fixcore.message import DataDictionary, Group, GroupDef, Message
|
|
|
23
23
|
SOH = "\x01"
|
|
24
24
|
|
|
25
25
|
# Tags the transport owns; never copied from the user's raw into the body.
|
|
26
|
-
|
|
26
|
+
CONTROL_TAGS = frozenset({8, 9, 10, 34, 49, 52, 56})
|
|
27
27
|
_MSG_TYPE = 35
|
|
28
28
|
_TRANSACT_TIME = 60
|
|
29
29
|
|
|
@@ -57,7 +57,7 @@ def build_message_from_raw(
|
|
|
57
57
|
if not msg_type:
|
|
58
58
|
raise ValueError("Missing MsgType (tag 35)")
|
|
59
59
|
|
|
60
|
-
body = [(t, v) for t, v in pairs if t != _MSG_TYPE and t not in
|
|
60
|
+
body = [(t, v) for t, v in pairs if t != _MSG_TYPE and t not in CONTROL_TAGS]
|
|
61
61
|
if transact_time is not None and not any(t == _TRANSACT_TIME for t, _ in body):
|
|
62
62
|
body.insert(0, (_TRANSACT_TIME, transact_time))
|
|
63
63
|
|
|
@@ -28,6 +28,7 @@ from typing import Optional
|
|
|
28
28
|
|
|
29
29
|
from fixcore.message.message import Message
|
|
30
30
|
|
|
31
|
+
from .fix_builder import CONTROL_TAGS
|
|
31
32
|
from .perf_models import RunConfig, ScenarioConfig
|
|
32
33
|
|
|
33
34
|
_TOKEN_RE = re.compile(r"\{([^}]+)\}")
|
|
@@ -94,10 +95,7 @@ class PayloadFactory:
|
|
|
94
95
|
tpl = self._ts.get_template(self._uid, tpl_id) if self._ts else None
|
|
95
96
|
if tpl is None:
|
|
96
97
|
raise PerfConfigError(f"template {tpl_id!r} not found for scenario {scn.name!r}")
|
|
97
|
-
|
|
98
|
-
fields = {int(k): str(v) for k, v in tpl.get("fields", {}).items()}
|
|
99
|
-
except (TypeError, ValueError):
|
|
100
|
-
raise PerfConfigError(f"template {tpl_id!r} has non-integer tags")
|
|
98
|
+
fields = self._parse_fields(tpl, tpl_id)
|
|
101
99
|
self._templates[tpl_id] = fields
|
|
102
100
|
for val in fields.values():
|
|
103
101
|
for tok in _tokens(val):
|
|
@@ -116,10 +114,7 @@ class PayloadFactory:
|
|
|
116
114
|
tpl = self._ts.get_template(self._uid, tpl_id) if self._ts else None
|
|
117
115
|
if tpl is None:
|
|
118
116
|
raise PerfConfigError(f"{label} template {tpl_id!r} not found")
|
|
119
|
-
|
|
120
|
-
fields = {int(k): str(v) for k, v in tpl.get("fields", {}).items()}
|
|
121
|
-
except (TypeError, ValueError):
|
|
122
|
-
raise PerfConfigError(f"{label} template {tpl_id!r} has non-integer tags")
|
|
117
|
+
fields = self._parse_fields(tpl, tpl_id, label)
|
|
123
118
|
vars_ = self.config.payload.variables
|
|
124
119
|
for val in fields.values():
|
|
125
120
|
for tok in _tokens(val):
|
|
@@ -130,6 +125,17 @@ class PayloadFactory:
|
|
|
130
125
|
raise PerfConfigError(f"unknown token {{{tok}}} in {label} template {tpl_id!r}")
|
|
131
126
|
return fields
|
|
132
127
|
|
|
128
|
+
def _parse_fields(self, tpl: dict, tpl_id: str, label: str = "") -> dict[int, str]:
|
|
129
|
+
try:
|
|
130
|
+
fields = {int(k): str(v) for k, v in tpl.get("fields", {}).items()}
|
|
131
|
+
except (TypeError, ValueError):
|
|
132
|
+
what = f"{label} template" if label else "template"
|
|
133
|
+
raise PerfConfigError(f"{what} {tpl_id!r} has non-integer tags")
|
|
134
|
+
# Templates saved from composed raw keep transport-owned header tags
|
|
135
|
+
# (8/9/10/34/49/52/56). The session stamps those on send, so copying
|
|
136
|
+
# them into the body would duplicate them on the wire.
|
|
137
|
+
return {t: v for t, v in fields.items() if t not in CONTROL_TAGS}
|
|
138
|
+
|
|
133
139
|
def _validate_token(self, tok: str, scn: ScenarioConfig, tpl_id: str,
|
|
134
140
|
seen: list[str], vars_: dict) -> None:
|
|
135
141
|
if tok in _SIMPLE or tok in vars_:
|
|
@@ -171,6 +171,33 @@ def test_exec_template_cannot_override_standard_tags():
|
|
|
171
171
|
assert msg.get_field_or(9000, "") == "x" # custom field preserved
|
|
172
172
|
|
|
173
173
|
|
|
174
|
+
def test_transport_tags_stripped_from_templates():
|
|
175
|
+
# Templates saved from a composed raw message keep header tags the session
|
|
176
|
+
# stamps on send (8/9/10/34/49/52/56); copying them into the body would
|
|
177
|
+
# duplicate them on the wire.
|
|
178
|
+
hdr = {"8": "FIX.4.2", "9": "123", "10": "045", "34": "7",
|
|
179
|
+
"49": "CLIENT", "52": "20260101-00:00:00", "56": "VENUE"}
|
|
180
|
+
ts = FakeTemplateStore({
|
|
181
|
+
"ord": _tpl("ord", {"35": "D", "9001": "x", **hdr}),
|
|
182
|
+
"exe": _tpl("exe", {"35": "8", "9002": "y", **hdr}),
|
|
183
|
+
"nos": _tpl("nos", {"35": "D", "9003": "z", **hdr}),
|
|
184
|
+
})
|
|
185
|
+
cfg = _config([ScenarioConfig(name="s", weight=1, sequence="parallel", messages=["nos"])],
|
|
186
|
+
order_template_id="ord", exec_template_id="exe")
|
|
187
|
+
f = PayloadFactory(cfg, ts, "u")
|
|
188
|
+
|
|
189
|
+
order_msg, _ = f.build_single_order()
|
|
190
|
+
scn_msg, _ = f.build_template_message("nos", f.new_context(), {})
|
|
191
|
+
exec_msg = f.build_exec_message({376: "CORR1", 38: "100"}, exec_type="2", ord_status="2",
|
|
192
|
+
last_qty=100, cum_qty=100, leaves_qty=0, px="10")
|
|
193
|
+
for msg in (order_msg, scn_msg, exec_msg):
|
|
194
|
+
for tag in (8, 9, 10, 34, 49, 52, 56):
|
|
195
|
+
assert not msg.has_field(tag), f"transport tag {tag} leaked into body"
|
|
196
|
+
assert order_msg.get_field_or(9001, "") == "x"
|
|
197
|
+
assert exec_msg.get_field_or(9002, "") == "y"
|
|
198
|
+
assert scn_msg.get_field_or(9003, "") == "z"
|
|
199
|
+
|
|
200
|
+
|
|
174
201
|
def test_ref_token_rejected_in_standalone_template():
|
|
175
202
|
ts = FakeTemplateStore({"ord": _tpl("ord", {"35": "D", "41": "{ref:x.clordid}"})})
|
|
176
203
|
cfg = _config([], order_template_id="ord")
|
|
@@ -53,6 +53,23 @@ def test_create_session(authed):
|
|
|
53
53
|
assert "session_id" in body
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
def test_mutating_responses_carry_owner_username_for_admin(authed):
|
|
57
|
+
# The admin "Mine" filter matches on owner_username client-side; if any
|
|
58
|
+
# session response omits it, the upserted session vanishes from the list
|
|
59
|
+
# until a full refetch. Every endpoint must enrich it like GET /sessions/.
|
|
60
|
+
client, _ = authed
|
|
61
|
+
created = client.post("/api/sessions/", json=_ini(9876)).json()
|
|
62
|
+
assert created["owner_username"] == "admin"
|
|
63
|
+
sid = created["session_id"]
|
|
64
|
+
assert client.get(f"/api/sessions/{sid}").json()["owner_username"] == "admin"
|
|
65
|
+
patched = client.patch(f"/api/sessions/{sid}", json={"display_name": "renamed"}).json()
|
|
66
|
+
assert patched["owner_username"] == "admin"
|
|
67
|
+
stopped = client.post(f"/api/sessions/{sid}/stop").json()
|
|
68
|
+
assert stopped["owner_username"] == "admin"
|
|
69
|
+
imported = client.post("/api/sessions/import", json=[_ini(9877, "A", "B")]).json()
|
|
70
|
+
assert imported[0]["owner_username"] == "admin"
|
|
71
|
+
|
|
72
|
+
|
|
56
73
|
def test_list_sessions(authed):
|
|
57
74
|
client, _ = authed
|
|
58
75
|
client.post("/api/sessions/", json=_ini(9001, "A", "B"))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|