File: disco_test.py

package info (click to toggle)
python-certbot 4.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,688 kB
  • sloc: python: 21,764; makefile: 182; sh: 108
file content (305 lines) | stat: -rw-r--r-- 11,418 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
"""Tests for certbot._internal.plugins.disco."""
import functools
import string
import sys
from typing import List
import unittest
from unittest import mock

import pytest

from certbot import errors
from certbot import interfaces
from certbot._internal.plugins import null
from certbot._internal.plugins import standalone
from certbot._internal.plugins import webroot

if sys.version_info >= (3, 10):  # pragma: no cover
    import importlib.metadata as importlib_metadata
else:
    import importlib_metadata


class _EntryPointLoadFail(importlib_metadata.EntryPoint):
    def load(self):
        raise RuntimeError("Loading failure")


EP_SA = importlib_metadata.EntryPoint(
    name="sa",
    value="certbot._internal.plugins.standalone:Authenticator",
    group="certbot.plugins")

EP_WR = importlib_metadata.EntryPoint(
    name="wr",
    value="certbot._internal.plugins.webroot:Authenticator",
    group="certbot.plugins")

EP_SA_LOADFAIL = _EntryPointLoadFail(
    name="sa",
    value="certbot._internal.plugins.standalone:Authenticator",
    group="certbot.plugins")


class PluginEntryPointTest(unittest.TestCase):
    """Tests for certbot._internal.plugins.disco.PluginEntryPoint."""

    def setUp(self):
        self.ep1 = importlib_metadata.EntryPoint(
            name="ep1",
            value="p1.ep1:Authenticator",
            group="certbot.plugins")
        self.ep1prim = importlib_metadata.EntryPoint(
            name="ep1",
            value="p2.pe2:Authenticator",
            group="certbot.plugins")
        # nested
        self.ep2 = importlib_metadata.EntryPoint(
            name="ep2",
            value="p2.foo.ep2:Authenticator",
            group="certbot.plugins")
        # project name != top-level package name
        self.ep3 = importlib_metadata.EntryPoint(
            name="ep3",
            value="a.ep3:Authenticator",
            group="certbot.plugins")

        from certbot._internal.plugins.disco import PluginEntryPoint
        self.plugin_ep = PluginEntryPoint(EP_SA)

    def test_entry_point_to_plugin_name_not_prefixed(self):
        from certbot._internal.plugins.disco import PluginEntryPoint

        names = {
            self.ep1: "ep1",
            self.ep1prim: "ep1",
            self.ep2: "ep2",
            self.ep3: "ep3",
            EP_SA: "sa",
        }

        for entry_point, name in names.items():
            assert name == PluginEntryPoint.entry_point_to_plugin_name(entry_point)

    def test_description(self):
        assert "server locally" in self.plugin_ep.description

    def test_description_with_name(self):
        self.plugin_ep.plugin_cls = mock.MagicMock(description="Desc")
        assert "Desc (sa)" == self.plugin_ep.description_with_name

    def test_long_description(self):
        self.plugin_ep.plugin_cls = mock.MagicMock(
            long_description="Long desc")
        assert "Long desc" == self.plugin_ep.long_description

    def test_long_description_nonexistent(self):
        self.plugin_ep.plugin_cls = mock.MagicMock(
            description="Long desc not found", spec=["description"])
        assert "Long desc not found" == self.plugin_ep.long_description

    def test_ifaces(self):
        assert self.plugin_ep.ifaces((interfaces.Authenticator,))
        assert not self.plugin_ep.ifaces((interfaces.Installer,))
        assert not self.plugin_ep.ifaces((
            interfaces.Installer, interfaces.Authenticator))

    def test__init__(self):
        assert self.plugin_ep.initialized is False
        assert self.plugin_ep.prepared is False
        assert self.plugin_ep.misconfigured is False
        assert self.plugin_ep.available is False
        assert self.plugin_ep.problem is None
        assert self.plugin_ep.entry_point is EP_SA
        assert "sa" == self.plugin_ep.name

        assert self.plugin_ep.plugin_cls is standalone.Authenticator

    def test_init(self):
        config = mock.MagicMock()
        plugin = self.plugin_ep.init(config=config)
        assert self.plugin_ep.initialized is True
        assert plugin.config is config
        # memoize!
        assert self.plugin_ep.init() is plugin
        assert plugin.config is config
        # try to give different config
        assert self.plugin_ep.init(123) is plugin
        assert plugin.config is config

        assert self.plugin_ep.prepared is False
        assert self.plugin_ep.misconfigured is False
        assert self.plugin_ep.available is False

    def test_prepare(self):
        config = mock.MagicMock()
        self.plugin_ep.init(config=config)
        self.plugin_ep.prepare()
        assert self.plugin_ep.prepared
        assert self.plugin_ep.misconfigured is False

        # output doesn't matter that much, just test if it runs
        str(self.plugin_ep)

    def test_prepare_misconfigured(self):
        plugin = mock.MagicMock()
        plugin.prepare.side_effect = errors.MisconfigurationError
        # pylint: disable=protected-access
        self.plugin_ep._initialized = plugin
        assert isinstance(self.plugin_ep.prepare(), errors.MisconfigurationError)
        assert self.plugin_ep.prepared
        assert self.plugin_ep.misconfigured
        assert isinstance(self.plugin_ep.problem, errors.MisconfigurationError)
        assert self.plugin_ep.available

    def test_prepare_no_installation(self):
        plugin = mock.MagicMock()
        plugin.prepare.side_effect = errors.NoInstallationError
        # pylint: disable=protected-access
        self.plugin_ep._initialized = plugin
        assert isinstance(self.plugin_ep.prepare(), errors.NoInstallationError)
        assert self.plugin_ep.prepared is True
        assert self.plugin_ep.misconfigured is False
        assert self.plugin_ep.available is False

    def test_prepare_generic_plugin_error(self):
        plugin = mock.MagicMock()
        plugin.prepare.side_effect = errors.PluginError
        # pylint: disable=protected-access
        self.plugin_ep._initialized = plugin
        assert isinstance(self.plugin_ep.prepare(), errors.PluginError)
        assert self.plugin_ep.prepared
        assert self.plugin_ep.misconfigured is False
        assert self.plugin_ep.available is False

    def test_str(self):
        output = str(self.plugin_ep)
        assert "Authenticator" in output
        assert "Installer" not in output
        assert "Plugin" in output

    def test_repr(self):
        assert "PluginEntryPoint#sa" == repr(self.plugin_ep)


class PluginsRegistryTest(unittest.TestCase):
    """Tests for certbot._internal.plugins.disco.PluginsRegistry."""

    @classmethod
    def _create_new_registry(cls, plugins):
        from certbot._internal.plugins.disco import PluginsRegistry
        return PluginsRegistry(plugins)

    def setUp(self):
        self.plugin_ep = mock.MagicMock()
        self.plugin_ep.name = "mock"
        self.plugin_ep.__hash__.side_effect = TypeError
        self.plugins = {self.plugin_ep.name: self.plugin_ep}
        self.reg = self._create_new_registry(self.plugins)
        self.ep1 = importlib_metadata.EntryPoint(
            name="ep1",
            value="p1.ep1",
            group="certbot.plugins")

    def test_find_all(self):
        from certbot._internal.plugins.disco import PluginsRegistry
        with mock.patch("certbot._internal.plugins.disco.importlib_metadata") as mock_meta:
            mock_meta.entry_points.side_effect = [
                [EP_SA], [EP_WR, self.ep1],
            ]
            with mock.patch.object(importlib_metadata.EntryPoint, 'load') as mock_load:
                mock_load.side_effect = [
                    standalone.Authenticator, webroot.Authenticator,
                    null.Installer, null.Installer]
                plugins = PluginsRegistry.find_all()
        assert plugins["sa"].plugin_cls is standalone.Authenticator
        assert plugins["sa"].entry_point is EP_SA
        assert plugins["wr"].plugin_cls is webroot.Authenticator
        assert plugins["wr"].entry_point is EP_WR
        assert plugins["ep1"].plugin_cls is null.Installer
        assert plugins["ep1"].entry_point is self.ep1
        assert "p1:ep1" not in plugins

    def test_find_all_error_message(self):
        from certbot._internal.plugins.disco import PluginsRegistry
        with mock.patch("certbot._internal.plugins.disco.importlib_metadata") as mock_meta:
            #EP_SA.load = None  # This triggers a TypeError when the entrypoint loads
            mock_meta.entry_points.side_effect = [
                [EP_SA_LOADFAIL], [EP_WR, self.ep1],
            ]
            with self.assertRaises(errors.PluginError) as cm:
                PluginsRegistry.find_all()
            assert "standalone' plugin errored" in str(cm.exception)

    def test_getitem(self):
        assert self.plugin_ep == self.reg["mock"]

    def test_iter(self):
        assert ["mock"] == list(self.reg)

    def test_len(self):
        assert 0 == len(self._create_new_registry({}))
        assert 1 == len(self.reg)

    def test_init(self):
        self.plugin_ep.init.return_value = "baz"
        assert ["baz"] == self.reg.init("bar")
        self.plugin_ep.init.assert_called_once_with("bar")

    def test_filter(self):
        assert self.plugins == \
            self.reg.filter(lambda p_ep: p_ep.name.startswith("m"))
        assert {} == self.reg.filter(lambda p_ep: p_ep.name.startswith("b"))

    def test_ifaces(self):
        self.plugin_ep.ifaces.return_value = True
        # pylint: disable=protected-access
        assert self.plugins == self.reg.ifaces()._plugins
        self.plugin_ep.ifaces.return_value = False
        assert {} == self.reg.ifaces()._plugins

    def test_prepare(self):
        self.plugin_ep.prepare.return_value = "baz"
        assert ["baz"] == self.reg.prepare()
        self.plugin_ep.prepare.assert_called_once_with()

    def test_prepare_order(self):
        order: List[str] = []
        plugins = {
            c: mock.MagicMock(prepare=functools.partial(order.append, c))
            for c in string.ascii_letters
        }
        reg = self._create_new_registry(plugins)
        reg.prepare()
        # order of prepare calls must be sorted to prevent deadlock
        # caused by plugins acquiring locks during prepare
        assert order == sorted(string.ascii_letters)

    def test_available(self):
        self.plugin_ep.available = True
        # pylint: disable=protected-access
        assert self.plugins == self.reg.available()._plugins
        self.plugin_ep.available = False
        assert {} == self.reg.available()._plugins

    def test_find_init(self):
        assert self.reg.find_init(mock.Mock()) is None
        self.plugin_ep.initialized = True
        assert self.reg.find_init(self.plugin_ep.init()) is self.plugin_ep

    def test_repr(self):
        self.plugin_ep.__repr__ = lambda _: "PluginEntryPoint#mock"
        assert "PluginsRegistry(PluginEntryPoint#mock)" == \
                         repr(self.reg)

    def test_str(self):
        assert "No plugins" == str(self._create_new_registry({}))
        self.plugin_ep.__str__ = lambda _: "Mock"
        assert "Mock" == str(self.reg)
        plugins = {self.plugin_ep.name: self.plugin_ep, "foo": "Bar"}
        reg = self._create_new_registry(plugins)
        assert "Bar\n\nMock" == str(reg)


if __name__ == "__main__":
    sys.exit(pytest.main(sys.argv[1:] + [__file__]))  # pragma: no cover