diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
index 45581ac8..ad3b45ad 100644
--- a/tests/unit/conftest.py
+++ b/tests/unit/conftest.py
@@ -112,6 +112,7 @@ def host(bridge, storage):
def client():
client = MagicMock()
client.jid = jid.JID("test_user@test.example/123")
+ client.server_jid = jid.JID("test.example")
client.pubsub_service = jid.JID("pubsub.test.example")
client.pubsub_client = AsyncMock()
return client
diff --git a/tests/unit/test_plugin_xep_0215.py b/tests/unit/test_plugin_xep_0215.py
new file mode 100644
index 00000000..36bc1289
--- /dev/null
+++ b/tests/unit/test_plugin_xep_0215.py
@@ -0,0 +1,224 @@
+#!/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 .
+
+from twisted.internet import defer
+from pytest_twisted import ensureDeferred as ed
+from unittest.mock import MagicMock
+from sat.plugins.plugin_xep_0215 import XEP_0215
+from sat.tools import xml_tools
+from twisted.words.protocols.jabber import jid
+
+
+class TestXEP0215:
+ def test_parse_services(self, host):
+ """Services are parsed correctly"""
+ xep_0215 = XEP_0215(host)
+
+ services_elt = xml_tools.parse(
+ """
+
+
+
+
+
+ https://test.example/some_extension
+
+
+ some_value
+
+
+ 0
+
+
+ value_1
+ value_2
+
+
+
+ "
+ """
+ )
+
+ services = xep_0215.parse_services(services_elt)
+
+ expected_services = [
+ {
+ "host": "test.example",
+ "type": "stun",
+ "port": 1234,
+ },
+ {
+ "host": "example.org",
+ "type": "turn",
+ "port": 5678,
+ "restricted": True,
+ "extended": [
+ {
+ "fields": [
+ {
+ "name": "some_key",
+ "type": "text-single",
+ "value": "some_value",
+ "values": ["some_value"],
+ },
+ {
+ "name": "some_bool",
+ "type": "boolean",
+ "value": "0",
+ "values": ["0"],
+ },
+ {
+ "name": "multiple_values",
+ "type": "list-multi",
+ "value": "value_1",
+ "values": ["value_1", "value_2"],
+ },
+ ],
+ "namespace": "https://test.example/some_extension",
+ }
+ ],
+ },
+ ]
+
+ assert services == expected_services
+
+ @ed
+ async def test_get_external_services(self, host, client):
+ xep_0215 = XEP_0215(host)
+ client._xep_0215_services = {}
+
+ iq_result = MagicMock()
+ iq_result.send.return_value = defer.succeed(iq_result)
+
+ client.IQ.return_value = iq_result
+
+ iq_result_elt = xml_tools.parse(
+ """
+
+
+
+
+
+
+ """
+ )
+ iq_result.send.return_value = defer.succeed(iq_result_elt)
+
+ services = await xep_0215.get_external_services(client)
+
+ expected_services = [
+ {
+ "host": "test.example",
+ "type": "stun",
+ "port": 1234,
+ },
+ {
+ "host": "example.org",
+ "type": "turn",
+ "port": 5678,
+ "restricted": True,
+ },
+ ]
+
+ assert services == expected_services
+
+ @ed
+ async def test_request_credentials(self, host, client):
+ xep_0215 = XEP_0215(host)
+
+ iq_result = MagicMock()
+ iq_result.send.return_value = defer.succeed(iq_result)
+
+ client.IQ.return_value = iq_result
+
+ iq_result_elt = xml_tools.parse(
+ """
+
+
+
+
+
+ """
+ )
+ iq_result.send.return_value = defer.succeed(iq_result_elt)
+
+ credentials = await xep_0215.request_credentials(client, "test.example", "stun")
+
+ expected_credentials = [
+ {
+ "host": "test.example",
+ "type": "stun",
+ "port": 1234,
+ "username": "user1",
+ "password": "pass1",
+ "restricted": True,
+ }
+ ]
+
+ assert credentials == expected_credentials
+
+ def test_services_push(self, host, client):
+ xep_0215 = XEP_0215(host)
+
+ client._xep_0215_services = {
+ jid.JID("test.example"): [
+ {
+ "host": "test.example",
+ "type": "stun",
+ "port": 1234,
+ },
+ {
+ "host": "example.org",
+ "type": "turn",
+ "port": 5678,
+ },
+ ],
+ }
+
+ iq_elt = xml_tools.parse(
+ """
+
+
+
+
+
+
+
+ """
+ )
+
+ xep_0215.on_services_push(iq_elt, client)
+
+ expected_services = [
+ {
+ "host": "test.example",
+ "type": "stun",
+ "port": 1234,
+ "expires": 1681130096.0,
+ },
+ {
+ "host": "example.net",
+ "type": "stun",
+ "port": 2345,
+ },
+ ]
+
+ assert client._xep_0215_services[jid.JID("test.example")] == expected_services