main
py 181 lines 6.91 KB
Raw
1 import asyncio
2 import sys
3 from pathlib import Path
4
5 import pytest
6
7 PROJECT_ROOT = Path(__file__).resolve().parents[1]
8 if str(PROJECT_ROOT) not in sys.path:
9 sys.path.insert(0, str(PROJECT_ROOT))
10
11 from plugins._whatsapp_integration.helpers import bridge_manager
12
13
14 @pytest.fixture(autouse=True)
15 def reset_bridge_manager_state():
16 bridge_manager._bridge_lock = None
17 bridge_manager._bridge_lock_loop = None
18 bridge_manager._bridge_process = None
19 bridge_manager._bridge_config.clear()
20 yield
21 bridge_manager._bridge_lock = None
22 bridge_manager._bridge_lock_loop = None
23 bridge_manager._bridge_process = None
24 bridge_manager._bridge_config.clear()
25
26
27 def test_get_bridge_lock_recreates_lock_when_loop_marker_changes():
28 async def _run():
29 original_lock = asyncio.Lock()
30 bridge_manager._bridge_lock = original_lock
31 bridge_manager._bridge_lock_loop = object()
32
33 refreshed_lock = bridge_manager._get_bridge_lock()
34
35 assert refreshed_lock is bridge_manager._bridge_lock
36 assert refreshed_lock is not original_lock
37
38 asyncio.run(_run())
39
40
41 def test_ensure_bridge_dependencies_reinstalls_invalid_dependency_tree(monkeypatch):
42 state_writes: list[dict] = []
43 validate_results = [False, True]
44 reinstall_calls: list[bool] = []
45
46 monkeypatch.setattr(bridge_manager, "NODE_MODULES_DIR", "/tmp/whatsapp-node-modules")
47 monkeypatch.setattr(bridge_manager.os.path, "isdir", lambda path: path == "/tmp/whatsapp-node-modules")
48
49 async def fake_build_dependency_state():
50 return {"package_json_hash": "a", "package_lock_hash": "b"}
51
52 async def fake_validate_bridge_dependencies():
53 return validate_results.pop(0)
54
55 async def fake_reinstall_bridge_dependencies():
56 reinstall_calls.append(True)
57
58 monkeypatch.setattr(bridge_manager, "_build_dependency_state", fake_build_dependency_state)
59 monkeypatch.setattr(bridge_manager, "_load_dependency_state", lambda: {"package_json_hash": "a", "package_lock_hash": "b"})
60 monkeypatch.setattr(bridge_manager, "_validate_bridge_dependencies", fake_validate_bridge_dependencies)
61 monkeypatch.setattr(bridge_manager, "_reinstall_bridge_dependencies", fake_reinstall_bridge_dependencies)
62 monkeypatch.setattr(bridge_manager, "_write_dependency_state", lambda state: state_writes.append(state))
63
64 asyncio.run(bridge_manager._ensure_bridge_dependencies())
65
66 assert reinstall_calls == [True]
67 assert state_writes == [{"package_json_hash": "a", "package_lock_hash": "b"}]
68
69
70 def test_ensure_bridge_dependencies_bootstraps_missing_state_without_reinstall(monkeypatch):
71 state_writes: list[dict] = []
72 reinstall_calls: list[bool] = []
73
74 monkeypatch.setattr(bridge_manager, "NODE_MODULES_DIR", "/tmp/whatsapp-node-modules")
75 monkeypatch.setattr(bridge_manager.os.path, "isdir", lambda path: path == "/tmp/whatsapp-node-modules")
76
77 async def fake_build_dependency_state():
78 return {"package_json_hash": "a", "package_lock_hash": "b"}
79
80 async def fake_validate_bridge_dependencies():
81 return True
82
83 async def fake_reinstall_bridge_dependencies():
84 reinstall_calls.append(True)
85
86 monkeypatch.setattr(bridge_manager, "_build_dependency_state", fake_build_dependency_state)
87 monkeypatch.setattr(bridge_manager, "_load_dependency_state", lambda: None)
88 monkeypatch.setattr(bridge_manager, "_validate_bridge_dependencies", fake_validate_bridge_dependencies)
89 monkeypatch.setattr(bridge_manager, "_reinstall_bridge_dependencies", fake_reinstall_bridge_dependencies)
90 monkeypatch.setattr(bridge_manager, "_write_dependency_state", lambda state: state_writes.append(state))
91
92 asyncio.run(bridge_manager._ensure_bridge_dependencies())
93
94 assert reinstall_calls == []
95 assert state_writes == [{"package_json_hash": "a", "package_lock_hash": "b"}]
96
97
98 def test_reinstall_bridge_dependencies_uses_local_npm_cache(monkeypatch):
99 run_calls: list[tuple[list[str], dict[str, str] | None]] = []
100 real_isdir = bridge_manager.os.path.isdir
101
102 monkeypatch.setattr(bridge_manager.PrintStyle, "warning", staticmethod(lambda message: None))
103 monkeypatch.setattr(bridge_manager.PrintStyle, "info", staticmethod(lambda message: None))
104 monkeypatch.setattr(bridge_manager, "NODE_MODULES_DIR", "/tmp/whatsapp-node-modules")
105 monkeypatch.setattr(bridge_manager, "BRIDGE_PACKAGE_LOCK", "/tmp/package-lock.json")
106 monkeypatch.setattr(bridge_manager, "BRIDGE_INSTALL_STATE", "/tmp/deps-state.json")
107 monkeypatch.setattr(bridge_manager, "BRIDGE_NPM_CACHE", "/tmp/npm-cache")
108 monkeypatch.setattr(
109 bridge_manager.os.path,
110 "isdir",
111 lambda path: path == "/tmp/whatsapp-node-modules" or real_isdir(path),
112 )
113 monkeypatch.setattr(
114 bridge_manager.os.path,
115 "isfile",
116 lambda path: path == "/tmp/package-lock.json",
117 )
118 monkeypatch.setattr(bridge_manager.shutil, "rmtree", lambda path, ignore_errors=True: None)
119
120 async def fake_run_subprocess(command, *, cwd, env=None):
121 run_calls.append((list(command), env))
122 return ""
123
124 monkeypatch.setattr(bridge_manager, "_run_subprocess", fake_run_subprocess)
125
126 asyncio.run(bridge_manager._reinstall_bridge_dependencies())
127
128 assert run_calls == [
129 (
130 ["npm", "ci", "--omit=dev", "--no-audit", "--no-fund"],
131 {"npm_config_cache": "/tmp/npm-cache"},
132 ),
133 ]
134
135
136 def test_start_bridge_reinstalls_and_retries_after_dependency_failure(monkeypatch):
137 ensure_calls: list[bool] = []
138 start_attempts = [
139 (False, "Error: Cannot find package '@whiskeysockets/baileys'"),
140 (True, ""),
141 ]
142
143 async def fake_ensure_bridge_dependencies(force_reinstall: bool = False):
144 ensure_calls.append(force_reinstall)
145
146 async def fake_start_bridge_once(**kwargs):
147 return start_attempts.pop(0)
148
149 monkeypatch.setattr(bridge_manager, "_ensure_bridge_dependencies", fake_ensure_bridge_dependencies)
150 monkeypatch.setattr(bridge_manager, "_start_bridge_once", fake_start_bridge_once)
151
152 started = asyncio.run(bridge_manager.start_bridge(
153 port=3100,
154 session_dir="/tmp/wa-session",
155 cache_dir="/tmp/wa-cache",
156 ))
157
158 assert started is True
159 assert ensure_calls == [False, True]
160
161
162 def test_start_bridge_does_not_reinstall_on_non_dependency_failure(monkeypatch):
163 ensure_calls: list[bool] = []
164
165 async def fake_ensure_bridge_dependencies(force_reinstall: bool = False):
166 ensure_calls.append(force_reinstall)
167
168 async def fake_start_bridge_once(**kwargs):
169 return False, "Bridge closed unexpectedly without module resolution errors"
170
171 monkeypatch.setattr(bridge_manager, "_ensure_bridge_dependencies", fake_ensure_bridge_dependencies)
172 monkeypatch.setattr(bridge_manager, "_start_bridge_once", fake_start_bridge_once)
173
174 started = asyncio.run(bridge_manager.start_bridge(
175 port=3100,
176 session_dir="/tmp/wa-session",
177 cache_dir="/tmp/wa-cache",
178 ))
179
180 assert started is False
181 assert ensure_calls == [False]