collab-runtime 0.2.9__py3-none-any.whl

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 (82) hide show
  1. collab/__init__.py +77 -0
  2. collab/__main__.py +11 -0
  3. collab_runtime-0.2.9.dist-info/METADATA +218 -0
  4. collab_runtime-0.2.9.dist-info/RECORD +82 -0
  5. collab_runtime-0.2.9.dist-info/WHEEL +5 -0
  6. collab_runtime-0.2.9.dist-info/entry_points.txt +3 -0
  7. collab_runtime-0.2.9.dist-info/licenses/LICENSE +21 -0
  8. collab_runtime-0.2.9.dist-info/top_level.txt +10 -0
  9. scripts/cleanup.py +395 -0
  10. scripts/collab_git_hook.py +190 -0
  11. scripts/format_code.py +594 -0
  12. scripts/generate_tests.py +560 -0
  13. scripts/validate_code.py +1397 -0
  14. src/__init__.py +4 -0
  15. src/dashboard/index.html +1131 -0
  16. src/live_locks_watcher.py +1982 -0
  17. src/lock_client.py +4268 -0
  18. src/logging_config.py +259 -0
  19. src/main.py +436 -0
  20. tests/backend/__init__.py +0 -0
  21. tests/backend/functional/__init__.py +0 -0
  22. tests/backend/functional/test_package_imports.py +43 -0
  23. tests/backend/integration/__init__.py +0 -0
  24. tests/backend/integration/test_cli_contract_parity.py +220 -0
  25. tests/backend/performance/__init__.py +0 -0
  26. tests/backend/reliability/__init__.py +0 -0
  27. tests/backend/security/__init__.py +0 -0
  28. tests/backend/unit/live_locks_watcher/__init__.py +5 -0
  29. tests/backend/unit/live_locks_watcher/_helpers.py +123 -0
  30. tests/backend/unit/live_locks_watcher/conftest.py +18 -0
  31. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_dashboard.py +188 -0
  32. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_developer.py +56 -0
  33. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_graceful_shutdown.py +459 -0
  34. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_main.py +1925 -0
  35. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_module.py +187 -0
  36. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_multi_session.py +320 -0
  37. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_notify.py +67 -0
  38. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_parsing.py +155 -0
  39. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_process_helpers.py +684 -0
  40. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_processing.py +173 -0
  41. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_prompt_abort.py +71 -0
  42. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_reconcile.py +516 -0
  43. tests/backend/unit/live_locks_watcher/test_live_locks_watcher_scan.py +296 -0
  44. tests/backend/unit/lock_client/__init__.py +1 -0
  45. tests/backend/unit/lock_client/_helpers.py +132 -0
  46. tests/backend/unit/lock_client/test_lock_client_acquire.py +214 -0
  47. tests/backend/unit/lock_client/test_lock_client_active.py +104 -0
  48. tests/backend/unit/lock_client/test_lock_client_api.py +63 -0
  49. tests/backend/unit/lock_client/test_lock_client_cli.py +682 -0
  50. tests/backend/unit/lock_client/test_lock_client_daemon.py +3730 -0
  51. tests/backend/unit/lock_client/test_lock_client_dashboard.py +438 -0
  52. tests/backend/unit/lock_client/test_lock_client_discover.py +241 -0
  53. tests/backend/unit/lock_client/test_lock_client_force_release.py +354 -0
  54. tests/backend/unit/lock_client/test_lock_client_helper_branches.py +1890 -0
  55. tests/backend/unit/lock_client/test_lock_client_history.py +301 -0
  56. tests/backend/unit/lock_client/test_lock_client_isolation.py +316 -0
  57. tests/backend/unit/lock_client/test_lock_client_pid.py +75 -0
  58. tests/backend/unit/lock_client/test_lock_client_reconcile.py +464 -0
  59. tests/backend/unit/lock_client/test_lock_client_release.py +77 -0
  60. tests/backend/unit/lock_client/test_lock_client_shutdown.py +1110 -0
  61. tests/backend/unit/lock_client/test_lock_client_utils.py +474 -0
  62. tests/backend/unit/lock_client/test_lock_client_watch.py +866 -0
  63. tests/backend/unit/scripts/__init__.py +1 -0
  64. tests/backend/unit/scripts/_helpers.py +42 -0
  65. tests/backend/unit/scripts/test_cleanup.py +285 -0
  66. tests/backend/unit/scripts/test_collab_git_hook.py +280 -0
  67. tests/backend/unit/scripts/test_collab_git_hook_ported.py +50 -0
  68. tests/backend/unit/scripts/test_format_code.py +368 -0
  69. tests/backend/unit/scripts/test_format_code_ported.py +177 -0
  70. tests/backend/unit/scripts/test_generate_tests.py +305 -0
  71. tests/backend/unit/scripts/test_hook_templates.py +357 -0
  72. tests/backend/unit/scripts/test_setup_hook_overlay.py +95 -0
  73. tests/backend/unit/scripts/test_validate_code.py +867 -0
  74. tests/backend/unit/scripts/test_validate_code_ported.py +237 -0
  75. tests/backend/unit/test_entrypoints_main_run.py +83 -0
  76. tests/backend/unit/test_logging_config.py +529 -0
  77. tests/backend/unit/test_main_watch_pid_file.py +278 -0
  78. tests/conftest.py +167 -0
  79. tests/frontend/__init__.py +0 -0
  80. tests/frontend/jest/__init__.py +0 -0
  81. tests/frontend/playwright/__init__.py +0 -0
  82. tests/packaging/test_smoke_install.py +76 -0
@@ -0,0 +1,354 @@
1
+ """Force-release tests for LockClient (admin & non-admin flows)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._helpers import (
6
+ FakeClient,
7
+ FakeResponse,
8
+ load_lock_client_module,
9
+ make_create_client,
10
+ )
11
+
12
+ mod = load_lock_client_module()
13
+
14
+
15
+ def test_force_release(monkeypatch):
16
+ """Test force-releasing a lock (admin operation)."""
17
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
18
+ monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service_key")
19
+
20
+ response = FakeResponse(status=200, data=[{"file_path": "src/app.py"}])
21
+ monkeypatch.setattr(mod, "_get_create_client", lambda: make_create_client(response))
22
+
23
+ lc = mod.LockClient(developer_id="admin")
24
+ ok, msg = lc.force_release("src/app.py")
25
+ assert isinstance(ok, bool)
26
+ assert isinstance(msg, str)
27
+
28
+
29
+ def test_force_release_api_exception(monkeypatch):
30
+ """Test force_release when API call raises an exception."""
31
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
32
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
33
+
34
+ class ExplodingClient(FakeClient):
35
+ def execute(self):
36
+ raise RuntimeError("Network timeout error")
37
+
38
+ monkeypatch.setattr(
39
+ mod,
40
+ "_get_create_client",
41
+ lambda: (lambda url, key: ExplodingClient(FakeResponse())),
42
+ )
43
+ monkeypatch.setattr(mod.time, "sleep", lambda x: None)
44
+
45
+ lc = mod.LockClient(developer_id="admin")
46
+ ok, msg = lc.force_release("src/app.py")
47
+ assert ok is False
48
+ assert "API Error" in msg
49
+
50
+
51
+ def test_force_release_with_error(monkeypatch):
52
+ """Test force_release when response has error."""
53
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
54
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
55
+
56
+ response = FakeResponse(status=500, data=None, error="Server error")
57
+ monkeypatch.setattr(mod, "_get_create_client", lambda: make_create_client(response))
58
+
59
+ lc = mod.LockClient(developer_id="admin")
60
+ ok, msg = lc.force_release("src/app.py")
61
+ assert ok is False
62
+ assert "API Error" in msg
63
+
64
+
65
+ def test_force_release_no_lock(monkeypatch):
66
+ """Test force_release when no lock exists to remove."""
67
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
68
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
69
+
70
+ # Need data to be truly None (not []) to hit the "No lock removed" path
71
+ class NullDataResponse:
72
+ status = 200
73
+ data = None
74
+ error = None
75
+
76
+ monkeypatch.setattr(
77
+ mod, "_get_create_client", lambda: make_create_client(NullDataResponse())
78
+ )
79
+
80
+ lc = mod.LockClient(developer_id="admin")
81
+ ok, msg = lc.force_release("src/app.py")
82
+ assert ok is False
83
+ assert "No lock removed" in msg
84
+
85
+
86
+ def test_force_release_nonadmin_own_lock(monkeypatch):
87
+ """Non-admin user can force-release their own lock."""
88
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
89
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
90
+ monkeypatch.delenv("SUPABASE_SERVICE_ROLE_KEY", raising=False)
91
+ monkeypatch.setattr(mod, "SUPABASE_SERVICE_ROLE_KEY", None)
92
+
93
+ status_resp = FakeResponse(
94
+ status=200,
95
+ data=[
96
+ {
97
+ "file_path": "src/app.py",
98
+ "developer_id": "alice",
99
+ "expires_at": "2099-01-01T00:00:00Z",
100
+ }
101
+ ],
102
+ )
103
+ delete_resp = FakeResponse(status=200, data=[{"file_path": "src/app.py"}])
104
+
105
+ call_log = []
106
+
107
+ class TrackingClient(FakeClient):
108
+ """Tracks method calls to verify developer_id filter is applied."""
109
+
110
+ def __init__(self, resp):
111
+ super().__init__(resp)
112
+ self._current_resp = resp
113
+
114
+ def select(self, *args, **kwargs):
115
+ self._current_resp = status_resp
116
+ return self
117
+
118
+ def delete(self, *args, **kwargs):
119
+ self._current_resp = delete_resp
120
+ call_log.append("delete")
121
+ return self
122
+
123
+ def eq(self, col, val):
124
+ call_log.append(("eq", col, val))
125
+ return self
126
+
127
+ def execute(self):
128
+ return self._current_resp
129
+
130
+ monkeypatch.setattr(
131
+ mod,
132
+ "_get_create_client",
133
+ lambda: (lambda url, key: TrackingClient(status_resp)),
134
+ )
135
+
136
+ lc = mod.LockClient(developer_id="alice")
137
+ ok, msg = lc.force_release("src/app.py")
138
+ assert ok is True
139
+ eq_calls = [c for c in call_log if isinstance(c, tuple) and c[0] == "eq"]
140
+ dev_id_filters = [c for c in eq_calls if c[1] == "developer_id"]
141
+ assert len(dev_id_filters) > 0, "Non-admin should filter by developer_id"
142
+ assert dev_id_filters[-1][2] == "alice"
143
+
144
+
145
+ def test_force_release_nonadmin_other_dev_lock_denied(monkeypatch):
146
+ """Non-admin user cannot force-release another developer's lock."""
147
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
148
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
149
+ monkeypatch.delenv("SUPABASE_SERVICE_ROLE_KEY", raising=False)
150
+ monkeypatch.setattr(mod, "SUPABASE_SERVICE_ROLE_KEY", None)
151
+
152
+ status_resp = FakeResponse(
153
+ status=200,
154
+ data=[
155
+ {
156
+ "file_path": "src/app.py",
157
+ "developer_id": "bob",
158
+ "expires_at": "2099-01-01T00:00:00Z",
159
+ }
160
+ ],
161
+ )
162
+ monkeypatch.setattr(
163
+ mod, "_get_create_client", lambda: make_create_client(status_resp)
164
+ )
165
+
166
+ lc = mod.LockClient(developer_id="alice")
167
+ ok, msg = lc.force_release("src/app.py")
168
+ assert ok is False
169
+ assert "Permission denied" in msg
170
+ assert "@bob" in msg
171
+
172
+
173
+ def test_force_release_admin_other_dev_lock_succeeds(monkeypatch):
174
+ """Admin user can force-release any developer's lock."""
175
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
176
+ monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service_key")
177
+ monkeypatch.setattr(mod, "SUPABASE_SERVICE_ROLE_KEY", "service_key")
178
+
179
+ delete_resp = FakeResponse(status=200, data=[{"file_path": "src/app.py"}])
180
+
181
+ call_log = []
182
+
183
+ class TrackingClient(FakeClient):
184
+ def delete(self, *args, **kwargs):
185
+ call_log.append("delete")
186
+ return self
187
+
188
+ def eq(self, col, val):
189
+ call_log.append(("eq", col, val))
190
+ return self
191
+
192
+ monkeypatch.setattr(
193
+ mod,
194
+ "_get_create_client",
195
+ lambda: (lambda url, key: TrackingClient(delete_resp)),
196
+ )
197
+
198
+ lc = mod.LockClient(developer_id="admin_user")
199
+ ok, msg = lc.force_release("src/app.py")
200
+ assert ok is True
201
+
202
+
203
+ # ---------------------------------------------------------------------------
204
+ # force_release_all tests
205
+ # ---------------------------------------------------------------------------
206
+
207
+
208
+ def test_force_release_all_non_admin_returns_zero(monkeypatch):
209
+ """force_release_all returns 0 immediately when user is not admin."""
210
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
211
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
212
+ monkeypatch.delenv("SUPABASE_SERVICE_ROLE_KEY", raising=False)
213
+ monkeypatch.setattr(mod, "SUPABASE_SERVICE_ROLE_KEY", None)
214
+
215
+ lc = mod.LockClient(developer_id="alice")
216
+ assert not lc._is_admin
217
+ result = lc.force_release_all()
218
+ assert result == 0
219
+
220
+
221
+ def test_force_release_all_empty_locks_returns_zero(monkeypatch):
222
+ """force_release_all returns 0 when there are no active locks."""
223
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
224
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
225
+
226
+ lc = mod.LockClient(local_only=True, developer_id="root")
227
+ lc._is_admin = True
228
+ monkeypatch.setattr(lc, "active", lambda: [])
229
+
230
+ result = lc.force_release_all()
231
+ assert result == 0
232
+
233
+
234
+ def test_force_release_all_counts_deleted_rows(monkeypatch):
235
+ """force_release_all returns count from API-returned deleted rows."""
236
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
237
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
238
+
239
+ lc = mod.LockClient(local_only=True, developer_id="root")
240
+ lc._is_admin = True
241
+
242
+ locks = [{"file_path": f"src/file{i}.py"} for i in range(3)]
243
+ monkeypatch.setattr(lc, "active", lambda: locks)
244
+
245
+ class RespWithData:
246
+ status = 200
247
+ data = [
248
+ {"file_path": "src/file0.py"},
249
+ {"file_path": "src/file1.py"},
250
+ {"file_path": "src/file2.py"},
251
+ ]
252
+ error = None
253
+
254
+ class FakeDeleteClient:
255
+ def table(self, *a, **k):
256
+ return self
257
+
258
+ def delete(self, *a, **k):
259
+ return self
260
+
261
+ def in_(self, *a, **k):
262
+ return self
263
+
264
+ def execute(self):
265
+ return RespWithData()
266
+
267
+ lc._client = FakeDeleteClient()
268
+ result = lc.force_release_all()
269
+ assert result == 3
270
+
271
+
272
+ def test_force_release_all_chunk_exception_returns_partial(monkeypatch):
273
+ """force_release_all returns partial count when chunk delete raises."""
274
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
275
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
276
+
277
+ lc = mod.LockClient(local_only=True, developer_id="root")
278
+ lc._is_admin = True
279
+
280
+ locks = [{"file_path": f"src/file{i}.py"} for i in range(5)]
281
+ monkeypatch.setattr(lc, "active", lambda: locks)
282
+
283
+ class ExplodingClient:
284
+ def table(self, *a, **k):
285
+ return self
286
+
287
+ def delete(self, *a, **k):
288
+ return self
289
+
290
+ def in_(self, *a, **k):
291
+ return self
292
+
293
+ def execute(self):
294
+ raise RuntimeError("network error")
295
+
296
+ lc._client = ExplodingClient()
297
+ # Should return 0 (no chunks completed) without raising
298
+ monkeypatch.setattr(mod.time, "sleep", lambda x: None)
299
+ result = lc.force_release_all()
300
+ assert result == 0 # first chunk already fails
301
+
302
+
303
+ def test_force_release_all_api_error_returns_partial(monkeypatch):
304
+ """force_release_all returns partial count when chunk returns error."""
305
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
306
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
307
+
308
+ lc = mod.LockClient(local_only=True, developer_id="root")
309
+ lc._is_admin = True
310
+
311
+ locks = [{"file_path": f"src/file{i}.py"} for i in range(3)]
312
+ monkeypatch.setattr(lc, "active", lambda: locks)
313
+
314
+ class ErrorResp:
315
+ status = 500
316
+ data = None
317
+ error = "Server error"
318
+
319
+ class ErrorClient:
320
+ def table(self, *a, **k):
321
+ return self
322
+
323
+ def delete(self, *a, **k):
324
+ return self
325
+
326
+ def in_(self, *a, **k):
327
+ return self
328
+
329
+ def execute(self):
330
+ return ErrorResp()
331
+
332
+ lc._client = ErrorClient()
333
+ result = lc.force_release_all()
334
+ assert result == 0 # first chunk fails, 0 deleted so far
335
+
336
+
337
+ def test_force_release_all_locks_with_no_path_skipped(monkeypatch):
338
+ """force_release_all skips lock entries that have empty or missing file_path."""
339
+ monkeypatch.setenv("SUPABASE_URL", "https://test.supabase.co")
340
+ monkeypatch.setenv("SUPABASE_ANON_KEY", "test_key")
341
+
342
+ lc = mod.LockClient(local_only=True, developer_id="root")
343
+ lc._is_admin = True
344
+
345
+ # Only locks with empty/missing file_path - should result in 0 paths
346
+ locks = [{"file_path": ""}, {"developer_id": "root"}, {"file_path": None}]
347
+ monkeypatch.setattr(lc, "active", lambda: locks)
348
+
349
+ result = lc.force_release_all()
350
+ assert result == 0 # No valid paths -> count == 0 -> early return
351
+
352
+
353
+ # (test_force_release_permission_denied removed: duplicate of
354
+ # test_force_release_nonadmin_other_dev_lock_denied above)