comodor 0.8.1__tar.gz → 0.8.2__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 (147) hide show
  1. {comodor-0.8.1 → comodor-0.8.2}/PKG-INFO +1 -1
  2. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/_version.py +2 -2
  3. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/app.py +6 -1
  4. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/web/server.py +32 -4
  5. {comodor-0.8.1 → comodor-0.8.2}/tests/test_run_loop.py +7 -1
  6. {comodor-0.8.1 → comodor-0.8.2}/tests/test_web.py +86 -0
  7. {comodor-0.8.1 → comodor-0.8.2}/.gitignore +0 -0
  8. {comodor-0.8.1 → comodor-0.8.2}/LICENSE +0 -0
  9. {comodor-0.8.1 → comodor-0.8.2}/README.md +0 -0
  10. {comodor-0.8.1 → comodor-0.8.2}/pyproject.toml +0 -0
  11. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/__init__.py +0 -0
  12. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/__main__.py +0 -0
  13. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/agent/__init__.py +0 -0
  14. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/agent/context.py +0 -0
  15. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/agent/loop.py +0 -0
  16. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/agent/prompts.py +0 -0
  17. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/agent/spawn.py +0 -0
  18. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/agent/tokens.py +0 -0
  19. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/browser/__init__.py +0 -0
  20. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/browser/cdp.py +0 -0
  21. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/browser/launch.py +0 -0
  22. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/browser/page.py +0 -0
  23. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/catalogue.py +0 -0
  24. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/cli.py +0 -0
  25. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/config.py +0 -0
  26. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/doctor.py +0 -0
  27. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/events.py +0 -0
  28. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/__init__.py +0 -0
  29. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/associations.py +0 -0
  30. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/bm25.py +0 -0
  31. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/hotindex.py +0 -0
  32. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/memory.py +0 -0
  33. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/progress.py +0 -0
  34. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/reflect.py +0 -0
  35. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/rules.py +0 -0
  36. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/signals.py +0 -0
  37. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/store.py +0 -0
  38. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/learning/writer.py +0 -0
  39. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/mcp/__init__.py +0 -0
  40. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/mcp/catalogue.py +0 -0
  41. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/mcp/commands.py +0 -0
  42. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/mcp/http.py +0 -0
  43. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/mcp/manager.py +0 -0
  44. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/mcp/protocol.py +0 -0
  45. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/net/__init__.py +0 -0
  46. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/net/http.py +0 -0
  47. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/net/sse.py +0 -0
  48. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/paths.py +0 -0
  49. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/providers/__init__.py +0 -0
  50. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/providers/anthropic.py +0 -0
  51. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/providers/base.py +0 -0
  52. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/providers/caching.py +0 -0
  53. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/providers/fake.py +0 -0
  54. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/providers/gateway.py +0 -0
  55. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/providers/openai_compat.py +0 -0
  56. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/providers/registry.py +0 -0
  57. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/safety/__init__.py +0 -0
  58. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/safety/checkpoints.py +0 -0
  59. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/safety/permissions.py +0 -0
  60. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/safety/redact.py +0 -0
  61. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/session/__init__.py +0 -0
  62. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/session/search.py +0 -0
  63. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/session/store.py +0 -0
  64. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/setup.py +0 -0
  65. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/skills/__init__.py +0 -0
  66. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/skills/catalogue.py +0 -0
  67. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/skills/commands.py +0 -0
  68. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/skills/examples.py +0 -0
  69. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/skills/loader.py +0 -0
  70. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/skills/propose.py +0 -0
  71. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/skills/registry.py +0 -0
  72. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/__init__.py +0 -0
  73. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/base.py +0 -0
  74. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/browse.py +0 -0
  75. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/browser.py +0 -0
  76. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/delegate.py +0 -0
  77. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/fs.py +0 -0
  78. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/history.py +0 -0
  79. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/mcp.py +0 -0
  80. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/overflow.py +0 -0
  81. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/registry.py +0 -0
  82. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/search.py +0 -0
  83. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/shell.py +0 -0
  84. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/skills.py +0 -0
  85. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/todo.py +0 -0
  86. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/tools/web.py +0 -0
  87. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/__init__.py +0 -0
  88. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/bidi.py +0 -0
  89. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/chooser.py +0 -0
  90. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/console.py +0 -0
  91. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/input/__init__.py +0 -0
  92. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/input/keys.py +0 -0
  93. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/input/reader.py +0 -0
  94. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/layout.py +0 -0
  95. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/markdown.py +0 -0
  96. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/screen.py +0 -0
  97. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/theme.py +0 -0
  98. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/widgets/__init__.py +0 -0
  99. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/widgets/buttons.py +0 -0
  100. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/widgets/chat.py +0 -0
  101. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/widgets/history.py +0 -0
  102. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/widgets/overlay.py +0 -0
  103. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/widgets/panel.py +0 -0
  104. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/widgets/progress.py +0 -0
  105. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/widgets/prompt.py +0 -0
  106. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/widgets/statusbar.py +0 -0
  107. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/ui/widgets/toast.py +0 -0
  108. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/uninstall.py +0 -0
  109. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/update.py +0 -0
  110. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/web/__init__.py +0 -0
  111. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/web/commands.py +0 -0
  112. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/web/index.html +0 -0
  113. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/web/session.py +0 -0
  114. {comodor-0.8.1 → comodor-0.8.2}/src/comodor/workspace.py +0 -0
  115. {comodor-0.8.1 → comodor-0.8.2}/tests/conftest.py +0 -0
  116. {comodor-0.8.1 → comodor-0.8.2}/tests/support/fake_mcp_server.py +0 -0
  117. {comodor-0.8.1 → comodor-0.8.2}/tests/test_agent_loop.py +0 -0
  118. {comodor-0.8.1 → comodor-0.8.2}/tests/test_app.py +0 -0
  119. {comodor-0.8.1 → comodor-0.8.2}/tests/test_associations.py +0 -0
  120. {comodor-0.8.1 → comodor-0.8.2}/tests/test_bidi.py +0 -0
  121. {comodor-0.8.1 → comodor-0.8.2}/tests/test_browser.py +0 -0
  122. {comodor-0.8.1 → comodor-0.8.2}/tests/test_caching.py +0 -0
  123. {comodor-0.8.1 → comodor-0.8.2}/tests/test_chooser.py +0 -0
  124. {comodor-0.8.1 → comodor-0.8.2}/tests/test_delegate.py +0 -0
  125. {comodor-0.8.1 → comodor-0.8.2}/tests/test_doctor.py +0 -0
  126. {comodor-0.8.1 → comodor-0.8.2}/tests/test_history.py +0 -0
  127. {comodor-0.8.1 → comodor-0.8.2}/tests/test_input.py +0 -0
  128. {comodor-0.8.1 → comodor-0.8.2}/tests/test_layout.py +0 -0
  129. {comodor-0.8.1 → comodor-0.8.2}/tests/test_learning.py +0 -0
  130. {comodor-0.8.1 → comodor-0.8.2}/tests/test_markdown.py +0 -0
  131. {comodor-0.8.1 → comodor-0.8.2}/tests/test_mcp.py +0 -0
  132. {comodor-0.8.1 → comodor-0.8.2}/tests/test_mcp_http.py +0 -0
  133. {comodor-0.8.1 → comodor-0.8.2}/tests/test_overflow.py +0 -0
  134. {comodor-0.8.1 → comodor-0.8.2}/tests/test_performance.py +0 -0
  135. {comodor-0.8.1 → comodor-0.8.2}/tests/test_prefix_stability.py +0 -0
  136. {comodor-0.8.1 → comodor-0.8.2}/tests/test_progress.py +0 -0
  137. {comodor-0.8.1 → comodor-0.8.2}/tests/test_propose.py +0 -0
  138. {comodor-0.8.1 → comodor-0.8.2}/tests/test_providers.py +0 -0
  139. {comodor-0.8.1 → comodor-0.8.2}/tests/test_real_browser.py +0 -0
  140. {comodor-0.8.1 → comodor-0.8.2}/tests/test_reflex.py +0 -0
  141. {comodor-0.8.1 → comodor-0.8.2}/tests/test_setup.py +0 -0
  142. {comodor-0.8.1 → comodor-0.8.2}/tests/test_skill_budget.py +0 -0
  143. {comodor-0.8.1 → comodor-0.8.2}/tests/test_skill_catalogue.py +0 -0
  144. {comodor-0.8.1 → comodor-0.8.2}/tests/test_skills.py +0 -0
  145. {comodor-0.8.1 → comodor-0.8.2}/tests/test_tools.py +0 -0
  146. {comodor-0.8.1 → comodor-0.8.2}/tests/test_uninstall.py +0 -0
  147. {comodor-0.8.1 → comodor-0.8.2}/tests/test_update.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: comodor
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary: Comodor — a self-improving terminal coding agent with a Rich TUI
5
5
  Project-URL: Homepage, https://comodor.ai
6
6
  Project-URL: Repository, https://github.com/ifekri/Comodor
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.8.1'
22
- __version_tuple__ = version_tuple = (0, 8, 1)
21
+ __version__ = version = '0.8.2'
22
+ __version_tuple__ = version_tuple = (0, 8, 2)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -46,6 +46,11 @@ from .widgets.history import SessionRef
46
46
  from .widgets.overlay import info_overlay, permission_overlay, select_overlay
47
47
  from .widgets.statusbar import StatusModel
48
48
 
49
+ #: How long quitting waits for a turn in flight. Quitting mid-task should give
50
+ #: the agent a moment to stop rather than abandoning it, so shutting down can
51
+ #: legitimately take this long — which is what anything timing the exit has to
52
+ #: allow for.
53
+ SHUTDOWN_JOIN_SECONDS = 2.0
49
54
  IDLE_SLEEP = 0.02
50
55
  SPINNER_INTERVAL = 0.1
51
56
  # How long the prompt must sit still before recall is warmed for the draft.
@@ -199,7 +204,7 @@ class App:
199
204
  if self.mcp is not None:
200
205
  self.mcp.close()
201
206
  if self.worker and self.worker.is_alive():
202
- self.worker.join(timeout=2.0)
207
+ self.worker.join(timeout=SHUTDOWN_JOIN_SECONDS)
203
208
  # Flush whatever the agent produced after the last event we pumped —
204
209
  # quitting mid-turn must not lose the transcript.
205
210
  self._persist_new_messages()
@@ -140,20 +140,43 @@ def _handler_for(server: Server) -> type[BaseHTTPRequestHandler]:
140
140
  return ""
141
141
 
142
142
  def _body(self) -> dict[str, Any]:
143
+ """Read the body, whatever is going to be done with it.
144
+
145
+ Always read, and read all of it. Bytes left in the socket are read
146
+ by the *next* request on the connection as its request line, and a
147
+ socket closed with unread data sends a reset instead of a close —
148
+ which the client sees as a connection aborted rather than as the
149
+ 401 that was actually sent.
150
+ """
143
151
  try:
144
152
  length = int(self.headers.get("Content-Length") or 0)
145
153
  except ValueError:
154
+ length = 0
155
+ if length <= 0:
146
156
  return {}
147
- if length <= 0 or length > MAX_BODY:
157
+
158
+ raw = b""
159
+ remaining = length
160
+ while remaining > 0:
161
+ chunk = self.rfile.read(min(remaining, 65536))
162
+ if not chunk:
163
+ break
164
+ remaining -= len(chunk)
165
+ # Past the cap it is drained and dropped, not left unread.
166
+ if len(raw) < MAX_BODY:
167
+ raw += chunk
168
+ if length > MAX_BODY:
148
169
  return {}
149
170
  try:
150
- return json.loads(self.rfile.read(length) or b"{}")
151
- except (ValueError, OSError):
171
+ return json.loads(raw or b"{}")
172
+ except ValueError:
152
173
  return {}
153
174
 
154
175
  # -- reading ------------------------------------------------------- #
155
176
 
156
177
  def do_GET(self) -> None:
178
+ if self.headers.get("Content-Length"):
179
+ self._body() # rare, but the same rule applies
157
180
  parts = urlparse(self.path)
158
181
  query = parse_qs(parts.query)
159
182
  route = parts.path
@@ -202,6 +225,10 @@ def _handler_for(server: Server) -> type[BaseHTTPRequestHandler]:
202
225
  # -- doing --------------------------------------------------------- #
203
226
 
204
227
  def do_POST(self) -> None:
228
+ # Before any verdict: an unread body breaks the connection it
229
+ # arrived on, and the caller sees a reset instead of the refusal.
230
+ body = self._body()
231
+
205
232
  if not server.authorised(self._token()):
206
233
  self._json(401, {"error": "unauthorised"})
207
234
  return
@@ -213,7 +240,6 @@ def _handler_for(server: Server) -> type[BaseHTTPRequestHandler]:
213
240
  return
214
241
 
215
242
  route = urlparse(self.path).path
216
- body = self._body()
217
243
 
218
244
  if route == "/api/send":
219
245
  started = server.session.send(str(body.get("text") or ""))
@@ -243,6 +269,8 @@ def _handler_for(server: Server) -> type[BaseHTTPRequestHandler]:
243
269
  def do_OPTIONS(self) -> None:
244
270
  # No CORS for anyone. A preflight that gets no permissions means a
245
271
  # cross-origin fetch cannot send the header the POST routes want.
272
+ if self.headers.get("Content-Length"):
273
+ self._body()
246
274
  self._send(405)
247
275
 
248
276
  return Handler
@@ -83,7 +83,13 @@ def run_with(app, events, monkeypatch, budget: float = 3.0):
83
83
  app.run()
84
84
  finally:
85
85
  watchdog.cancel()
86
- assert time.monotonic() < deadline + 1.0, "the loop did not exit"
86
+ # Shutting down is allowed to wait for a turn still in flight, so the
87
+ # exit cannot be quicker than that however fast the loop itself is. The
88
+ # tolerance is that budget plus slack, taken from the source rather than
89
+ # guessed, or the two drift apart and the test fails on a loaded machine
90
+ # for a reason that is not a bug.
91
+ allowed = app_module.SHUTDOWN_JOIN_SECONDS + 1.5
92
+ assert time.monotonic() < deadline + allowed, "the loop did not exit"
87
93
  return terminal, live
88
94
 
89
95
 
@@ -373,3 +373,89 @@ def test_an_events_own_fields_cannot_overwrite_its_kind(served):
373
373
 
374
374
  assert frames and frames[0]["kind"] == "request"
375
375
  assert frames[0]["about"] == "permission"
376
+
377
+
378
+ # --------------------------------------------------------------------------- #
379
+ # a refusal must still leave the connection usable
380
+ # --------------------------------------------------------------------------- #
381
+
382
+
383
+ def test_a_refused_post_leaves_the_connection_clean(served):
384
+ """The bug this exists for. A verdict reached before the body is read
385
+ leaves those bytes in the socket; the next request on the connection reads
386
+ them as its own request line, and a socket closed with unread data sends a
387
+ reset — which the caller sees as an aborted connection rather than the 401
388
+ that was actually sent."""
389
+ import http.client
390
+
391
+ connection = http.client.HTTPConnection("127.0.0.1", served.port, timeout=15)
392
+ try:
393
+ payload = json.dumps({"text": "x" * 50_000})
394
+ # Rejected for the missing guard header, with a body already in flight.
395
+ connection.request("POST", "/api/send", body=payload,
396
+ headers={"Content-Type": "application/json",
397
+ "Cookie": f"{COOKIE}={served.token}"})
398
+ first = connection.getresponse()
399
+ first.read()
400
+ assert first.status == 403
401
+
402
+ # The same connection, reused. If the body was not drained this reads
403
+ # the leftover as a request line and fails.
404
+ connection.request("GET", "/api/state",
405
+ headers={"Cookie": f"{COOKIE}={served.token}"})
406
+ second = connection.getresponse()
407
+ state = json.loads(second.read())
408
+
409
+ assert second.status == 200
410
+ assert "mode" in state
411
+ finally:
412
+ connection.close()
413
+
414
+
415
+ def test_an_unauthorised_post_leaves_the_connection_clean(served):
416
+ import http.client
417
+
418
+ connection = http.client.HTTPConnection("127.0.0.1", served.port, timeout=15)
419
+ try:
420
+ connection.request("POST", "/api/send",
421
+ body=json.dumps({"text": "y" * 20_000}),
422
+ headers={"Content-Type": "application/json",
423
+ "X-Comodor": "1"})
424
+ first = connection.getresponse()
425
+ first.read()
426
+ assert first.status == 401
427
+
428
+ connection.request("GET", "/api/state",
429
+ headers={"Cookie": f"{COOKIE}={served.token}"})
430
+ second = connection.getresponse()
431
+ second.read()
432
+
433
+ assert second.status == 200
434
+ finally:
435
+ connection.close()
436
+
437
+
438
+ def test_a_body_over_the_cap_is_drained_rather_than_left_in_the_socket(served):
439
+ """Refusing to read is what breaks the connection, so an oversized body is
440
+ read and dropped instead."""
441
+ import http.client
442
+
443
+ from comodor.web.server import MAX_BODY
444
+
445
+ connection = http.client.HTTPConnection("127.0.0.1", served.port, timeout=30)
446
+ try:
447
+ connection.request("POST", "/api/send", body="z" * (MAX_BODY + 5_000),
448
+ headers={"Content-Type": "application/json",
449
+ "Cookie": f"{COOKIE}={served.token}",
450
+ "X-Comodor": "1"})
451
+ first = connection.getresponse()
452
+ first.read()
453
+
454
+ connection.request("GET", "/api/state",
455
+ headers={"Cookie": f"{COOKIE}={served.token}"})
456
+ second = connection.getresponse()
457
+ second.read()
458
+
459
+ assert second.status == 200
460
+ finally:
461
+ connection.close()
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