diff --git a/tests/unit/test_plugin_xep_0338.py b/tests/unit/test_plugin_xep_0338.py
new file mode 100644
index 00000000..babf21b1
--- /dev/null
+++ b/tests/unit/test_plugin_xep_0338.py
@@ -0,0 +1,139 @@
+#!/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.words.xish import domish
+
+from sat.plugins.plugin_xep_0338 import NS_JINGLE_GROUPING, XEP_0338
+from sat.tools.xml_tools import parse
+
+
+class TestXEP0338:
+ def test_parse_sdp(self, host):
+ """'group' attribute in SDP is correctly parsed"""
+ xep_0338 = XEP_0338(host)
+
+ call_data = {}
+ metadata = {}
+ media_type = "video"
+ application_data = {}
+ transport_data = {}
+
+ # SDP: a=group:BUNDLE audio video
+ attribute = "group"
+ parts = ["BUNDLE", "audio", "video"]
+
+ xep_0338._parse_sdp_a_trigger(
+ attribute,
+ parts,
+ call_data,
+ metadata,
+ media_type,
+ application_data,
+ transport_data,
+ )
+
+ assert metadata == {"group": {"BUNDLE": ["audio", "video"]}}
+
+ def test_generate_sdp(self, host):
+ """'group' attribute in SDP is correctly generated"""
+ xep_0338 = XEP_0338(host)
+
+ session = {"metadata": {"group": {"BUNDLE": ["audio", "video"]}}}
+ sdp_lines = []
+ local = True
+
+ xep_0338._generate_sdp_session_trigger(session, local, sdp_lines)
+
+ assert sdp_lines == ["a=group:BUNDLE audio video"]
+
+ def test_group_building(self, host, client):
+ """ element are built from session in session init trigger"""
+ xep_0338 = XEP_0338(host)
+
+ session = {
+ "jingle_elt": domish.Element((None, "jingle")),
+ "metadata": {"group": {"BUNDLE": ["audio", "video"]}},
+ }
+ content_name = "audio"
+ media = "audio"
+ media_data = {}
+ desc_elt = domish.Element((None, "description"))
+
+ xep_0338._jingle_session_init_trigger(
+ client, session, content_name, media, media_data, desc_elt
+ )
+
+ group_elts = list(session["jingle_elt"].elements(NS_JINGLE_GROUPING, "group"))
+ assert len(group_elts) == 1
+ group_elt = group_elts[0]
+ assert group_elt["semantics"] == "BUNDLE"
+ content_names = [
+ content_elt["name"]
+ for content_elt in group_elt.elements(NS_JINGLE_GROUPING, "content")
+ ]
+ assert content_names == ["audio", "video"]
+
+ def test_group_parsing(self, host, client):
+ """ elements are correctly parsed in jingle_handler trigger"""
+ xep_0338 = XEP_0338(host)
+
+ action = xep_0338._j.A_SESSION_INITIATE
+ session = {
+ "contents": ["audio", "video"],
+ "metadata": {},
+ }
+
+ raw_xml = """
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ session["jingle_elt"] = parse(raw_xml)
+
+ for content_elt in session["jingle_elt"].elements("urn:xmpp:jingle:1", "content"):
+ content_name = content_elt["name"]
+ desc_elt = next(
+ content_elt.elements("urn:xmpp:jingle:apps:rtp:1", "description")
+ )
+
+ xep_0338._jingle_handler_trigger(
+ client, action, session, content_name, desc_elt
+ )
+
+ group_elts = list(session["jingle_elt"].elements(NS_JINGLE_GROUPING, "group"))
+ assert len(group_elts) == 1
+ group_elt = group_elts[0]
+ assert group_elt["semantics"] == "BUNDLE"
+ content_names = [
+ content_elt["name"]
+ for content_elt in group_elt.elements(NS_JINGLE_GROUPING, "content")
+ ]
+ assert content_names == ["audio", "video"]