1# Copyright (C) 2017-2020 by the Free Software Foundation, Inc.
2#
3# This file is part of GNU Mailman.
4#
5# GNU Mailman is free software: you can redistribute it and/or modify it under
6# the terms of the GNU General Public License as published by the Free
7# Software Foundation, either version 3 of the License, or (at your option)
8# any later version.
9#
10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13# more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# GNU Mailman.  If not, see <https://www.gnu.org/licenses/>.
17
18"""Test some additional plugin stuff."""
19
20import sys
21import unittest
22
23from contextlib import ExitStack
24from mailman.interfaces.plugin import IPlugin
25from mailman.plugins.initialize import initialize
26from mailman.plugins.testing.layer import PluginRESTLayer
27from mailman.testing.helpers import call_api
28from mailman.testing.layers import ConfigLayer
29from tempfile import TemporaryDirectory
30from types import SimpleNamespace
31from unittest.mock import patch
32from urllib.error import HTTPError
33from zope.interface import implementer
34
35
36class TestRESTPlugin(unittest.TestCase):
37    layer = PluginRESTLayer
38
39    def test_plugin_raises_exception(self):
40        with self.assertRaises(HTTPError) as cm:
41            call_api('http://localhost:9001/3.1/plugins/example/no')
42        self.assertEqual(cm.exception.code, 400)
43
44
45@implementer(IPlugin)
46class TestablePlugin:
47    def pre_hook(self):
48        pass
49
50    def post_hook(self):
51        pass
52
53    resource = None
54
55
56class TestInitializePlugins(unittest.TestCase):
57    layer = ConfigLayer
58
59    def test_duplicate_plugin_name(self):
60        with ExitStack() as resources:
61            system_path = resources.enter_context(TemporaryDirectory())
62            fake_plugin_config = {
63                'path': system_path,
64                'enabled': 'yes',
65                'class': 'ExamplePlugin',
66                }
67            log_mock = resources.enter_context(
68                patch('mailman.plugins.initialize.log'))
69            fake_mailman_config = SimpleNamespace(
70                plugin_configs=[('example', fake_plugin_config)],
71                plugins=['example'],
72                )
73            resources.enter_context(patch(
74                'mailman.plugins.initialize.config', fake_mailman_config))
75            initialize()
76            log_mock.error.assert_called_once_with(
77                'Duplicate plugin name: example')
78
79    def test_does_not_implement(self):
80        with ExitStack() as resources:
81            system_path = resources.enter_context(TemporaryDirectory())
82            fake_plugin_config = {
83                'path': system_path,
84                'enabled': 'yes',
85                'class': 'ExamplePlugin',
86                }
87            log_mock = resources.enter_context(
88                patch('mailman.plugins.initialize.log'))
89            fake_mailman_config = SimpleNamespace(
90                plugin_configs=[('example', fake_plugin_config)],
91                plugins=[],
92                )
93            resources.enter_context(patch(
94                'mailman.plugins.initialize.config', fake_mailman_config))
95            resources.enter_context(patch(
96                'mailman.plugins.initialize.call_name',
97                # object() does not implement IPlugin.
98                return_value=object()))
99            initialize()
100            log_mock.error.assert_called_once_with(
101                'Plugin class does not implement IPlugin: ExamplePlugin')
102            self.assertNotIn(system_path, sys.path)
103
104    def test_adds_plugins_to_config(self):
105        with ExitStack() as resources:
106            system_path = resources.enter_context(TemporaryDirectory())
107            fake_plugin_config = {
108                'path': system_path,
109                'enabled': 'yes',
110                'class': 'ExamplePlugin',
111                }
112            fake_mailman_config = SimpleNamespace(
113                plugin_configs=[('example', fake_plugin_config)],
114                plugins={},
115                )
116            resources.enter_context(patch(
117                'mailman.plugins.initialize.config', fake_mailman_config))
118            testable_plugin = TestablePlugin()
119            resources.enter_context(patch(
120                'mailman.plugins.initialize.call_name',
121                # object() does not implement IPlugin.
122                return_value=testable_plugin))
123            initialize()
124            self.assertIn('example', fake_mailman_config.plugins)
125            self.assertEqual(
126                fake_mailman_config.plugins['example'],
127                testable_plugin)
128
129    def test_not_enabled(self):
130        with ExitStack() as resources:
131            fake_plugin_config = {
132                'path': '/does/not/exist',
133                'enabled': 'no',
134                'class': 'ExamplePlugin',
135                }
136            log_mock = resources.enter_context(
137                patch('mailman.plugins.initialize.log'))
138            fake_mailman_config = SimpleNamespace(
139                plugin_configs=[('example', fake_plugin_config)],
140                plugins={},
141                )
142            resources.enter_context(patch(
143                'mailman.plugins.initialize.config', fake_mailman_config))
144            initialize()
145            log_mock.info.assert_called_once_with(
146                'Plugin not enabled, or empty class path: example')
147