Mercurial > libervia-web
view tests/browser/unit/test_calls.py @ 1604:4a9679369856
browser (call): implements group calls:
rel 430
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 15 May 2024 17:40:33 +0200 |
parents | 00d04f51787e |
children |
line wrap: on
line source
#!/usr/bin/env python3 # Libervia Web # Copyright (C) 2009-2023 Jérôme Poisson (goffi@goffi.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import sys import pytest from unittest.mock import MagicMock, AsyncMock # Mock all non-stdlib modules class CancelError(Exception): pass sys.modules["bridge"] = MagicMock(AsyncBridge=AsyncMock) browser = sys.modules["browser"] = MagicMock( aio=MagicMock(), console=MagicMock(), document=MagicMock(), window=MagicMock() ) sys.modules["cache"] = MagicMock(cache=MagicMock()) dialog = sys.modules["dialog"] = MagicMock() dialog.CancelError = CancelError confirm_mock = dialog.Confirm.return_value = MagicMock() confirm_ashow = confirm_mock.ashow = AsyncMock() sys.modules["jid"] = MagicMock(JID=MagicMock) sys.modules["jid_search"] = MagicMock(JidSearch=MagicMock()) sys.modules["loading"] = MagicMock() sys.modules["template"] = MagicMock(Template=MagicMock()) sys.modules["webrtc"] = MagicMock(WebRTC=MagicMock()) # After mocking the modules, import the actual module from libervia.web.pages.calls import _browser as calls calls.cache.fill_identities = AsyncMock() class TestCallUI: @pytest.fixture def call_ui(self): ui = calls.CallUI() ui.webrtc.sid = None ui.call_status_tpl = MagicMock() ui.call_status_wrapper_elt = MagicMock() ui.call_box_elt = MagicMock() ui._update_call_button = MagicMock() ui.audio_player_elt = MagicMock() ui.switch_mode = MagicMock() ui.incoming_call_dialog_elt = MagicMock() ui.call_status_wrapper_elt.appended_elements = [] # Mock the `<=` operator for call_status_wrapper_elt def mock_append_child(parent, child): # Store the child for later assertion parent.appended_elements.append(child) return child ui.call_status_wrapper_elt.__le__ = mock_append_child return ui def test_status(self, call_ui): """Allowed values are set""" # Set initial status call_ui._status = None assert call_ui.status == None # Change status call_ui.status = "dialing" assert call_ui.status == "dialing" def test_disallowed_status(self, call_ui): """Not allowed status raises an Exception""" with pytest.raises(Exception, match="this status is not allowed"): call_ui.status = "disallowed_status" def test_status_element_update(self, call_ui): """A status change update the status element""" call_ui._callee = "test_callee" mock_cache = calls.cache mock_cache.identities = {"test_callee": {"nicknames": ["Test Callee Nickname"]}} status_elt_mock = MagicMock() call_ui.call_status_tpl.get_elt.return_value = status_elt_mock call_ui.status = "dialing" call_ui.call_status_tpl.get_elt.assert_called_with( { "entity": "test_callee", "status": "dialing", "name": "Test Callee Nickname", } ) assert call_ui.call_status_wrapper_elt.appended_elements[-1] == status_elt_mock def test_status_element_updates_with_unknown_callee(self, call_ui): """A status change update the element even if callee is not known""" call_ui._callee = "test_callee" mock_cache = calls.cache mock_cache.identities = {} call_ui.status = "dialing" call_ui.call_status_tpl.get_elt.assert_called_with( {"entity": "test_callee", "status": "dialing", "name": "test_callee"} ) def test_call_mode(self, call_ui): call_ui.call_mode = calls.AUDIO assert call_ui.call_mode == calls.AUDIO call_ui._update_call_button.assert_called_once() call_ui.call_box_elt.select.assert_called_with(".is-video-only") call_ui.call_mode = calls.VIDEO assert call_ui.call_mode == calls.VIDEO call_ui.call_box_elt.select.assert_called_with(".is-video-only") def test_call_mode_invalid_mode(self, call_ui): with pytest.raises(ValueError, match="Invalid call mode"): call_ui.call_mode = "invalid_mode" def test_switch_call_mode(self, call_ui): call_ui._call_mode = calls.AUDIO call_ui.switch_call_mode(None) assert call_ui.call_mode == calls.VIDEO call_ui._call_mode = calls.VIDEO call_ui.switch_call_mode(None) assert call_ui.call_mode == calls.AUDIO @pytest.mark.asyncio async def test_ignore_new_call_when_call_in_progress(self, call_ui): """Ignoring new incoming call when a call is already in progress.""" call_ui.sid = "some_sid" await call_ui.on_action_new( { "from_jid": "test@example.org", "session_id": "session123", "sub_type": "audio", }, "action_id", ) call_ui.audio_player_elt.play.assert_not_called() @pytest.mark.asyncio async def test_action_new_fill_identities(self, call_ui): """Filling identities from cache with the correct JID.""" calls.cache.identities = {"test@example.org": {"nicknames": ["Test User"]}} await call_ui.on_action_new( { "from_jid": "test@example.org", "session_id": "session123", "sub_type": "audio", }, "action_id", ) calls.cache.fill_identities.assert_called_once_with(["test@example.org"]) @pytest.mark.asyncio async def test_incoming_call_triggers_audio(self, call_ui): """Incoming call triggering the play action on the audio element.""" await call_ui.on_action_new( { "from_jid": "test@example.org", "session_id": "session123", "sub_type": "audio", }, "action_id", ) call_ui.audio_player_elt.play.assert_called_once() @pytest.mark.asyncio async def test_user_accepts_call(self, call_ui): """User accepting the incoming call.""" confirm_ashow.return_value = True await call_ui.on_action_new( { "from_jid": "test@example.org", "session_id": "session123", "sub_type": "video", }, "action_id", ) call_ui.switch_mode.assert_called_once_with("call") @pytest.mark.asyncio async def test_user_rejects_call(self, call_ui): """User rejecting the incoming call.""" confirm_ashow.return_value = False await call_ui.on_action_new( { "from_jid": "test@example.org", "session_id": "session123", "sub_type": "video", }, "action_id", ) call_ui.switch_mode.assert_not_called() @pytest.mark.asyncio async def test_call_gets_cancelled(self, call_ui): """Incoming call gets cancelled.""" confirm_ashow.side_effect = dialog.CancelError await call_ui.on_action_new( { "from_jid": "test@example.org", "session_id": "session123", "sub_type": "audio", }, "action_id", ) assert call_ui.sid is None call_ui.switch_mode.assert_not_called() def test_toggle_fullscreen_enter(self, call_ui, monkeypatch): """Entering fullscreen mode for video elements.""" monkeypatch.setattr(browser.document, "fullscreenElement", None) monkeypatch.setattr(call_ui.call_box_elt, "requestFullscreen", MagicMock()) btn_mock_show = MagicMock() monkeypatch.setattr("dialog.notification.show", btn_mock_show) btn_mock_add = MagicMock() btn_mock_remove = MagicMock() full_screen_mock = MagicMock(classList=MagicMock(add=btn_mock_add)) exit_full_screen_mock = MagicMock(classList=MagicMock(remove=btn_mock_remove)) mock_document = { "full_screen_btn": full_screen_mock, "exit_full_screen_btn": exit_full_screen_mock, } getitem_mock = MagicMock() monkeypatch.setattr(browser.document, "__getitem__", getitem_mock) getitem_mock.side_effect = mock_document.__getitem__ call_ui.toggle_fullscreen(True) call_ui.call_box_elt.requestFullscreen.assert_called_once() btn_mock_add.assert_called_with("is-hidden") btn_mock_remove.assert_called_with("is-hidden") btn_mock_show.assert_not_called() def test_toggle_fullscreen_exit(self, call_ui, monkeypatch): """Exiting fullscreen mode for video elements.""" # As it's not None, we're in fullscreen monkeypatch.setattr(browser.document, "fullscreenElement", "mocked_value") monkeypatch.setattr(browser.document, "exitFullscreen", MagicMock()) btn_mock_show = MagicMock() monkeypatch.setattr("dialog.notification.show", btn_mock_show) btn_mock_add = MagicMock() btn_mock_remove = MagicMock() full_screen_mock = MagicMock(classList=MagicMock(remove=btn_mock_remove)) exit_full_screen_mock = MagicMock(classList=MagicMock(add=btn_mock_add)) mock_document = { "full_screen_btn": full_screen_mock, "exit_full_screen_btn": exit_full_screen_mock, } getitem_mock = MagicMock() monkeypatch.setattr(browser.document, "__getitem__", getitem_mock) getitem_mock.side_effect = mock_document.__getitem__ call_ui.toggle_fullscreen(False) browser.document.exitFullscreen.assert_called_once() btn_mock_add.assert_called_with("is-hidden") btn_mock_remove.assert_called_with("is-hidden") btn_mock_show.assert_not_called() def test_toggle_audio_mute(self, call_ui, monkeypatch): """Toggle audio mute.""" mock_toggle_audio_mute = MagicMock( return_value=True ) monkeypatch.setattr(call_ui.webrtc, "toggle_audio_mute", mock_toggle_audio_mute) btn_mock_show = MagicMock() monkeypatch.setattr("dialog.notification.show", btn_mock_show) evt_mock = MagicMock(currentTarget=MagicMock(classList=MagicMock())) call_ui.toggle_audio_mute(evt_mock) mock_toggle_audio_mute.assert_called_once() evt_mock.currentTarget.classList.remove.assert_called_with("is-success") evt_mock.currentTarget.classList.add.assert_called_with("muted", "is-warning") btn_mock_show.assert_called_once_with("audio is now muted", level="info", delay=2) def test_toggle_video_mute(self, call_ui, monkeypatch): """Toggle video mute.""" mock_toggle_video_mute = MagicMock( return_value=True ) monkeypatch.setattr(call_ui.webrtc, "toggle_video_mute", mock_toggle_video_mute) btn_mock_show = MagicMock() monkeypatch.setattr("dialog.notification.show", btn_mock_show) evt_mock = MagicMock(currentTarget=MagicMock(classList=MagicMock())) call_ui.toggle_video_mute(evt_mock) mock_toggle_video_mute.assert_called_once() evt_mock.currentTarget.classList.remove.assert_called_with("is-success") evt_mock.currentTarget.classList.add.assert_called_with("muted", "is-warning") btn_mock_show.assert_called_once_with("video is now muted", level="info", delay=2)