main
py 80 lines 2.91 KB
Raw
1 import re
2 import sys
3 from pathlib import Path
4
5
6 PROJECT_ROOT = Path(__file__).resolve().parents[1]
7 if str(PROJECT_ROOT) not in sys.path:
8 sys.path.insert(0, str(PROJECT_ROOT))
9
10
11 def _get_named_exports(source: str) -> set[str]:
12 exports: set[str] = set()
13
14 exports.update(re.findall(r"^export\s+function\s+([A-Za-z0-9_]+)\s*\(", source, flags=re.M))
15 exports.update(re.findall(r"^export\s+const\s+([A-Za-z0-9_]+)\s*=", source, flags=re.M))
16 exports.update(re.findall(r"^export\s+class\s+([A-Za-z0-9_]+)\s*[\{:]", source, flags=re.M))
17
18 for m in re.findall(r"^export\s*\{([^}]+)\}\s*;?", source, flags=re.M):
19 for item in m.split(","):
20 item = item.strip()
21 if not item:
22 continue
23 # Handle: `foo as bar`
24 parts = item.split()
25 if len(parts) >= 3 and parts[-2] == "as":
26 exports.add(parts[-1])
27 else:
28 exports.add(parts[0])
29
30 return exports
31
32
33 def test_websocket_js_exports_minimal_namespaced_api_surface() -> None:
34 source = (PROJECT_ROOT / "webui" / "js" / "websocket.js").read_text(encoding="utf-8")
35 exports = _get_named_exports(source)
36
37 assert "createNamespacedClient" in exports
38 assert "getNamespacedClient" in exports
39
40 assert "broadcast" not in exports
41 assert "requestAll" not in exports
42
43
44 def test_completed_state_push_cannot_overwrite_disconnected_mode() -> None:
45 source = (
46 PROJECT_ROOT / "webui" / "components" / "sync" / "sync-store.js"
47 ).read_text(encoding="utf-8")
48
49 apply_end = source.split("await applySnapshot(data.snapshot", 1)[1].split(
50 'this._setMode(SYNC_MODES.HEALTHY, "push applied");', 1
51 )[0]
52 assert "if (!stateSocket.isConnected()) return;" in apply_end
53
54
55 def test_state_push_handlers_are_serialized() -> None:
56 source = (
57 PROJECT_ROOT / "webui" / "components" / "sync" / "sync-store.js"
58 ).read_text(encoding="utf-8")
59
60 subscription = source.split('stateSocket.on("state_push"', 1)[1].split(
61 'debug("[syncStore] subscribed to state_push")', 1
62 )[0]
63 assert "this._pushQueue = this._pushQueue" in subscription
64 assert ".then(() => this._handlePush(envelope))" in subscription
65
66
67 def test_partial_snapshot_retains_sidebar_collections_and_extension_shape() -> None:
68 source = (PROJECT_ROOT / "webui" / "index.js").read_text(encoding="utf-8")
69 request_builder = source.split(
70 "export function buildStateRequestPayload", 1
71 )[1].split("export async function applySnapshot", 1)[0]
72
73 assert "collections_delta: true" in request_builder
74 assert "const hasCollections =" in source
75 assert "Array.isArray(snapshot.contexts) && Array.isArray(snapshot.tasks)" in source
76 assert "snapshot: extensionSnapshot" in source
77 assert "contexts: chatsStore.contexts" in source
78 assert "tasks: tasksStore.tasks" in source
79 assert "if (hasCollections)" in source
80 assert "snapshot.contexts || []" not in source