Mercurial > libervia-web
comparison tests/browser/unit/test_calls.py @ 1551:00d04f51787e
tests: add `calls` unit tests:
fix 423
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 09 Aug 2023 00:22:18 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
1550:4b0464b2a12b | 1551:00d04f51787e |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 # Libervia Web | |
4 # Copyright (C) 2009-2023 Jérôme Poisson (goffi@goffi.org) | |
5 | |
6 # This program is free software: you can redistribute it and/or modify | |
7 # it under the terms of the GNU Affero General Public License as published by | |
8 # the Free Software Foundation, either version 3 of the License, or | |
9 # (at your option) any later version. | |
10 | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU Affero General Public License for more details. | |
15 | |
16 # You should have received a copy of the GNU Affero General Public License | |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | |
19 import sys | |
20 import pytest | |
21 from unittest.mock import MagicMock, AsyncMock | |
22 | |
23 # Mock all non-stdlib modules | |
24 | |
25 | |
26 class CancelError(Exception): | |
27 pass | |
28 | |
29 | |
30 sys.modules["bridge"] = MagicMock(AsyncBridge=AsyncMock) | |
31 browser = sys.modules["browser"] = MagicMock( | |
32 aio=MagicMock(), console=MagicMock(), document=MagicMock(), window=MagicMock() | |
33 ) | |
34 sys.modules["cache"] = MagicMock(cache=MagicMock()) | |
35 dialog = sys.modules["dialog"] = MagicMock() | |
36 dialog.CancelError = CancelError | |
37 confirm_mock = dialog.Confirm.return_value = MagicMock() | |
38 confirm_ashow = confirm_mock.ashow = AsyncMock() | |
39 sys.modules["jid"] = MagicMock(JID=MagicMock) | |
40 sys.modules["jid_search"] = MagicMock(JidSearch=MagicMock()) | |
41 sys.modules["loading"] = MagicMock() | |
42 sys.modules["template"] = MagicMock(Template=MagicMock()) | |
43 sys.modules["webrtc"] = MagicMock(WebRTC=MagicMock()) | |
44 | |
45 # After mocking the modules, import the actual module | |
46 from libervia.web.pages.calls import _browser as calls | |
47 | |
48 calls.cache.fill_identities = AsyncMock() | |
49 | |
50 | |
51 class TestCallUI: | |
52 @pytest.fixture | |
53 def call_ui(self): | |
54 ui = calls.CallUI() | |
55 ui.webrtc.sid = None | |
56 ui.call_status_tpl = MagicMock() | |
57 ui.call_status_wrapper_elt = MagicMock() | |
58 ui.call_box_elt = MagicMock() | |
59 ui._update_call_button = MagicMock() | |
60 ui.audio_player_elt = MagicMock() | |
61 ui.switch_mode = MagicMock() | |
62 ui.incoming_call_dialog_elt = MagicMock() | |
63 | |
64 ui.call_status_wrapper_elt.appended_elements = [] | |
65 | |
66 # Mock the `<=` operator for call_status_wrapper_elt | |
67 def mock_append_child(parent, child): | |
68 # Store the child for later assertion | |
69 parent.appended_elements.append(child) | |
70 return child | |
71 | |
72 ui.call_status_wrapper_elt.__le__ = mock_append_child | |
73 | |
74 return ui | |
75 | |
76 def test_status(self, call_ui): | |
77 """Allowed values are set""" | |
78 # Set initial status | |
79 call_ui._status = None | |
80 assert call_ui.status == None | |
81 | |
82 # Change status | |
83 call_ui.status = "dialing" | |
84 assert call_ui.status == "dialing" | |
85 | |
86 def test_disallowed_status(self, call_ui): | |
87 """Not allowed status raises an Exception""" | |
88 with pytest.raises(Exception, match="this status is not allowed"): | |
89 call_ui.status = "disallowed_status" | |
90 | |
91 def test_status_element_update(self, call_ui): | |
92 """A status change update the status element""" | |
93 call_ui._callee = "test_callee" | |
94 mock_cache = calls.cache | |
95 mock_cache.identities = {"test_callee": {"nicknames": ["Test Callee Nickname"]}} | |
96 | |
97 status_elt_mock = MagicMock() | |
98 call_ui.call_status_tpl.get_elt.return_value = status_elt_mock | |
99 | |
100 call_ui.status = "dialing" | |
101 call_ui.call_status_tpl.get_elt.assert_called_with( | |
102 { | |
103 "entity": "test_callee", | |
104 "status": "dialing", | |
105 "name": "Test Callee Nickname", | |
106 } | |
107 ) | |
108 assert call_ui.call_status_wrapper_elt.appended_elements[-1] == status_elt_mock | |
109 | |
110 def test_status_element_updates_with_unknown_callee(self, call_ui): | |
111 """A status change update the element even if callee is not known""" | |
112 call_ui._callee = "test_callee" | |
113 mock_cache = calls.cache | |
114 mock_cache.identities = {} | |
115 | |
116 call_ui.status = "dialing" | |
117 call_ui.call_status_tpl.get_elt.assert_called_with( | |
118 {"entity": "test_callee", "status": "dialing", "name": "test_callee"} | |
119 ) | |
120 | |
121 def test_call_mode(self, call_ui): | |
122 call_ui.call_mode = calls.AUDIO | |
123 assert call_ui.call_mode == calls.AUDIO | |
124 call_ui._update_call_button.assert_called_once() | |
125 call_ui.call_box_elt.select.assert_called_with(".is-video-only") | |
126 | |
127 call_ui.call_mode = calls.VIDEO | |
128 assert call_ui.call_mode == calls.VIDEO | |
129 call_ui.call_box_elt.select.assert_called_with(".is-video-only") | |
130 | |
131 def test_call_mode_invalid_mode(self, call_ui): | |
132 with pytest.raises(ValueError, match="Invalid call mode"): | |
133 call_ui.call_mode = "invalid_mode" | |
134 | |
135 def test_switch_call_mode(self, call_ui): | |
136 call_ui._call_mode = calls.AUDIO | |
137 call_ui.switch_call_mode(None) | |
138 assert call_ui.call_mode == calls.VIDEO | |
139 | |
140 call_ui._call_mode = calls.VIDEO | |
141 call_ui.switch_call_mode(None) | |
142 assert call_ui.call_mode == calls.AUDIO | |
143 | |
144 @pytest.mark.asyncio | |
145 async def test_ignore_new_call_when_call_in_progress(self, call_ui): | |
146 """Ignoring new incoming call when a call is already in progress.""" | |
147 call_ui.sid = "some_sid" | |
148 await call_ui.on_action_new( | |
149 { | |
150 "from_jid": "test@example.org", | |
151 "session_id": "session123", | |
152 "sub_type": "audio", | |
153 }, | |
154 "action_id", | |
155 ) | |
156 call_ui.audio_player_elt.play.assert_not_called() | |
157 | |
158 @pytest.mark.asyncio | |
159 async def test_action_new_fill_identities(self, call_ui): | |
160 """Filling identities from cache with the correct JID.""" | |
161 calls.cache.identities = {"test@example.org": {"nicknames": ["Test User"]}} | |
162 | |
163 await call_ui.on_action_new( | |
164 { | |
165 "from_jid": "test@example.org", | |
166 "session_id": "session123", | |
167 "sub_type": "audio", | |
168 }, | |
169 "action_id", | |
170 ) | |
171 calls.cache.fill_identities.assert_called_once_with(["test@example.org"]) | |
172 | |
173 @pytest.mark.asyncio | |
174 async def test_incoming_call_triggers_audio(self, call_ui): | |
175 """Incoming call triggering the play action on the audio element.""" | |
176 await call_ui.on_action_new( | |
177 { | |
178 "from_jid": "test@example.org", | |
179 "session_id": "session123", | |
180 "sub_type": "audio", | |
181 }, | |
182 "action_id", | |
183 ) | |
184 call_ui.audio_player_elt.play.assert_called_once() | |
185 | |
186 @pytest.mark.asyncio | |
187 async def test_user_accepts_call(self, call_ui): | |
188 """User accepting the incoming call.""" | |
189 confirm_ashow.return_value = True | |
190 await call_ui.on_action_new( | |
191 { | |
192 "from_jid": "test@example.org", | |
193 "session_id": "session123", | |
194 "sub_type": "video", | |
195 }, | |
196 "action_id", | |
197 ) | |
198 call_ui.switch_mode.assert_called_once_with("call") | |
199 | |
200 @pytest.mark.asyncio | |
201 async def test_user_rejects_call(self, call_ui): | |
202 """User rejecting the incoming call.""" | |
203 confirm_ashow.return_value = False | |
204 await call_ui.on_action_new( | |
205 { | |
206 "from_jid": "test@example.org", | |
207 "session_id": "session123", | |
208 "sub_type": "video", | |
209 }, | |
210 "action_id", | |
211 ) | |
212 call_ui.switch_mode.assert_not_called() | |
213 | |
214 @pytest.mark.asyncio | |
215 async def test_call_gets_cancelled(self, call_ui): | |
216 """Incoming call gets cancelled.""" | |
217 confirm_ashow.side_effect = dialog.CancelError | |
218 await call_ui.on_action_new( | |
219 { | |
220 "from_jid": "test@example.org", | |
221 "session_id": "session123", | |
222 "sub_type": "audio", | |
223 }, | |
224 "action_id", | |
225 ) | |
226 assert call_ui.sid is None | |
227 call_ui.switch_mode.assert_not_called() | |
228 | |
229 def test_toggle_fullscreen_enter(self, call_ui, monkeypatch): | |
230 """Entering fullscreen mode for video elements.""" | |
231 monkeypatch.setattr(browser.document, "fullscreenElement", None) | |
232 monkeypatch.setattr(call_ui.call_box_elt, "requestFullscreen", MagicMock()) | |
233 | |
234 btn_mock_show = MagicMock() | |
235 monkeypatch.setattr("dialog.notification.show", btn_mock_show) | |
236 btn_mock_add = MagicMock() | |
237 btn_mock_remove = MagicMock() | |
238 | |
239 full_screen_mock = MagicMock(classList=MagicMock(add=btn_mock_add)) | |
240 exit_full_screen_mock = MagicMock(classList=MagicMock(remove=btn_mock_remove)) | |
241 | |
242 mock_document = { | |
243 "full_screen_btn": full_screen_mock, | |
244 "exit_full_screen_btn": exit_full_screen_mock, | |
245 } | |
246 | |
247 getitem_mock = MagicMock() | |
248 monkeypatch.setattr(browser.document, "__getitem__", getitem_mock) | |
249 getitem_mock.side_effect = mock_document.__getitem__ | |
250 | |
251 call_ui.toggle_fullscreen(True) | |
252 | |
253 call_ui.call_box_elt.requestFullscreen.assert_called_once() | |
254 btn_mock_add.assert_called_with("is-hidden") | |
255 btn_mock_remove.assert_called_with("is-hidden") | |
256 btn_mock_show.assert_not_called() | |
257 | |
258 def test_toggle_fullscreen_exit(self, call_ui, monkeypatch): | |
259 """Exiting fullscreen mode for video elements.""" | |
260 # As it's not None, we're in fullscreen | |
261 monkeypatch.setattr(browser.document, "fullscreenElement", "mocked_value") | |
262 monkeypatch.setattr(browser.document, "exitFullscreen", MagicMock()) | |
263 | |
264 btn_mock_show = MagicMock() | |
265 monkeypatch.setattr("dialog.notification.show", btn_mock_show) | |
266 btn_mock_add = MagicMock() | |
267 btn_mock_remove = MagicMock() | |
268 | |
269 full_screen_mock = MagicMock(classList=MagicMock(remove=btn_mock_remove)) | |
270 exit_full_screen_mock = MagicMock(classList=MagicMock(add=btn_mock_add)) | |
271 | |
272 mock_document = { | |
273 "full_screen_btn": full_screen_mock, | |
274 "exit_full_screen_btn": exit_full_screen_mock, | |
275 } | |
276 | |
277 getitem_mock = MagicMock() | |
278 monkeypatch.setattr(browser.document, "__getitem__", getitem_mock) | |
279 getitem_mock.side_effect = mock_document.__getitem__ | |
280 | |
281 call_ui.toggle_fullscreen(False) | |
282 | |
283 browser.document.exitFullscreen.assert_called_once() | |
284 btn_mock_add.assert_called_with("is-hidden") | |
285 btn_mock_remove.assert_called_with("is-hidden") | |
286 btn_mock_show.assert_not_called() | |
287 | |
288 def test_toggle_audio_mute(self, call_ui, monkeypatch): | |
289 """Toggle audio mute.""" | |
290 mock_toggle_audio_mute = MagicMock( | |
291 return_value=True | |
292 ) | |
293 monkeypatch.setattr(call_ui.webrtc, "toggle_audio_mute", mock_toggle_audio_mute) | |
294 | |
295 btn_mock_show = MagicMock() | |
296 monkeypatch.setattr("dialog.notification.show", btn_mock_show) | |
297 | |
298 evt_mock = MagicMock(currentTarget=MagicMock(classList=MagicMock())) | |
299 | |
300 call_ui.toggle_audio_mute(evt_mock) | |
301 | |
302 mock_toggle_audio_mute.assert_called_once() | |
303 evt_mock.currentTarget.classList.remove.assert_called_with("is-success") | |
304 evt_mock.currentTarget.classList.add.assert_called_with("muted", "is-warning") | |
305 btn_mock_show.assert_called_once_with("audio is now muted", level="info", delay=2) | |
306 | |
307 def test_toggle_video_mute(self, call_ui, monkeypatch): | |
308 """Toggle video mute.""" | |
309 mock_toggle_video_mute = MagicMock( | |
310 return_value=True | |
311 ) | |
312 monkeypatch.setattr(call_ui.webrtc, "toggle_video_mute", mock_toggle_video_mute) | |
313 | |
314 btn_mock_show = MagicMock() | |
315 monkeypatch.setattr("dialog.notification.show", btn_mock_show) | |
316 | |
317 evt_mock = MagicMock(currentTarget=MagicMock(classList=MagicMock())) | |
318 | |
319 call_ui.toggle_video_mute(evt_mock) | |
320 | |
321 mock_toggle_video_mute.assert_called_once() | |
322 evt_mock.currentTarget.classList.remove.assert_called_with("is-success") | |
323 evt_mock.currentTarget.classList.add.assert_called_with("muted", "is-warning") | |
324 btn_mock_show.assert_called_once_with("video is now muted", level="info", delay=2) |