From 5543e47835b81e6ca2b2a44a150dfe8a42680883 Mon Sep 17 00:00:00 2001 From: Michal Konečný Date: Dec 09 2019 14:32:59 +0000 Subject: Add API for plugins This commits adds four new API calls to work with plugins: - install plugin on project - remove plugin from project - view enabled plugins for project - view all available plugins in pagure Signed-off-by: Michal Konečný --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index 976c0e4..d828577 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -123,6 +123,10 @@ class APIERROR(enum.Enum): ENOPRSTATS = "No statistics could be computed for this PR" EUBLOCKED = "You have been blocked from this project" EREBASENOTALLOWED = "You are not authorized to rebase this pull-request" + ENOPLUGIN = "No such plugin" + EPLUGINDISABLED = "Plugin disabled" + EPLUGINCHANGENOTALLOWED = "This plugin cannot be changed" + EPLUGINNOTINSTALLED = "Project doesn't have this plugin installed" def get_authorized_api_project(session, repo, user=None, namespace=None): @@ -304,6 +308,7 @@ from pagure.api import fork # noqa: E402 from pagure.api import project # noqa: E402 from pagure.api import user # noqa: E402 from pagure.api import group # noqa: E402 +from pagure.api import plugins # noqa: E402 if pagure_config.get("PAGURE_CI_SERVICES", False): from pagure.api.ci import jenkins # noqa: E402 @@ -623,6 +628,11 @@ def api(): api_view_group_doc = load_doc(group.api_view_group) api_groups_doc = load_doc(group.api_groups) + api_install_plugin_doc = load_doc(plugins.api_install_plugin) + api_remove_plugin_doc = load_doc(plugins.api_remove_plugin) + api_view_plugins_project_doc = load_doc(plugins.api_view_plugins_project) + api_view_plugins_doc = load_doc(plugins.api_view_plugins) + if pagure_config.get("ENABLE_TICKETS", True): api_project_tags_doc = load_doc(api_project_tags) api_error_codes_doc = load_doc(api_error_codes) @@ -680,6 +690,12 @@ def api(): api_view_user_requests_actionable_doc, ], groups=[api_groups_doc, api_view_group_doc], + plugins=[ + api_install_plugin_doc, + api_remove_plugin_doc, + api_view_plugins_project_doc, + api_view_plugins_doc, + ], ci=ci_doc, extras=extras, ) diff --git a/pagure/api/plugins.py b/pagure/api/plugins.py new file mode 100644 index 0000000..cc8af48 --- /dev/null +++ b/pagure/api/plugins.py @@ -0,0 +1,367 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2019 - Copyright Red Hat Inc + + Authors: + Michal Konecny + +""" + +from __future__ import print_function, unicode_literals, absolute_import + +import flask +import logging + +from sqlalchemy.exc import SQLAlchemyError + +import pagure.exceptions +import pagure.lib.query + +import pagure.lib.plugins as plugins_lib +from pagure.api import ( + API, + api_method, + api_login_required, + api_login_optional, + APIERROR, +) +from pagure.api.utils import _get_repo, _check_token, _check_plugin + +_log = logging.getLogger(__name__) + +# List of ignored form fields, these fields will be not returned in response +IGNORED_FIELDS = ["active"] + + +def _filter_fields(plugin): + """ + Filter IGNORED_FIELDS from form and return list of the valid fields. + + :arg plugin: plugin class from which to read fields + :type plugin: plugin class + :return: list of valid fields + """ + fields = [] + for field in plugin.form_fields: + if field not in IGNORED_FIELDS: + fields.append(field) + + return fields + + +@API.route("//settings//install", methods=["POST"]) +@API.route("///settings//install", methods=["POST"]) +@API.route( + "/fork///settings//install", methods=["POST"] +) +@API.route( + "/fork////settings//install", + methods=["POST"], +) +@api_login_required(acls=["modify_project"]) +@api_method +def api_install_plugin(repo, plugin, username=None, namespace=None): + """ + Install plugin + -------------- + Install a plugin to a repository. + + :: + + POST /api/0//settings//install + POST /api/0///settings//install + + :: + + POST /api/0/fork///settings//install + POST /api/0/fork////settings/ + /install + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + "plugin": { + "mail_to": "serg@wh40k.com" + }, + "message": "Hook 'Mail' activated" + } + + """ + output = {} + repo = _get_repo(repo, username, namespace) + _check_token(repo, project_token=False) + plugin = _check_plugin(repo, plugin) + + fields = [] + new = True + dbobj = plugin.db_object() + + if hasattr(repo, plugin.backref): + dbobj = getattr(repo, plugin.backref) + + # There should always be only one, but let's double check + if dbobj: + new = False + else: + dbobj = plugin.db_object() + + form = plugin.form(obj=dbobj, csrf_enabled=False) + form.active.data = True + for field in plugin.form_fields: + fields.append(getattr(form, field)) + + if form.validate_on_submit(): + form.populate_obj(obj=dbobj) + + if new: + dbobj.project_id = repo.id + flask.g.session.add(dbobj) + try: + flask.g.session.flush() + except SQLAlchemyError: # pragma: no cover + flask.g.session.rollback() + _log.exception("Could not add plugin %s", plugin.name) + raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) + + try: + # Set up the main script if necessary + plugin.set_up(repo) + # Install the plugin itself + plugin.install(repo, dbobj) + except pagure.exceptions.FileNotFoundException as err: + flask.g.session.rollback() + _log.exception(err) + raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) + + try: + flask.g.session.commit() + output["message"] = "Hook '%s' activated" % plugin.name + output["plugin"] = { + field: form[field].data for field in _filter_fields(plugin) + } + except SQLAlchemyError as err: # pragma: no cover + flask.g.session.rollback() + _log.exception(err) + raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) + + else: + raise pagure.exceptions.APIError( + 400, error_code=APIERROR.EINVALIDREQ, errors=form.errors + ) + + jsonout = flask.jsonify(output) + return jsonout + + +@API.route("//settings//remove", methods=["POST"]) +@API.route("///settings//remove", methods=["POST"]) +@API.route( + "/fork///settings//remove", methods=["POST"] +) +@API.route( + "/fork////settings//remove", + methods=["POST"], +) +@api_login_required(acls=["modify_project"]) +@api_method +def api_remove_plugin(repo, plugin, username=None, namespace=None): + """ + Remove plugin + -------------- + Remove a plugin from repository. + + :: + + POST /api/0//settings//remove + POST /api/0///settings//remove + + :: + + POST /api/0/fork///settings//remove + POST /api/0/fork////settings/ + /remove + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + "plugin": { + "mail_to": "serg@wh40k.com" + }, + "message": "Hook 'Mail' deactivated" + } + + """ + output = {} + repo = _get_repo(repo, username, namespace) + _check_token(repo, project_token=False) + plugin = _check_plugin(repo, plugin) + + dbobj = plugin.db_object() + + enabled_plugins = { + plugin[0]: plugin[1] + for plugin in plugins_lib.get_enabled_plugins(repo) + } + + # If the plugin is not installed raise error + if plugin not in enabled_plugins.keys(): + raise pagure.exceptions.APIError( + 400, error_code=APIERROR.EPLUGINNOTINSTALLED + ) + + if enabled_plugins[plugin]: + dbobj = enabled_plugins[plugin] + + form = plugin.form(obj=dbobj) + form.active.data = False + + try: + plugin.remove(repo) + except pagure.exceptions.FileNotFoundException as err: + flask.g.session.rollback() + _log.exception(err) + raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) + + try: + flask.g.session.commit() + output["message"] = "Hook '%s' deactivated" % plugin.name + output["plugin"] = { + field: form[field].data for field in _filter_fields(plugin) + } + except SQLAlchemyError as err: # pragma: no cover + flask.g.session.rollback() + _log.exception(err) + raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) + + jsonout = flask.jsonify(output) + return jsonout + + +@API.route("///settings/plugins") +@API.route("/fork///settings/plugins") +@API.route("//settings/plugins") +@API.route("/fork////settings/plugins") +@api_login_optional() +@api_method +def api_view_plugins_project(repo, username=None, namespace=None): + """ + List project's plugins + ---------------------- + List installed plugins on a project. + + :: + + GET /api/0//settings/plugins + GET /api/0///settings/plugins + + :: + + GET /api/0/fork///settings/plugins + GET /api/0/fork////settings/plugins + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + 'plugins': + [ + { + 'Mail': + { + 'mail_to': 'serg@wh40k.com' + } + } + ], + 'total_plugins': 1 + } + + """ + repo = _get_repo(repo, username, namespace) + + plugins = { + plugin[0]: plugin[1] + for plugin in plugins_lib.get_enabled_plugins(repo) + } + output = {} + + output["plugins"] = [] + + for (plugin, dbobj) in plugins.items(): + if dbobj: + form = plugin.form(obj=dbobj) + fields = _filter_fields(plugin) + output["plugins"].append( + {plugin.name: {field: form[field].data for field in fields}} + ) + + output["total_plugins"] = len(output["plugins"]) + + jsonout = flask.jsonify(output) + return jsonout + + +@API.route("/_plugins") +@api_method +def api_view_plugins(): + """ + List plugins + ------------ + List every plugin available in this pagure instance. For each plugin their + name is provided as well as the name of the argument + to provide to enable/disable them. + + :: + + GET /api/0/plugins + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + 'plugins': [ + { + 'Block Un-Signed commits': [ + ] + }, + { + 'Block non fast-forward pushes': [ + 'branches', + ] + }, + { + 'Fedmsg': [ + ] + }, + ], + 'total_issues': 3 + } + + """ + plugins = plugins_lib.get_plugin_names() + + output = {} + + output["total_plugins"] = len(plugins) + output["plugins"] = [] + + for plugin_name in plugins: + # Skip plugins that are disabled + if plugin_name in pagure.config.config.get("DISABLED_PLUGINS", []): + continue + plugin = plugins_lib.get_plugin(plugin_name) + fields = _filter_fields(plugin) + output["plugins"].append({plugin_name: fields}) + + jsonout = flask.jsonify(output) + return jsonout diff --git a/pagure/api/utils.py b/pagure/api/utils.py index 8b87edd..30754a1 100644 --- a/pagure/api/utils.py +++ b/pagure/api/utils.py @@ -15,7 +15,8 @@ import logging import pagure.exceptions -import pagure.lib.query + +from pagure.lib import plugins from pagure.config import config as pagure_config from pagure.api import APIERROR, get_authorized_api_project @@ -233,3 +234,33 @@ def _check_private_pull_request_access(request): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EPRNOTALLOWED ) + + +def _check_plugin(repo, plugin): + """ + Check if plugin exists. + + :param repo: Repository object + :param plugin: Plugin class + :return plugin object + """ + plugin = plugins.get_plugin(plugin) + if not plugin: + raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPLUGIN) + + if repo.private and plugin.name == "Pagure CI": + raise pagure.exceptions.APIError( + 404, error_code=APIERROR.EPLUGINDISABLED + ) + + if plugin.name in pagure.config.config.get("DISABLED_PLUGINS", []): + raise pagure.exceptions.APIError( + 404, error_code=APIERROR.EPLUGINDISABLED + ) + + if plugin.name == "default": + raise pagure.exceptions.APIError( + 403, error_code=APIERROR.EPLUGINCHANGENOTALLOWED + ) + + return plugin diff --git a/pagure/templates/api.html b/pagure/templates/api.html index eb5fce0..e4b4ecd 100644 --- a/pagure/templates/api.html +++ b/pagure/templates/api.html @@ -88,6 +88,18 @@ {% endfor %} +

+ Plugins + + + +

+
+ {% for html in plugins %} + {{ html | InsertDiv | safe }} + {% endfor %} +
+ {% if config.get('PAGURE_CI_SERVICES') %}

Continous Integration Services diff --git a/tests/test_pagure_flask_api.py b/tests/test_pagure_flask_api.py index f9d684e..85bdb2b 100644 --- a/tests/test_pagure_flask_api.py +++ b/tests/test_pagure_flask_api.py @@ -241,7 +241,7 @@ class PagureFlaskApitests(tests.SimplePagureTest): output = self.app.get("/api/0/-/error_codes") self.assertEqual(output.status_code, 200) data = json.loads(output.get_data(as_text=True)) - self.assertEqual(len(data), 37) + self.assertEqual(len(data), 41) self.assertEqual( sorted(data.keys()), sorted( @@ -283,6 +283,10 @@ class PagureFlaskApitests(tests.SimplePagureTest): "ETRACKERREADONLY", "EUBLOCKED", "EREBASENOTALLOWED", + "ENOPLUGIN", + "EPLUGINDISABLED", + "EPLUGINCHANGENOTALLOWED", + "EPLUGINNOTINSTALLED", ] ), ) diff --git a/tests/test_pagure_flask_api_plugins_install.py b/tests/test_pagure_flask_api_plugins_install.py new file mode 100644 index 0000000..96a12fe --- /dev/null +++ b/tests/test_pagure_flask_api_plugins_install.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2019 - Copyright Red Hat Inc + + Authors: + Michal Konecny + +""" + +from __future__ import unicode_literals, absolute_import + +import datetime +import unittest +import sys +import os +import json + +from mock import patch, MagicMock + +sys.path.insert( + 0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") +) + +import pagure.lib.query # noqa: E402 +import tests # noqa: E402 + + +class PagureFlaskApiPluginInstalltests(tests.Modeltests): + """ Tests for the flask API of pagure for installing a plugin + """ + + @patch("pagure.lib.notify.send_email", MagicMock(return_value=True)) + def setUp(self): + """ Set up the environnment, ran before every tests. """ + super(PagureFlaskApiPluginInstalltests, self).setUp() + + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + # Create project-less token for user foo + item = pagure.lib.model.Token( + id="project-less-foo", + user_id=2, + project_id=None, + expiration=datetime.datetime.utcnow() + + datetime.timedelta(days=30), + ) + self.session.add(item) + self.session.commit() + tests.create_tokens_acl(self.session, token_id="project-less-foo") + + # Create project-specific token for user foo + item = pagure.lib.model.Token( + id="project-specific-foo", + user_id=2, + project_id=1, + expiration=datetime.datetime.utcnow() + + datetime.timedelta(days=30), + ) + self.session.add(item) + self.session.commit() + tests.create_tokens_acl(self.session, token_id="project-specific-foo") + + def test_install_plugin_own_project_no_data(self): + """ Test installing a new plugin on a project for which you're the + main maintainer. + """ + + # pingou's token with all the ACLs + headers = {"Authorization": "token aaabbbcccddd"} + + # Install a plugin on /test/ where pingou is the main admin + output = self.app.post( + "/api/0/test/settings/Mail/install", headers=headers + ) + self.assertEqual(output.status_code, 400) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + pagure.api.APIERROR.EINVALIDREQ.name, data["error_code"] + ) + self.assertEqual(pagure.api.APIERROR.EINVALIDREQ.value, data["error"]) + self.assertEqual( + data["errors"], {"mail_to": ["This field is required."]} + ) + + def test_install_plugin_own_project(self): + """ Test installing a new plugin on a project for which you're the + main maintainer. + """ + + # pingou's token with all the ACLs + headers = {"Authorization": "token aaabbbcccddd"} + + # complete data set + data = {"mail_to": "serg@wh40k.com"} + + # Create an issue on /test/ where pingou is the main admin + output = self.app.post( + "/api/0/test/settings/Mail/install", headers=headers, data=data + ) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + data, + { + "plugin": {"mail_to": "serg@wh40k.com"}, + "message": "Hook 'Mail' activated", + }, + ) + + @patch("pagure.lib.notify.send_email", MagicMock(return_value=True)) + def test_install_plugin_someone_else_project_project_less_token(self): + """ Test installing a new plugin on a project with which you have + nothing to do. + """ + + # pingou's token with all the ACLs + headers = {"Authorization": "token project-less-foo"} + + # Install a plugin on /test/ where pingou is the main admin + output = self.app.post( + "/api/0/test/settings/Prevent creating new branches by git push/" + "install", + headers=headers, + ) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + data, + { + "plugin": {}, + "message": "Hook 'Prevent creating new branches by git push' " + "activated", + }, + ) + + @patch("pagure.lib.notify.send_email", MagicMock(return_value=True)) + def test_install_plugin_project_specific_token(self): + """ Test installing a new plugin on a project with a regular + project-specific token. + """ + + # pingou's token with all the ACLs + headers = {"Authorization": "token project-specific-foo"} + + # complete data set + data = {"mail_to": "serg@wh40k.com"} + + # Create an issue on /test/ where pingou is the main admin + output = self.app.post( + "/api/0/test/settings/Mail/install", headers=headers, data=data + ) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + data, + { + "plugin": {"mail_to": "serg@wh40k.com"}, + "message": "Hook 'Mail' activated", + }, + ) + + @patch("pagure.lib.notify.send_email", MagicMock(return_value=True)) + def test_install_plugin_invalid_project_specific_token(self): + """ Test installing a new plugin on a project with a regular + project-specific token but for another project. + """ + + # pingou's token with all the ACLs + headers = {"Authorization": "token project-specific-foo"} + + # complete data set + data = {"mail_to": "serg@wh40k.com"} + + # Create an issue on /test/ where pingou is the main admin + output = self.app.post( + "/api/0/test2/settings/Mail/install", headers=headers, data=data + ) + self.assertEqual(output.status_code, 401) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + pagure.api.APIERROR.EINVALIDTOK.name, data["error_code"] + ) + self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"]) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_pagure_flask_api_plugins_remove.py b/tests/test_pagure_flask_api_plugins_remove.py new file mode 100644 index 0000000..4f3a141 --- /dev/null +++ b/tests/test_pagure_flask_api_plugins_remove.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2019 - Copyright Red Hat Inc + + Authors: + Michal Konecny + +""" + +from __future__ import unicode_literals, absolute_import + +import datetime +import unittest +import sys +import os +import json + +from mock import patch, MagicMock + +sys.path.insert( + 0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") +) + +import pagure.lib.plugins as plugins # noqa: E402 +import pagure.lib.query # noqa: E402 +import tests # noqa: E402 + + +class PagureFlaskApiPluginRemovetests(tests.Modeltests): + """ Tests for the flask API of pagure for removing a plugin + """ + + @patch("pagure.lib.notify.send_email", MagicMock(return_value=True)) + def setUp(self): + """ Set up the environnment, ran before every tests. """ + super(PagureFlaskApiPluginRemovetests, self).setUp() + + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + # Create project-less token for user foo + item = pagure.lib.model.Token( + id="project-less-foo", + user_id=2, + project_id=None, + expiration=datetime.datetime.utcnow() + + datetime.timedelta(days=30), + ) + self.session.add(item) + self.session.commit() + tests.create_tokens_acl(self.session, token_id="project-less-foo") + + # Create project-specific token for user foo + item = pagure.lib.model.Token( + id="project-specific-foo", + user_id=2, + project_id=1, + expiration=datetime.datetime.utcnow() + + datetime.timedelta(days=30), + ) + self.session.add(item) + self.session.commit() + + # Install plugin + repo = pagure.lib.query.get_authorized_project(self.session, "test") + plugin = plugins.get_plugin("Mail") + plugin.set_up(repo) + dbobj = plugin.db_object() + dbobj.active = True + dbobj.project_id = repo.id + dbobj.mail_to = "serg@wh40k.com" + plugin.install(repo, dbobj) + self.session.add(dbobj) + self.session.commit() + tests.create_tokens_acl(self.session, token_id="project-specific-foo") + + def test_remove_plugin_own_project_plugin_not_installed(self): + """ Test removing a plugin from a project for which you're the + main maintainer and the plugin is not installed. + """ + + # pingou's token with all the ACLs + headers = {"Authorization": "token aaabbbcccddd"} + + # Remove a plugin from /test/ where pingou is the main admin + output = self.app.post( + "/api/0/test/settings/IRC/remove", headers=headers + ) + self.assertEqual(output.status_code, 400) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + pagure.api.APIERROR.EPLUGINNOTINSTALLED.name, data["error_code"] + ) + self.assertEqual( + pagure.api.APIERROR.EPLUGINNOTINSTALLED.value, data["error"] + ) + + def test_remove_plugin_own_project(self): + """ Test removing a plugin from a project for which you're the + main maintainer. + """ + + # pingou's token with all the ACLs + headers = {"Authorization": "token aaabbbcccddd"} + + # Remove a plugin from /test/ where pingou is the main admin + output = self.app.post( + "/api/0/test/settings/Mail/remove", headers=headers + ) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + data, + { + "plugin": {"mail_to": "serg@wh40k.com"}, + "message": "Hook 'Mail' deactivated", + }, + ) + + @patch("pagure.lib.notify.send_email", MagicMock(return_value=True)) + def test_remove_plugin_someone_else_project_project_less_token(self): + """ Test removing a plugin from a project with which you have + nothing to do. + """ + + # pingou's token with all the ACLs + headers = {"Authorization": "token project-less-foo"} + + # Remove a plugin from /test/ where pingou is the main admin + output = self.app.post( + "/api/0/test/settings/Mail/" "remove", headers=headers + ) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + data, + { + "plugin": {"mail_to": "serg@wh40k.com"}, + "message": "Hook 'Mail' deactivated", + }, + ) + + @patch("pagure.lib.notify.send_email", MagicMock(return_value=True)) + def test_remove_plugin_project_specific_token(self): + """ Test removing a plugin from a project with a regular + project-specific token. + """ + + # pingou's token with all the ACLs + headers = {"Authorization": "token project-specific-foo"} + + # Remove a plugin from /test/ where pingou is the main admin + output = self.app.post( + "/api/0/test/settings/Mail/remove", headers=headers + ) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + data, + { + "plugin": {"mail_to": "serg@wh40k.com"}, + "message": "Hook 'Mail' deactivated", + }, + ) + + @patch("pagure.lib.notify.send_email", MagicMock(return_value=True)) + def test_remove_plugin_invalid_project_specific_token(self): + """ Test removing a plugin from a project with a regular + project-specific token but for another project. + """ + + # pingou's token with all the ACLs + headers = {"Authorization": "token project-specific-foo"} + + # Remove a plugin from /test2/ + output = self.app.post( + "/api/0/test2/settings/Mail/remove", headers=headers + ) + self.assertEqual(output.status_code, 401) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + pagure.api.APIERROR.EINVALIDTOK.name, data["error_code"] + ) + self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"]) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_pagure_flask_api_plugins_view.py b/tests/test_pagure_flask_api_plugins_view.py new file mode 100644 index 0000000..8cdf91a --- /dev/null +++ b/tests/test_pagure_flask_api_plugins_view.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2019 - Copyright Red Hat Inc + + Authors: + Michal Konecny + +""" + +from __future__ import unicode_literals, absolute_import + +import unittest +import sys +import os +import json + +from mock import patch + +sys.path.insert( + 0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") +) + +import tests # noqa: E402 + + +class PagureFlaskApiPluginViewtests(tests.Modeltests): + """ Tests for the flask API of pagure for viewing plugins + """ + + def test_view_plugin(self): + """ Test viewing every plugin available in pagure. + """ + + output = self.app.get("/api/0/_plugins") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + data, + { + "plugins": [ + {"Block Un-Signed commits": []}, + {"Block non fast-forward pushes": ["branches"]}, + {"Fedmsg": []}, + { + "IRC": [ + "server", + "port", + "room", + "nick", + "nick_pass", + "join", + "ssl", + ] + }, + {"Mail": ["mail_to"]}, + {"Mirroring": ["target", "public_key", "last_log"]}, + {"Pagure": []}, + { + "Pagure CI": [ + "ci_type", + "ci_url", + "ci_job", + "active_commit", + "active_pr", + ] + }, + {"Pagure requests": []}, + {"Pagure tickets": []}, + {"Prevent creating new branches by git push": []}, + {"Read the Doc": ["api_url", "api_token", "branches"]}, + ], + "total_plugins": 12, + }, + ) + + @patch.dict("pagure.config.config", {"DISABLED_PLUGINS": ["IRC"]}) + def test_view_plugin_disabled(self): + """ Test viewing every plugin available in pagure with one plugin disabled. + """ + + output = self.app.get("/api/0/_plugins") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + data, + { + "plugins": [ + {"Block Un-Signed commits": []}, + {"Block non fast-forward pushes": ["branches"]}, + {"Fedmsg": []}, + {"Mail": ["mail_to"]}, + {"Mirroring": ["target", "public_key", "last_log"]}, + {"Pagure": []}, + { + "Pagure CI": [ + "ci_type", + "ci_url", + "ci_job", + "active_commit", + "active_pr", + ] + }, + {"Pagure requests": []}, + {"Pagure tickets": []}, + {"Prevent creating new branches by git push": []}, + {"Read the Doc": ["api_url", "api_token", "branches"]}, + ], + "total_plugins": 12, + }, + ) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_pagure_flask_api_plugins_view_project.py b/tests/test_pagure_flask_api_plugins_view_project.py new file mode 100644 index 0000000..09d887e --- /dev/null +++ b/tests/test_pagure_flask_api_plugins_view_project.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2019 - Copyright Red Hat Inc + + Authors: + Michal Konecny + +""" + +from __future__ import unicode_literals, absolute_import + +import unittest +import sys +import os +import json + +from mock import patch, MagicMock + +sys.path.insert( + 0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") +) + +import pagure.lib.plugins as plugins # noqa: E402 +import pagure.lib.query # noqa: E402 +import tests # noqa: E402 + + +class PagureFlaskApiPluginViewProjecttests(tests.Modeltests): + """ Tests for the flask API of pagure for viewing enabled plugins on project + """ + + @patch("pagure.lib.notify.send_email", MagicMock(return_value=True)) + def setUp(self): + """ Set up the environnment, ran before every tests. """ + super(PagureFlaskApiPluginViewProjecttests, self).setUp() + + tests.create_projects(self.session) + + def test_view_plugin_on_project(self): + """ Test viewing plugins on a project. + """ + + # Install plugin + repo = pagure.lib.query.get_authorized_project(self.session, "test") + plugin = plugins.get_plugin("Mail") + plugin.set_up(repo) + dbobj = plugin.db_object() + dbobj.active = True + dbobj.project_id = repo.id + dbobj.mail_to = "serg@wh40k.com" + plugin.install(repo, dbobj) + self.session.add(dbobj) + self.session.commit() + + # Retrieve all plugins on project + output = self.app.get("/api/0/test/settings/plugins") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual( + data, + { + "plugins": [{"Mail": {"mail_to": "serg@wh40k.com"}}], + "total_plugins": 1, + }, + ) + + def test_viewing_plugin_on_project_no_plugin(self): + """ Test viewing plugins on a project, which doesn't + have any installed. + """ + + # Retrieve all plugins on project + output = self.app.get("/api/0/test/settings/plugins") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(data, {"plugins": [], "total_plugins": 0}) + + +if __name__ == "__main__": + unittest.main(verbosity=2)