Fix #5391 - Alembic migrations would only work for SQLite
[larjonas-mediagoblin.git] / mediagoblin / tests / test_pluginapi.py
blob2fd6df398cdc8d2d0898321538dec7374ed3efb2
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 import os
18 import json
19 import sys
21 from configobj import ConfigObj
22 import pytest
23 import pkg_resources
24 from validate import VdtTypeError
26 from mediagoblin import mg_globals
27 from mediagoblin.init.plugins import setup_plugins
28 from mediagoblin.init.config import read_mediagoblin_config
29 from mediagoblin.gmg_commands.assetlink import link_plugin_assets
30 from mediagoblin.tools import pluginapi
31 from mediagoblin.tests.tools import get_app
32 from mediagoblin.tools.common import CollectingPrinter
35 def with_cleanup(*modules_to_delete):
36 def _with_cleanup(fun):
37 """Wrapper that saves and restores mg_globals"""
38 def _with_cleanup_inner(*args, **kwargs):
39 old_app_config = mg_globals.app_config
40 old_global_config = mg_globals.global_config
41 # Need to delete icky modules before and after so as to make
42 # sure things work correctly.
43 for module in modules_to_delete:
44 try:
45 del sys.modules[module]
46 except KeyError:
47 pass
48 # The plugin cache gets populated as a side-effect of
49 # importing, so it's best to clear it before and after a test.
50 pman = pluginapi.PluginManager()
51 pman.clear()
52 try:
53 return fun(*args, **kwargs)
54 finally:
55 mg_globals.app_config = old_app_config
56 mg_globals.global_config = old_global_config
57 # Need to delete icky modules before and after so as to make
58 # sure things work correctly.
59 for module in modules_to_delete:
60 try:
61 del sys.modules[module]
62 except KeyError:
63 pass
64 pman.clear()
66 _with_cleanup_inner.__name__ = fun.__name__
67 return _with_cleanup_inner
68 return _with_cleanup
71 def build_config(sections):
72 """Builds a ConfigObj object with specified data
74 :arg sections: list of ``(section_name, section_data,
75 subsection_list)`` tuples where section_data is a dict and
76 subsection_list is a list of ``(section_name, section_data,
77 subsection_list)``, ...
79 For example:
81 >>> build_config([
82 ... ('mediagoblin', {'key1': 'val1'}, []),
83 ... ('section2', {}, [
84 ... ('subsection1', {}, [])
85 ... ])
86 ... ])
87 """
88 cfg = ConfigObj()
89 cfg.filename = 'foo'
90 def _iter_section(cfg, section_list):
91 for section_name, data, subsection_list in section_list:
92 cfg[section_name] = data
93 _iter_section(cfg[section_name], subsection_list)
95 _iter_section(cfg, sections)
96 return cfg
99 @with_cleanup()
100 def test_no_plugins():
101 """Run setup_plugins with no plugins in config"""
102 cfg = build_config([('mediagoblin', {}, [])])
103 mg_globals.app_config = cfg['mediagoblin']
104 mg_globals.global_config = cfg
106 pman = pluginapi.PluginManager()
107 setup_plugins()
109 # Make sure we didn't load anything.
110 assert len(pman.plugins) == 0
113 @with_cleanup('mediagoblin.plugins.sampleplugin')
114 def test_one_plugin():
115 """Run setup_plugins with a single working plugin"""
116 cfg = build_config([
117 ('mediagoblin', {}, []),
118 ('plugins', {}, [
119 ('mediagoblin.plugins.sampleplugin', {}, [])
123 mg_globals.app_config = cfg['mediagoblin']
124 mg_globals.global_config = cfg
126 pman = pluginapi.PluginManager()
127 setup_plugins()
129 # Make sure we only found one plugin
130 assert len(pman.plugins) == 1
131 # Make sure the plugin is the one we think it is.
132 assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin'
133 # Make sure there was one hook registered
134 assert len(pman.hooks) == 1
135 # Make sure _setup_plugin_called was called once
136 import mediagoblin.plugins.sampleplugin
137 assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1
140 @with_cleanup('mediagoblin.plugins.sampleplugin')
141 def test_same_plugin_twice():
142 """Run setup_plugins with a single working plugin twice"""
143 cfg = build_config([
144 ('mediagoblin', {}, []),
145 ('plugins', {}, [
146 ('mediagoblin.plugins.sampleplugin', {}, []),
147 ('mediagoblin.plugins.sampleplugin', {}, []),
151 mg_globals.app_config = cfg['mediagoblin']
152 mg_globals.global_config = cfg
154 pman = pluginapi.PluginManager()
155 setup_plugins()
157 # Make sure we only found one plugin
158 assert len(pman.plugins) == 1
159 # Make sure the plugin is the one we think it is.
160 assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin'
161 # Make sure there was one hook registered
162 assert len(pman.hooks) == 1
163 # Make sure _setup_plugin_called was called once
164 import mediagoblin.plugins.sampleplugin
165 assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1
168 @with_cleanup()
169 def test_disabled_plugin():
170 """Run setup_plugins with a single working plugin twice"""
171 cfg = build_config([
172 ('mediagoblin', {}, []),
173 ('plugins', {}, [
174 ('-mediagoblin.plugins.sampleplugin', {}, []),
178 mg_globals.app_config = cfg['mediagoblin']
179 mg_globals.global_config = cfg
181 pman = pluginapi.PluginManager()
182 setup_plugins()
184 # Make sure we didn't load the plugin
185 assert len(pman.plugins) == 0
188 CONFIG_ALL_CALLABLES = [
189 ('mediagoblin', {}, []),
190 ('plugins', {}, [
191 ('mediagoblin.tests.testplugins.callables1', {}, []),
192 ('mediagoblin.tests.testplugins.callables2', {}, []),
193 ('mediagoblin.tests.testplugins.callables3', {}, []),
198 @with_cleanup()
199 def test_hook_handle():
201 Test the hook_handle method
203 cfg = build_config(CONFIG_ALL_CALLABLES)
205 mg_globals.app_config = cfg['mediagoblin']
206 mg_globals.global_config = cfg
208 setup_plugins()
210 # Just one hook provided
211 call_log = []
212 assert pluginapi.hook_handle(
213 "just_one", call_log) == "Called just once"
214 assert call_log == ["expect this one call"]
216 # Nothing provided and unhandled not okay
217 call_log = []
218 pluginapi.hook_handle(
219 "nothing_handling", call_log) == None
220 assert call_log == []
222 # Nothing provided and unhandled okay
223 call_log = []
224 assert pluginapi.hook_handle(
225 "nothing_handling", call_log, unhandled_okay=True) is None
226 assert call_log == []
228 # Multiple provided, go with the first!
229 call_log = []
230 assert pluginapi.hook_handle(
231 "multi_handle", call_log) == "the first returns"
232 assert call_log == ["Hi, I'm the first"]
234 # Multiple provided, one has CantHandleIt
235 call_log = []
236 assert pluginapi.hook_handle(
237 "multi_handle_with_canthandle",
238 call_log) == "the second returns"
239 assert call_log == ["Hi, I'm the second"]
242 @with_cleanup()
243 def test_hook_runall():
245 Test the hook_runall method
247 cfg = build_config(CONFIG_ALL_CALLABLES)
249 mg_globals.app_config = cfg['mediagoblin']
250 mg_globals.global_config = cfg
252 setup_plugins()
254 # Just one hook, check results
255 call_log = []
256 assert pluginapi.hook_runall(
257 "just_one", call_log) == ["Called just once"]
258 assert call_log == ["expect this one call"]
260 # None provided, check results
261 call_log = []
262 assert pluginapi.hook_runall(
263 "nothing_handling", call_log) == []
264 assert call_log == []
266 # Multiple provided, check results
267 call_log = []
268 assert pluginapi.hook_runall(
269 "multi_handle", call_log) == [
270 "the first returns",
271 "the second returns",
272 "the third returns",
274 assert call_log == [
275 "Hi, I'm the first",
276 "Hi, I'm the second",
277 "Hi, I'm the third"]
279 # Multiple provided, one has CantHandleIt, check results
280 call_log = []
281 assert pluginapi.hook_runall(
282 "multi_handle_with_canthandle", call_log) == [
283 "the second returns",
284 "the third returns",
286 assert call_log == [
287 "Hi, I'm the second",
288 "Hi, I'm the third"]
291 @with_cleanup()
292 def test_hook_transform():
294 Test the hook_transform method
296 cfg = build_config(CONFIG_ALL_CALLABLES)
298 mg_globals.app_config = cfg['mediagoblin']
299 mg_globals.global_config = cfg
301 setup_plugins()
303 assert pluginapi.hook_transform(
304 "expand_tuple", (-1, 0)) == (-1, 0, 1, 2, 3)
307 def test_plugin_config():
309 Make sure plugins can set up their own config
311 config, validation_result = read_mediagoblin_config(
312 pkg_resources.resource_filename(
313 'mediagoblin.tests', 'appconfig_plugin_specs.ini'))
315 pluginspec_section = config['plugins'][
316 'mediagoblin.tests.testplugins.pluginspec']
317 assert pluginspec_section['some_string'] == 'not blork'
318 assert pluginspec_section['dont_change_me'] == 'still the default'
320 # Make sure validation works... this should be an error
321 assert isinstance(
322 validation_result[
323 'plugins'][
324 'mediagoblin.tests.testplugins.pluginspec'][
325 'some_int'],
326 VdtTypeError)
328 # the callables thing shouldn't really have anything though.
329 assert len(config['plugins'][
330 'mediagoblin.tests.testplugins.callables1']) == 0
333 @pytest.fixture()
334 def context_modified_app(request):
336 Get a MediaGoblin app fixture using appconfig_context_modified.ini
338 return get_app(
339 request,
340 mgoblin_config=pkg_resources.resource_filename(
341 'mediagoblin.tests', 'appconfig_context_modified.ini'))
344 def test_modify_context(context_modified_app):
346 Test that we can modify both the view/template specific and
347 global contexts for templates.
349 # Specific thing passed into a page
350 result = context_modified_app.get("/modify_context/specific/")
351 assert result.body.strip() == b"""Specific page!
353 specific thing: in yer specificpage
354 global thing: globally appended!
355 something: orother
356 doubleme: happyhappy"""
358 # General test, should have global context variable only
359 result = context_modified_app.get("/modify_context/")
360 assert result.body.strip() == b"""General page!
362 global thing: globally appended!
363 lol: cats
364 doubleme: joyjoy"""
367 @pytest.fixture()
368 def static_plugin_app(request):
370 Get a MediaGoblin app fixture using appconfig_static_plugin.ini
372 return get_app(
373 request,
374 mgoblin_config=pkg_resources.resource_filename(
375 'mediagoblin.tests', 'appconfig_static_plugin.ini'))
378 def test_plugin_assetlink(static_plugin_app):
380 Test that the assetlink command works correctly
382 linked_assets_dir = mg_globals.app_config['plugin_linked_assets_dir']
383 plugin_link_dir = os.path.join(
384 linked_assets_dir.rstrip(os.path.sep),
385 'staticstuff')
387 plugin_statics = pluginapi.hook_runall("static_setup")
388 assert len(plugin_statics) == 1
389 plugin_static = plugin_statics[0]
391 def run_assetlink():
392 printer = CollectingPrinter()
394 link_plugin_assets(
395 plugin_static, linked_assets_dir, printer)
397 return printer
399 # it shouldn't exist yet
400 assert not os.path.lexists(plugin_link_dir)
402 # link dir doesn't exist, link it
403 result = run_assetlink().collection[0]
404 assert result == \
405 'Linked asset directory for plugin "staticstuff":\n %s\nto:\n %s\n' % (
406 plugin_static.file_path.rstrip(os.path.sep),
407 plugin_link_dir)
408 assert os.path.lexists(plugin_link_dir)
409 assert os.path.islink(plugin_link_dir)
410 assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
412 # link dir exists, leave it alone
413 # (and it should exist still since we just ran it..)
414 result = run_assetlink().collection[0]
415 assert result == 'Skipping "staticstuff"; already set up.\n'
416 assert os.path.lexists(plugin_link_dir)
417 assert os.path.islink(plugin_link_dir)
418 assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
420 # link dir exists, is a symlink to somewhere else (re-link)
421 junk_file_path = os.path.join(
422 linked_assets_dir.rstrip(os.path.sep),
423 'junk.txt')
424 with open(junk_file_path, 'w') as junk_file:
425 junk_file.write('barf')
427 os.unlink(plugin_link_dir)
428 os.symlink(junk_file_path, plugin_link_dir)
430 result = run_assetlink().combined_string
431 assert result == """Old link found for "staticstuff"; removing.
432 Linked asset directory for plugin "staticstuff":
436 """ % (plugin_static.file_path.rstrip(os.path.sep), plugin_link_dir)
437 assert os.path.lexists(plugin_link_dir)
438 assert os.path.islink(plugin_link_dir)
439 assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
441 # link dir exists, but is a non-symlink
442 os.unlink(plugin_link_dir)
443 with open(plugin_link_dir, 'w') as clobber_file:
444 clobber_file.write('clobbered!')
446 result = run_assetlink().collection[0]
447 assert result == 'Could not link "staticstuff": %s exists and is not a symlink\n' % (
448 plugin_link_dir)
450 with open(plugin_link_dir, 'r') as clobber_file:
451 assert clobber_file.read() == 'clobbered!'
454 def test_plugin_staticdirect(static_plugin_app):
456 Test that the staticdirect utilities pull up the right things
458 result = json.loads(
459 static_plugin_app.get('/staticstuff/').body.decode())
461 assert len(result) == 2
463 assert result['mgoblin_bunny_pic'] == '/test_static/images/bunny_pic.png'
464 assert result['plugin_bunny_css'] == \
465 '/plugin_static/staticstuff/css/bunnify.css'