Mercurial > libervia-backend
view tests/unit/frontends/test_webrtc.py @ 4309:b56b1eae7994
component email gateway: add multicasting:
XEP-0033 multicasting is now supported both for incoming and outgoing messages. XEP-0033
metadata are converted to suitable Email headers and vice versa.
Email address and JID are both supported, and delivery is done by the gateway when
suitable on incoming messages.
rel 450
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 26 Sep 2024 16:12:01 +0200 |
parents | f1d0cde61af7 |
children |
line wrap: on
line source
#!/usr/bin/env python3 # Libervia: an XMPP client # 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/>. from unittest.mock import AsyncMock, MagicMock import pytest try: from gi.repository import Gst except ImportError: pytest.skip("Gst not available.", allow_module_level=True) from libervia.backend.core import exceptions from libervia.backend.tools.common import data_format from libervia.frontends.tools import webrtc as webrtc_mod @pytest.fixture def host(monkeypatch): host = MagicMock() host.bridge = AsyncMock() host.app.expand = lambda s: s return host @pytest.fixture(scope="function") def webrtc(host): """Fixture for WebRTC instantiation.""" profile = "test_profile" instance = webrtc_mod.WebRTC(host.bridge, profile) instance._set_media_types = MagicMock() instance.start_pipeline = MagicMock() instance.link_element_or_pad = MagicMock() instance.webrtcbin = MagicMock() instance.webrtcbin.emit = MagicMock() instance.GstSdp_SDPMessage_new_from_text = MagicMock() instance.GstWebRTC_WebRTCSessionDescription_new = MagicMock() instance.Gst_Promise_new_with_change_func = MagicMock() return instance class TestWebRtc: def test_get_payload_types(self, webrtc): """The method can identify the correct payload types for video and audio.""" fake_sdpmsg = MagicMock() fake_media = MagicMock() fake_caps = MagicMock() fake_structure = MagicMock() # This side effect will return 'fake_video_encoding' first, then # 'fake_audio_encoding'. fake_structure.__getitem__.side_effect = [ "fake_video_encoding", "fake_audio_encoding", ] fake_caps.get_structure.return_value = fake_structure fake_media.get_format.side_effect = ["webrtc-datachannel", "10", "20"] fake_media.get_caps_from_media.return_value = fake_caps fake_sdpmsg.get_media.return_value = fake_media fake_sdpmsg.medias_len.return_value = 1 fake_media.formats_len.return_value = 3 result = webrtc.get_payload_types( fake_sdpmsg, "fake_video_encoding", "fake_audio_encoding" ) expected_result = {"fake_video_encoding": 10, "fake_audio_encoding": 20} assert result == expected_result def test_on_accepted_call(self, webrtc): """The method correctly sets the remote SDP upon acceptance of an outgoing call.""" sdp_str = "mock_sdp_string" profile_str = "test_profile" webrtc.on_accepted_call(sdp_str, profile_str) # remote description must be set assert webrtc.webrtcbin.emit.call_count == 1 assert webrtc.webrtcbin.emit.call_args[0][0] == "set-remote-description" @pytest.mark.asyncio async def test_answer_call(self, webrtc, monkeypatch): """The method correctly answers an incoming call.""" mock_setup_call = AsyncMock() def mock_get_payload_types(sdpmsg, video_encoding, audio_encoding): return {"VP8": 96, "OPUS": 97} monkeypatch.setattr(webrtc, "setup_call", mock_setup_call) monkeypatch.setattr(webrtc, "get_payload_types", mock_get_payload_types) sdp_str = "mock_sdp_string" profile_str = "mock_profile" await webrtc.answer_call(sdp_str, profile_str) mock_setup_call.assert_called_once_with("responder", audio_pt=97, video_pt=96) # remote description must be set assert webrtc.webrtcbin.emit.call_count == 1 assert webrtc.webrtcbin.emit.call_args[0][0] == "set-remote-description" def test_on_remote_decodebin_stream_video(self, webrtc, monkeypatch): """The method correctly handles video streams from the remote decodebin.""" mock_pipeline = MagicMock() monkeypatch.setattr(webrtc, "pipeline", mock_pipeline) mock_pad = MagicMock() mock_caps = MagicMock() mock_structure = MagicMock() mock_pad.has_current_caps.return_value = True mock_pad.get_current_caps.return_value = mock_caps mock_caps.__len__.return_value = 1 mock_caps.__getitem__.return_value = mock_structure mock_structure.get_name.return_value = "video/x-h264" # We use non-standard resolution as example to trigger the workaround mock_structure.get_int.side_effect = lambda x: MagicMock( value=990 if x == "width" else 557 ) webrtc.on_remote_decodebin_stream(None, mock_pad) assert webrtc._remote_video_pad == mock_pad mock_pipeline.add.assert_called() mock_pad.link.assert_called() def test_on_remote_decodebin_stream_audio(self, webrtc, monkeypatch): """The method correctly handles audio streams from the remote decodebin.""" mock_pipeline = MagicMock() monkeypatch.setattr(webrtc, "pipeline", mock_pipeline) mock_pad = MagicMock() mock_caps = MagicMock() mock_structure = MagicMock() mock_pad.has_current_caps.return_value = True mock_pad.get_current_caps.return_value = mock_caps mock_caps.__len__.return_value = 1 mock_caps.__getitem__.return_value = mock_structure mock_structure.get_name.return_value = "audio/x-raw" webrtc.on_remote_decodebin_stream(None, mock_pad) mock_pipeline.add.assert_called() mock_pad.link.assert_called() @pytest.mark.skipif(Gst is None, reason="GStreamer is not available") @pytest.mark.asyncio async def test_setup_call_correct_role(self, host, webrtc, monkeypatch): """Roles are set in setup_call.""" monkeypatch.setattr(Gst, "parse_launch", MagicMock()) # we use MagicMock class and not instance on purpose, to pass the "isinstance" # test of "setup_call". monkeypatch.setattr(Gst, "Pipeline", MagicMock) monkeypatch.setattr(data_format, "deserialise", MagicMock(return_value=[])) await webrtc.setup_call("initiator") assert webrtc.role == "initiator" await webrtc.setup_call("responder") assert webrtc.role == "responder" with pytest.raises(AssertionError): await webrtc.setup_call("invalid_role") @pytest.mark.asyncio async def test_setup_call_test_mode(self, host, webrtc, monkeypatch): """Test mode use fake video and audio in setup_call.""" monkeypatch.setattr(data_format, "deserialise", MagicMock(return_value=[])) monkeypatch.setattr(webrtc, "sources_data", webrtc_mod.SourcesTest()) await webrtc.setup_call("initiator") assert "videotestsrc" in webrtc.gst_pipe_desc assert "audiotestsrc" in webrtc.gst_pipe_desc @pytest.mark.asyncio async def test_setup_call_normal_mode(self, host, webrtc, monkeypatch): """Normal mode use real video and audio in setup_call.""" monkeypatch.setattr(data_format, "deserialise", MagicMock(return_value=[])) monkeypatch.setattr(webrtc, "sources_data", webrtc_mod.SourcesAuto()) await webrtc.setup_call("initiator") assert "v4l2src" in webrtc.gst_pipe_desc assert "pulsesrc" in webrtc.gst_pipe_desc @pytest.mark.skipif(Gst is None, reason="GStreamer is not available") @pytest.mark.asyncio async def test_setup_call_with_stun_and_turn(self, host, webrtc, monkeypatch): """STUN and TURN server configurations are done in setup_call.""" mock_pipeline = MagicMock() mock_parse_launch = MagicMock() mock_parse_launch.return_value = mock_pipeline # As for "test_setup_call_correct_role" we user MagicMock class and not instance # on purpose here. monkeypatch.setattr(Gst, "Pipeline", MagicMock) monkeypatch.setattr(Gst, "parse_launch", mock_parse_launch) mock_pipeline.get_by_name.return_value = webrtc.webrtcbin mock_external_disco = [ {"type": "stun", "transport": "udp", "host": "stun.host", "port": "3478"}, { "type": "turn", "transport": "udp", "host": "turn.host", "port": "3478", "username": "user", "password": "pass", }, ] monkeypatch.setattr( data_format, "deserialise", MagicMock(return_value=mock_external_disco) ) mock_emit = AsyncMock() monkeypatch.setattr(webrtc.webrtcbin, "emit", mock_emit) mock_set_property = AsyncMock() monkeypatch.setattr(webrtc.webrtcbin, "set_property", mock_set_property) await webrtc.setup_call("initiator") host.bridge.external_disco_get.assert_called_once_with("", webrtc.profile) mock_set_property.assert_any_call("stun-server", "stun://stun.host:3478") mock_emit.assert_called_once_with( "add-turn-server", "turn://user:pass@turn.host:3478" ) @pytest.mark.skipif(Gst is None, reason="GStreamer is not available") @pytest.mark.asyncio async def test_setup_call_gstreamer_pipeline_failure(self, webrtc, monkeypatch): """Test setup_call method handling Gstreamer pipeline failure.""" monkeypatch.setattr(Gst, "parse_launch", lambda _: None) with pytest.raises(exceptions.InternalError): await webrtc.setup_call("initiator")