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.
Files changed (94) hide show
  1. {fixtureqa-0.3.3/fixtureqa.egg-info → fixtureqa-0.3.5}/PKG-INFO +1 -1
  2. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/sessions.py +18 -12
  3. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/fix_builder.py +2 -2
  4. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_payload.py +14 -8
  5. {fixtureqa-0.3.3 → fixtureqa-0.3.5/fixtureqa.egg-info}/PKG-INFO +1 -1
  6. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/pyproject.toml +1 -1
  7. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_perf_payload.py +27 -0
  8. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_sessions.py +17 -0
  9. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/LICENSE +0 -0
  10. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/README.md +0 -0
  11. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/__init__.py +0 -0
  12. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/__main__.py +0 -0
  13. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/__init__.py +0 -0
  14. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/app.py +0 -0
  15. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/connection_manager.py +0 -0
  16. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/deps.py +0 -0
  17. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/__init__.py +0 -0
  18. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/admin.py +0 -0
  19. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/auth.py +0 -0
  20. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/branding.py +0 -0
  21. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/custom_tags.py +0 -0
  22. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/fix_spec.py +0 -0
  23. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/messages.py +0 -0
  24. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/perf.py +0 -0
  25. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/scenarios.py +0 -0
  26. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/setup.py +0 -0
  27. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/spec_overlay.py +0 -0
  28. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/templates.py +0 -0
  29. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/routers/ws.py +0 -0
  30. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/api/schemas.py +0 -0
  31. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/config/__init__.py +0 -0
  32. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/__init__.py +0 -0
  33. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/atomic_io.py +0 -0
  34. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/auth.py +0 -0
  35. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/config_store.py +0 -0
  36. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/custom_tag_store.py +0 -0
  37. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/db_migrations.py +0 -0
  38. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/events.py +0 -0
  39. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/fix_application.py +0 -0
  40. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/fix_parser.py +0 -0
  41. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/fix_spec_parser.py +0 -0
  42. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/fix_tags.py +0 -0
  43. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/housekeeping.py +0 -0
  44. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/inbound.py +0 -0
  45. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/message_log.py +0 -0
  46. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/message_store.py +0 -0
  47. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/models.py +0 -0
  48. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_engine.py +0 -0
  49. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_models.py +0 -0
  50. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_stats.py +0 -0
  51. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_store.py +0 -0
  52. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/perf_writer.py +0 -0
  53. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/scenario_runner.py +0 -0
  54. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/scenario_store.py +0 -0
  55. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/session.py +0 -0
  56. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/session_manager.py +0 -0
  57. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/spec_overlay_store.py +0 -0
  58. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/template_store.py +0 -0
  59. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/user_store.py +0 -0
  60. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/core/venue_responses.py +0 -0
  61. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/fix_specs/FIX42.xml +0 -0
  62. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/fix_specs/FIX44.xml +0 -0
  63. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/server.py +0 -0
  64. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
  65. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/assets/index-BYDmHEr1.js +0 -0
  66. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/assets/index-D9vW5wFo.css +0 -0
  67. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
  68. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/favicon.svg +0 -0
  69. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/static/index.html +0 -0
  70. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixture/ui/__init__.py +0 -0
  71. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixtureqa.egg-info/SOURCES.txt +0 -0
  72. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixtureqa.egg-info/dependency_links.txt +0 -0
  73. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixtureqa.egg-info/entry_points.txt +0 -0
  74. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixtureqa.egg-info/requires.txt +0 -0
  75. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/fixtureqa.egg-info/top_level.txt +0 -0
  76. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/setup.cfg +0 -0
  77. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_atomic_io.py +0 -0
  78. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_auth.py +0 -0
  79. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_connection_manager.py +0 -0
  80. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_db_migrations.py +0 -0
  81. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_fix_builder.py +0 -0
  82. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_health.py +0 -0
  83. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_inbound.py +0 -0
  84. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_inbound_validation.py +0 -0
  85. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_message_store.py +0 -0
  86. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_perf_api.py +0 -0
  87. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_perf_engine.py +0 -0
  88. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_perf_models.py +0 -0
  89. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_perf_rehydrate.py +0 -0
  90. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_scenarios.py +0 -0
  91. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_session_lifecycle.py +0 -0
  92. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_session_manager_concurrency.py +0 -0
  93. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_templates.py +0 -0
  94. {fixtureqa-0.3.3 → fixtureqa-0.3.5}/tests/test_ws.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixtureqa
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: FIXture — FIX Protocol Testing Tool
5
5
  Requires-Python: >=3.10
6
6
  License-File: LICENSE
@@ -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 if user.is_admin else None
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
- _CONTROL_TAGS = frozenset({8, 9, 10, 34, 49, 52, 56})
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 _CONTROL_TAGS]
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
- try:
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
- try:
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_:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixtureqa
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: FIXture — FIX Protocol Testing Tool
5
5
  Requires-Python: >=3.10
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fixtureqa"
7
- version = "0.3.3"
7
+ version = "0.3.5"
8
8
  description = "FIXture — FIX Protocol Testing Tool"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -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