Mercurial > libervia-desktop-kivy
comparison tests/unit/test_plugin_calls.py @ 500:b0f70be331c5
tests: unit test for "Calls" plugins:
rel 424
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 04 Oct 2023 22:54:42 +0200 |
parents | |
children | f6b8300e8234 |
comparison
equal
deleted
inserted
replaced
499:f387992d8e37 | 500:b0f70be331c5 |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 # Libervia: an XMPP client | |
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 from unittest.mock import AsyncMock, MagicMock | |
20 | |
21 from gi.repository import Gst | |
22 from libervia.backend.core import exceptions | |
23 from libervia.backend.tools.common import data_format | |
24 import pytest | |
25 | |
26 from libervia.desktop_kivy import G | |
27 from libervia.desktop_kivy.plugins import plugin_wid_calls | |
28 | |
29 | |
30 @pytest.fixture | |
31 def host(monkeypatch): | |
32 host = MagicMock() | |
33 host.a_bridge = AsyncMock() | |
34 monkeypatch.setattr(G, "_host", host, raising=False) | |
35 return host | |
36 | |
37 | |
38 @pytest.fixture(scope="function") | |
39 def webrtc(): | |
40 """Fixture for WebRTC instantiation.""" | |
41 host_mock = MagicMock() | |
42 profile = "test_profile" | |
43 instance = plugin_wid_calls.WebRTC(host_mock, profile) | |
44 | |
45 instance._set_media_types = MagicMock() | |
46 instance.start_pipeline = MagicMock() | |
47 instance.webrtc = MagicMock() | |
48 instance.webrtc.emit = MagicMock() | |
49 | |
50 instance.GstSdp_SDPMessage_new_from_text = MagicMock() | |
51 instance.GstWebRTC_WebRTCSessionDescription_new = MagicMock() | |
52 instance.Gst_Promise_new_with_change_func = MagicMock() | |
53 | |
54 return instance | |
55 | |
56 | |
57 class TestWebRtc: | |
58 def test_get_payload_types(self, webrtc): | |
59 """The method can identify the correct payload types for video and audio.""" | |
60 fake_sdpmsg = MagicMock() | |
61 fake_media = MagicMock() | |
62 fake_caps = MagicMock() | |
63 fake_structure = MagicMock() | |
64 | |
65 # This side effect will return 'fake_video_encoding' first, then | |
66 # 'fake_audio_encoding'. | |
67 fake_structure.__getitem__.side_effect = [ | |
68 "fake_video_encoding", | |
69 "fake_audio_encoding", | |
70 ] | |
71 fake_caps.get_structure.return_value = fake_structure | |
72 fake_media.get_format.side_effect = ["webrtc-datachannel", "10", "20"] | |
73 fake_media.get_caps_from_media.return_value = fake_caps | |
74 fake_sdpmsg.get_media.return_value = fake_media | |
75 fake_sdpmsg.medias_len.return_value = 1 | |
76 fake_media.formats_len.return_value = 3 | |
77 | |
78 result = webrtc.get_payload_types( | |
79 fake_sdpmsg, "fake_video_encoding", "fake_audio_encoding" | |
80 ) | |
81 expected_result = {"fake_video_encoding": 10, "fake_audio_encoding": 20} | |
82 | |
83 assert result == expected_result | |
84 | |
85 def test_on_accepted_call(self, webrtc): | |
86 """The method correctly sets the remote SDP upon acceptance of an outgoing call.""" | |
87 sdp_str = "mock_sdp_string" | |
88 profile_str = "test_profile" | |
89 | |
90 webrtc.on_accepted_call(sdp_str, profile_str) | |
91 | |
92 # remote description must be set | |
93 assert webrtc.webrtc.emit.call_count == 1 | |
94 assert webrtc.webrtc.emit.call_args[0][0] == "set-remote-description" | |
95 | |
96 @pytest.mark.asyncio | |
97 async def test_answer_call(self, webrtc, monkeypatch): | |
98 """The method correctly answers an incoming call.""" | |
99 mock_setup_call = AsyncMock() | |
100 | |
101 def mock_get_payload_types(sdpmsg, video_encoding, audio_encoding): | |
102 return {"VP8": 96, "OPUS": 97} | |
103 | |
104 monkeypatch.setattr(webrtc, "setup_call", mock_setup_call) | |
105 monkeypatch.setattr(webrtc, "get_payload_types", mock_get_payload_types) | |
106 | |
107 sdp_str = "mock_sdp_string" | |
108 profile_str = "mock_profile" | |
109 | |
110 await webrtc.answer_call(sdp_str, profile_str) | |
111 | |
112 mock_setup_call.assert_called_once_with("responder", audio_pt=97, video_pt=96) | |
113 | |
114 # remote description must be set | |
115 assert webrtc.webrtc.emit.call_count == 1 | |
116 assert webrtc.webrtc.emit.call_args[0][0] == "set-remote-description" | |
117 | |
118 def test_on_remote_decodebin_stream_video(self, webrtc, monkeypatch): | |
119 """The method correctly handles video streams from the remote decodebin.""" | |
120 mock_pipeline = MagicMock() | |
121 monkeypatch.setattr(webrtc, "pipeline", mock_pipeline) | |
122 | |
123 mock_pad = MagicMock() | |
124 mock_caps = MagicMock() | |
125 mock_structure = MagicMock() | |
126 | |
127 mock_pad.has_current_caps.return_value = True | |
128 mock_pad.get_current_caps.return_value = mock_caps | |
129 mock_caps.__len__.return_value = 1 | |
130 mock_caps.__getitem__.return_value = mock_structure | |
131 mock_structure.get_name.return_value = "video/x-h264" | |
132 # We use non-standard resolution as example to trigger the workaround | |
133 mock_structure.get_int.side_effect = lambda x: MagicMock( | |
134 value=990 if x == "width" else 557 | |
135 ) | |
136 | |
137 webrtc.on_remote_decodebin_stream(None, mock_pad) | |
138 | |
139 assert webrtc._remote_video_pad == mock_pad | |
140 mock_pipeline.add.assert_called() | |
141 mock_pipeline.set_state.assert_called() | |
142 mock_pad.link.assert_called() | |
143 | |
144 def test_on_remote_decodebin_stream_audio(self, webrtc, monkeypatch): | |
145 """The method correctly handles audio streams from the remote decodebin.""" | |
146 mock_pipeline = MagicMock() | |
147 monkeypatch.setattr(webrtc, "pipeline", mock_pipeline) | |
148 | |
149 mock_pad = MagicMock() | |
150 mock_caps = MagicMock() | |
151 mock_structure = MagicMock() | |
152 | |
153 mock_pad.has_current_caps.return_value = True | |
154 mock_pad.get_current_caps.return_value = mock_caps | |
155 mock_caps.__len__.return_value = 1 | |
156 mock_caps.__getitem__.return_value = mock_structure | |
157 mock_structure.get_name.return_value = "audio/x-raw" | |
158 | |
159 webrtc.on_remote_decodebin_stream(None, mock_pad) | |
160 | |
161 mock_pipeline.add.assert_called() | |
162 mock_pipeline.set_state.assert_called() | |
163 mock_pad.link.assert_called() | |
164 | |
165 @pytest.mark.asyncio | |
166 async def test_setup_call_correct_role(self, host, webrtc, monkeypatch): | |
167 """Roles are set in setup_call.""" | |
168 monkeypatch.setattr(Gst, "parse_launch", MagicMock()) | |
169 monkeypatch.setattr(data_format, "deserialise", MagicMock(return_value=[])) | |
170 | |
171 await webrtc.setup_call("initiator") | |
172 assert webrtc.role == "initiator" | |
173 | |
174 await webrtc.setup_call("responder") | |
175 assert webrtc.role == "responder" | |
176 | |
177 with pytest.raises(AssertionError): | |
178 await webrtc.setup_call("invalid_role") | |
179 | |
180 @pytest.mark.asyncio | |
181 async def test_setup_call_test_mode(self, host, webrtc, monkeypatch): | |
182 """Test mode use fake video and audio in setup_call.""" | |
183 monkeypatch.setattr(data_format, "deserialise", MagicMock(return_value=[])) | |
184 monkeypatch.setattr(webrtc, "test_mode", True) | |
185 await webrtc.setup_call("initiator") | |
186 assert "videotestsrc" in webrtc.gst_pipe_desc | |
187 assert "audiotestsrc" in webrtc.gst_pipe_desc | |
188 | |
189 @pytest.mark.asyncio | |
190 async def test_setup_call_normal_mode(self, host, webrtc, monkeypatch): | |
191 """Normal mode use real video and audio in setup_call.""" | |
192 monkeypatch.setattr(data_format, "deserialise", MagicMock(return_value=[])) | |
193 monkeypatch.setattr(webrtc, "test_mode", False) | |
194 await webrtc.setup_call("initiator") | |
195 assert "v4l2src" in webrtc.gst_pipe_desc | |
196 assert "pulsesrc" in webrtc.gst_pipe_desc | |
197 | |
198 @pytest.mark.asyncio | |
199 async def test_setup_call_with_stun_and_turn(self, host, webrtc, monkeypatch): | |
200 """STUN and TURN server configurations are done in setup_call.""" | |
201 mock_pipeline = MagicMock() | |
202 mock_parse_launch = MagicMock() | |
203 mock_parse_launch.return_value = mock_pipeline | |
204 monkeypatch.setattr(Gst, "parse_launch", mock_parse_launch) | |
205 | |
206 mock_pipeline.get_by_name.return_value = webrtc.webrtc | |
207 | |
208 mock_external_disco = [ | |
209 {"type": "stun", "transport": "udp", "host": "stun.host", "port": "3478"}, | |
210 { | |
211 "type": "turn", | |
212 "transport": "udp", | |
213 "host": "turn.host", | |
214 "port": "3478", | |
215 "username": "user", | |
216 "password": "pass", | |
217 }, | |
218 ] | |
219 | |
220 monkeypatch.setattr( | |
221 data_format, "deserialise", MagicMock(return_value=mock_external_disco) | |
222 ) | |
223 | |
224 mock_emit = AsyncMock() | |
225 monkeypatch.setattr(webrtc.webrtc, "emit", mock_emit) | |
226 | |
227 mock_set_property = AsyncMock() | |
228 monkeypatch.setattr(webrtc.webrtc, "set_property", mock_set_property) | |
229 | |
230 await webrtc.setup_call("initiator") | |
231 | |
232 G.host.a_bridge.external_disco_get.assert_called_once_with("", webrtc.profile) | |
233 mock_set_property.assert_any_call("stun-server", "stun://stun.host:3478") | |
234 mock_emit.assert_called_once_with( | |
235 "add-turn-server", "turn://user:pass@turn.host:3478" | |
236 ) | |
237 | |
238 @pytest.mark.asyncio | |
239 async def test_setup_call_gstreamer_pipeline_failure(self, webrtc, monkeypatch): | |
240 """Test setup_call method handling Gstreamer pipeline failure.""" | |
241 monkeypatch.setattr(Gst, "parse_launch", lambda _: None) | |
242 with pytest.raises(exceptions.InternalError): | |
243 await webrtc.setup_call("initiator") |