From 94ffa0aeba7e2dfdaf801c30c0f20df999f3391e Mon Sep 17 00:00:00 2001
From: Stanislav Levin <slev@altlinux.org>
Date: Thu, 24 Jun 2021 23:19:56 +0300
Subject: [PATCH 1/3] plugins: Don't treat keys of api as bytes

The plugin `plugins` iterates over the keys of API instance,
__iter__ of which is a generator of class.__name__ from
(Command, Object, Method, Backend, Updater). So, the allowed type
is str, not bytes.

Fixes: https://pagure.io/freeipa/issue/8898
Signed-off-by: Stanislav Levin <slev@altlinux.org>
---
 ipalib/misc.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ipalib/misc.py b/ipalib/misc.py
index a5f9b6daa1c..264ec298fa8 100644
--- a/ipalib/misc.py
+++ b/ipalib/misc.py
@@ -124,7 +124,7 @@ def execute(self, **options):
             for plugin in self.api[namespace]():
                 cls = type(plugin)
                 key = '{}.{}'.format(cls.__module__, cls.__name__)
-                result.setdefault(key, []).append(namespace.decode('utf-8'))
+                result.setdefault(key, []).append(namespace)
 
         return dict(
             result=result,

From ac4288ba35d02ab4e86bb1f193c221bb04a143c4 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <slev@altlinux.org>
Date: Thu, 24 Jun 2021 23:50:45 +0300
Subject: [PATCH 2/3] ipatests: Add tests for `plugins` plugin

Fixes: https://pagure.io/freeipa/issue/8898
Signed-off-by: Stanislav Levin <slev@altlinux.org>
---
 ipatests/test_xmlrpc/test_plugins_plugin.py | 60 +++++++++++++++++++++
 1 file changed, 60 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/test_plugins_plugin.py

diff --git a/ipatests/test_xmlrpc/test_plugins_plugin.py b/ipatests/test_xmlrpc/test_plugins_plugin.py
new file mode 100644
index 00000000000..06b42b87200
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_plugins_plugin.py
@@ -0,0 +1,60 @@
+#
+# Copyright (C) 2021  FreeIPA Contributors see COPYING for license
+#
+"""Test `plugins` plugin
+"""
+
+import pytest
+
+from ipalib import api, errors
+from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
+
+
+@pytest.mark.tier1
+class TestPlugins(XMLRPC_test):
+    """Test `plugins` plugin
+    """
+    EXPECTED_KEYS = ("result", "count", "summary")
+
+    def run_plugins(self, *args, **options):
+        cmd = api.Command.plugins
+        cmd_result = cmd(*args, **options)
+        return cmd_result
+
+    def assert_result(self, cmd_result):
+        assert tuple(cmd_result.keys()) == self.EXPECTED_KEYS
+        result = cmd_result["result"]
+        assert isinstance(result, dict)
+
+        actual_count = cmd_result["count"]
+        assert isinstance(actual_count, int)
+        assert len(result) == actual_count
+
+        expected_summaries = (
+            f"{actual_count} plugin loaded", f"{actual_count} plugins loaded"
+        )
+        assert cmd_result["summary"] in expected_summaries
+
+    @pytest.mark.parametrize(
+        "server", [True, False, None], ids=["server", "local", "local_default"]
+    )
+    def test_plugins(self, server):
+        options = {}
+        if server is not None:
+            options = {"server": server}
+        cmd_result = self.run_plugins(**options)
+        self.assert_result(cmd_result)
+        assert cmd_result["count"] >= 1
+
+    @pytest.mark.parametrize("server", [True, False], ids=["server", "local"])
+    def test_plugins_with_not_existed_argument(self, server):
+        with pytest.raises(errors.ZeroArgumentError):
+            self.run_plugins("notexistedarg", server=server)
+
+    @pytest.mark.parametrize("server", [True, False], ids=["server", "local"])
+    def test_plugins_with_not_existed_option(self, server):
+        with pytest.raises(errors.OptionError) as e:
+            self.run_plugins(
+                notexistedoption="notexistedoption", server=server
+            )
+        assert "Unknown option: notexistedoption" in str(e.value)

From 47006ae3ae51b57d2f0e715b9e9ef7e14b667679 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <slev@altlinux.org>
Date: Thu, 24 Jun 2021 23:52:09 +0300
Subject: [PATCH 3/3] ipatests: Add tests for `env` plugin

Signed-off-by: Stanislav Levin <slev@altlinux.org>
---
 ipatests/test_xmlrpc/test_env_plugin.py | 101 ++++++++++++++++++++++++
 1 file changed, 101 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/test_env_plugin.py

diff --git a/ipatests/test_xmlrpc/test_env_plugin.py b/ipatests/test_xmlrpc/test_env_plugin.py
new file mode 100644
index 00000000000..cebd7159c68
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_env_plugin.py
@@ -0,0 +1,101 @@
+#
+# Copyright (C) 2021  FreeIPA Contributors see COPYING for license
+#
+"""Test `env` plugin
+"""
+
+import pytest
+
+from ipalib import api, errors
+from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
+
+
+@pytest.mark.tier1
+class TestEnv(XMLRPC_test):
+    """Test `env` plugin
+    """
+    EXPECTED_KEYS = ("result", "count", "total", "summary")
+
+    def run_env(self, *args, **options):
+        cmd = api.Command.env
+        cmd_result = cmd(*args, **options)
+        return cmd_result
+
+    def assert_result(self, cmd_result):
+        assert tuple(cmd_result.keys()) == self.EXPECTED_KEYS
+        result = cmd_result["result"]
+        assert isinstance(result, dict)
+
+        total_count = cmd_result["total"]
+        assert isinstance(total_count, int)
+
+        actual_count = cmd_result["count"]
+        assert isinstance(actual_count, int)
+        assert actual_count <= total_count
+        assert len(result) == actual_count
+
+        if actual_count > 1:
+            assert cmd_result["summary"] == f"{actual_count} variables"
+        else:
+            assert cmd_result["summary"] is None
+
+    @pytest.mark.parametrize(
+        "server", [True, False, None], ids=["server", "local", "local_default"]
+    )
+    def test_env(self, server):
+        options = {}
+        if server is not None:
+            options = {"server": server}
+        cmd_result = self.run_env(**options)
+        self.assert_result(cmd_result)
+        actual_count = cmd_result["count"]
+        assert actual_count >= 1
+        assert cmd_result["total"] == actual_count
+        assert cmd_result["result"]["in_server"] == (server == True)
+
+    @pytest.mark.parametrize(
+        "args, kwargs",
+        [(("in_server",), {}), ((), {"variables": "in_server"})],
+        ids=["var_as_pos_arg", "var_as_known_arg"],
+    )
+    @pytest.mark.parametrize(
+        "server", [True, False], ids=["server", "local"]
+    )
+    def test_env_with_variables_one(self, args, kwargs, server):
+        kwargs["server"] = server
+        cmd_result = self.run_env(*args, **kwargs)
+        self.assert_result(cmd_result)
+        result = cmd_result["result"]
+        assert result["in_server"] == server
+        assert cmd_result["count"] == 1
+
+    @pytest.mark.parametrize(
+        "args, kwargs",
+        [
+            (("in_server", "version"), {}),
+            ((), {"variables": ("in_server", "version")}),
+        ],
+        ids=["vars_as_pos_args", "vars_as_known_args"],
+    )
+    @pytest.mark.parametrize(
+        "server", [True, False], ids=["server", "local"]
+    )
+    def test_env_with_variables_several(self, args, kwargs, server):
+        kwargs["server"] = server
+        cmd_result = self.run_env(*args, **kwargs)
+        self.assert_result(cmd_result)
+        result = cmd_result["result"]
+        assert result["in_server"] == server
+        assert cmd_result["count"] == 2
+
+    @pytest.mark.parametrize("server", [True, False], ids=["server", "local"])
+    def test_env_with_variables_missing_var(self, server):
+        cmd_result = self.run_env("notexistedvariable", server=server)
+        self.assert_result(cmd_result)
+        assert cmd_result["count"] == 0
+
+    @pytest.mark.parametrize("server", [True, False], ids=["server", "local"])
+    def test_env_with_not_existed_option(self, server):
+        with pytest.raises(errors.OptionError) as e:
+            self.run_env(notexistedoption="notexistedoption", server=server)
+        assert "Unknown option: notexistedoption" in str(e.value)
