From 53a62ab44e26ea4940f7555dbaf1faeaadf32c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 22 Oct 2015 11:07:55 +0200 Subject: [PATCH] Added Unit Tests for BlueprintPlugin mixin --- src/octoprint/plugin/types.py | 16 +++ tests/plugin/test_types_blueprint.py | 139 +++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 tests/plugin/test_types_blueprint.py diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py index bb193c9f..7319032e 100644 --- a/src/octoprint/plugin/types.py +++ b/src/octoprint/plugin/types.py @@ -1082,18 +1082,34 @@ class BlueprintPlugin(OctoPrintPlugin, RestartNeedingPlugin): :return: the blueprint ready to be registered with Flask """ + if hasattr(self, "_blueprint"): + # if we already constructed the blueprint and hence have it cached, + # return that instance - we don't want to instance it multiple times + return self._blueprint + import flask kwargs = self.get_blueprint_kwargs() blueprint = flask.Blueprint("plugin." + self._identifier, self._identifier, **kwargs) + + # we now iterate over all members of ourselves and look if we find an attribute + # that has data originating from one of our decorators - we ignore anything + # starting with a _ to only handle public stuff for member in [member for member in dir(self) if not member.startswith("_")]: f = getattr(self, member) + if hasattr(f, "_blueprint_rules") and member in f._blueprint_rules: + # this attribute was annotated with our @route decorator for blueprint_rule in f._blueprint_rules[member]: rule, options = blueprint_rule blueprint.add_url_rule(rule, options.pop("endpoint", f.__name__), view_func=f, **options) + if hasattr(f, "_blueprint_error_handler") and member in f._blueprint_error_handler: + # this attribute was annotated with our @error_handler decorator for code_or_exception in f._blueprint_error_handler[member]: blueprint.errorhandler(code_or_exception)(f) + + # cache and return the blueprint object + self._blueprint = blueprint return blueprint def get_blueprint_kwargs(self): diff --git a/tests/plugin/test_types_blueprint.py b/tests/plugin/test_types_blueprint.py new file mode 100644 index 00000000..17df406c --- /dev/null +++ b/tests/plugin/test_types_blueprint.py @@ -0,0 +1,139 @@ +import unittest +import mock + +import octoprint.plugin + +class BlueprintPluginTest(unittest.TestCase): + + def setUp(self): + self.basefolder = "/some/funny/basefolder" + + self.plugin = octoprint.plugin.BlueprintPlugin() + self.plugin._basefolder = self.basefolder + + class MyAssetPlugin(octoprint.plugin.BlueprintPlugin, octoprint.plugin.AssetPlugin): + def get_asset_folder(self): + return "/some/asset/folder" + + class MyTemplatePlugin(octoprint.plugin.BlueprintPlugin, octoprint.plugin.TemplatePlugin): + def get_template_folder(self): + return "/some/template/folder" + + self.assetplugin = MyAssetPlugin() + self.assetplugin._basefolder = self.basefolder + + self.templateplugin = MyTemplatePlugin() + self.templateplugin._basefolder = self.basefolder + + def test_route(self): + + def test_method(): + pass + + octoprint.plugin.BlueprintPlugin.route("/test/method", methods=["GET"])(test_method) + octoprint.plugin.BlueprintPlugin.route("/test/method/{foo}", methods=["PUT"])(test_method) + + self.assertTrue(hasattr(test_method, "_blueprint_rules")) + self.assertTrue("test_method" in test_method._blueprint_rules) + self.assertTrue(len(test_method._blueprint_rules["test_method"]) == 2) + self.assertListEqual(test_method._blueprint_rules["test_method"], [ + ("/test/method", dict(methods=["GET"])), + ("/test/method/{foo}", dict(methods=["PUT"])) + ]) + + def test_errorhandler(self): + + def test_method(): + pass + + octoprint.plugin.BlueprintPlugin.errorhandler(404)(test_method) + + self.assertTrue(hasattr(test_method, "_blueprint_error_handler")) + self.assertTrue("test_method" in test_method._blueprint_error_handler) + self.assertTrue(len(test_method._blueprint_error_handler["test_method"]) == 1) + self.assertListEqual(test_method._blueprint_error_handler["test_method"], [ + 404 + ]) + + def test_get_blueprint_kwargs(self): + import os + expected = dict( + static_folder=os.path.join(self.basefolder, "static"), + template_folder=os.path.join(self.basefolder, "templates") + ) + + result = self.plugin.get_blueprint_kwargs() + + self.assertEquals(result, expected) + + def test_get_blueprint_kwargs_assetplugin(self): + import os + expected = dict( + static_folder=self.assetplugin.get_asset_folder(), + template_folder=os.path.join(self.basefolder, "templates") + ) + + result = self.assetplugin.get_blueprint_kwargs() + + self.assertEquals(result, expected) + + def test_get_blueprint_kwargs_templateplugin(self): + import os + expected = dict( + static_folder=os.path.join(self.basefolder, "static"), + template_folder=self.templateplugin.get_template_folder() + ) + + result = self.templateplugin.get_blueprint_kwargs() + + self.assertEquals(result, expected) + + def test_get_blueprint(self): + import os + expected_kwargs = dict( + static_folder=os.path.join(self.basefolder, "static"), + template_folder=os.path.join(self.basefolder, "templates") + ) + + class MyPlugin(octoprint.plugin.BlueprintPlugin): + + @octoprint.plugin.BlueprintPlugin.route("/some/path", methods=["GET"]) + def route_method(self): + pass + + @octoprint.plugin.BlueprintPlugin.errorhandler(404) + def errorhandler_method(self): + pass + + @octoprint.plugin.BlueprintPlugin.route("/hidden/path", methods=["GET"]) + def _hidden_method(self): + pass + + plugin = MyPlugin() + plugin._basefolder = self.basefolder + plugin._identifier = "myplugin" + + with mock.patch("flask.Blueprint") as MockBlueprint: + blueprint = mock.MagicMock() + MockBlueprint.return_value = blueprint + + errorhandler = mock.MagicMock() + blueprint.errorhandler.return_value = errorhandler + + result = plugin.get_blueprint() + + self.assertEquals(result, blueprint) + + MockBlueprint.assert_called_once_with("plugin.myplugin", "myplugin", **expected_kwargs) + blueprint.add_url_rule.assert_called_once_with("/some/path", "route_method", view_func=plugin.route_method, methods=["GET"]) + + blueprint.errorhandler.assert_called_once_with(404) + errorhandler.assert_called_once_with(plugin.errorhandler_method) + + def test_get_blueprint_cached(self): + blueprint = mock.MagicMock() + self.plugin._blueprint = blueprint + + result = self.plugin.get_blueprint() + + self.assertEquals(blueprint, result)