From 2dba705f2e0a9a668241a3b54e3055334401b922 Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:08 +0000 Subject: [PATCH 1/10] Some modifications on project tag get api endpoint: - Add namespace support - Move it to project with the rest of project|repo related endpoints - Use api_method decortator and _get_repo --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index d828577..825d61d 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -454,66 +454,6 @@ def api_task_status(taskid): return flask.jsonify(output) -@API.route("//tags") -@API.route("//tags/") -@API.route("/fork///tags") -@API.route("/fork///tags/") -def api_project_tags(repo, username=None): - """ - List all the tags of a project - ------------------------------ - List the tags made on the project's issues. - - :: - - GET /api/0//tags - - :: - - GET /api/0/fork///tags - - Parameters - ^^^^^^^^^^ - - +---------------+----------+---------------+--------------------------+ - | Key | Type | Optionality | Description | - +===============+==========+===============+==========================+ - | ``pattern`` | string | Optional | | Filters the starting | - | | | | letters of the tags | - +---------------+----------+---------------+--------------------------+ - - Sample response - ^^^^^^^^^^^^^^^ - - :: - - { - "total_tags": 2, - "tags": ["tag1", "tag2"] - } - - """ - - pattern = flask.request.args.get("pattern", None) - if pattern is not None and not pattern.endswith("*"): - pattern += "*" - - project_obj = get_authorized_api_project(flask.g.session, repo, username) - if not project_obj: - output = {"output": "notok", "error": "Project not found"} - jsonout = flask.jsonify(output) - jsonout.status_code = 404 - return jsonout - - tags = pagure.lib.query.get_tags_of_project( - flask.g.session, project_obj, pattern=pattern - ) - - return flask.jsonify( - {"total_tags": len(tags), "tags": [tag.tag for tag in tags]} - ) - - @API.route("/error_codes/") @API.route("/error_codes") @API.route("/-/error_codes") @@ -634,7 +574,7 @@ def api(): 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_project_tags_doc = load_doc(project.api_project_tags) api_error_codes_doc = load_doc(api_error_codes) extras = [api_whoami_doc, api_version_doc, api_error_codes_doc] diff --git a/pagure/api/project.py b/pagure/api/project.py index 516bcf1..5f0bf30 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -41,6 +41,68 @@ from pagure.config import config as pagure_config _log = logging.getLogger(__name__) +@API.route("//tags") +@API.route("//tags/") +@API.route("///tags") +@API.route("///tags/") +@API.route("/fork///tags") +@API.route("/fork///tags/") +@API.route("/fork////tags") +@API.route("/fork////tags/") +@api_method +def api_project_tags(repo, username=None, namespace=None): + """ + List all the tags of a project + ------------------------------ + List the tags made on the project's issues. + + :: + + GET /api/0//tags + GET /api/0///git/tags + + :: + + GET /api/0/fork///tags + GET /api/0/fork////tags + + Parameters + ^^^^^^^^^^ + + +---------------+----------+---------------+--------------------------+ + | Key | Type | Optionality | Description | + +===============+==========+===============+==========================+ + | ``pattern`` | string | Optional | | Filters the starting | + | | | | letters of the tags | + +---------------+----------+---------------+--------------------------+ + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + "total_tags": 2, + "tags": ["tag1", "tag2"] + } + + """ + + pattern = flask.request.args.get("pattern", None) + if pattern is not None and not pattern.endswith("*"): + pattern += "*" + + project_obj = _get_repo(repo, username, namespace) + + tags = pagure.lib.query.get_tags_of_project( + flask.g.session, project_obj, pattern=pattern + ) + + return flask.jsonify( + {"total_tags": len(tags), "tags": [tag.tag for tag in tags]} + ) + + @API.route("//git/tags") @API.route("///git/tags") @API.route("/fork///git/tags") diff --git a/tests/test_pagure_flask_api.py b/tests/test_pagure_flask_api.py index 85bdb2b..a765e5f 100644 --- a/tests/test_pagure_flask_api.py +++ b/tests/test_pagure_flask_api.py @@ -93,10 +93,12 @@ class PagureFlaskApitests(tests.SimplePagureTest): output = self.app.get("/api/0/foo/tags/") self.assertEqual(output.status_code, 404) + expected_rv = { + "error": "Project not found", + "error_code": "ENOPROJECT", + } data = json.loads(output.get_data(as_text=True)) - self.assertEqual(set(data.keys()), set(["output", "error"])) - self.assertEqual(data["output"], "notok") - self.assertEqual(data["error"], "Project not found") + self.assertDictEqual(data, expected_rv) output = self.app.get("/api/0/test/tags/") self.assertEqual(output.status_code, 200) From 4ae11af10dcd2d3e223e8f80e1dd0161ebe46c39 Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:08 +0000 Subject: [PATCH 2/10] Fix doc typo --- diff --git a/pagure/lib/query.py b/pagure/lib/query.py index a4a3c49..bfd59a3 100644 --- a/pagure/lib/query.py +++ b/pagure/lib/query.py @@ -942,7 +942,7 @@ def edit_issue_tags( new_tag_color, user, ): - """ Removes the specified tag of a project. """ + """ Edits the specified tag of a project. """ user_obj = get_user(session, user) old_tag_name = old_tag From 1e66ef17fba411bb93666e80ab62042824d7fccc Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:08 +0000 Subject: [PATCH 3/10] tests: move project tags test to project tests file --- diff --git a/tests/test_pagure_flask_api.py b/tests/test_pagure_flask_api.py index a765e5f..a08ed1f 100644 --- a/tests/test_pagure_flask_api.py +++ b/tests/test_pagure_flask_api.py @@ -87,69 +87,6 @@ class PagureFlaskApitests(tests.SimplePagureTest): self.assertEqual(data["version"], pagure.__api_version__) self.assertEqual(sorted(data.keys()), ["version"]) - def test_api_project_tags(self): - """ Test the api_project_tags function. """ - tests.create_projects(self.session) - - output = self.app.get("/api/0/foo/tags/") - self.assertEqual(output.status_code, 404) - expected_rv = { - "error": "Project not found", - "error_code": "ENOPROJECT", - } - data = json.loads(output.get_data(as_text=True)) - self.assertDictEqual(data, expected_rv) - - output = self.app.get("/api/0/test/tags/") - self.assertEqual(output.status_code, 200) - data = json.loads(output.get_data(as_text=True)) - self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) - self.assertEqual(data["tags"], []) - self.assertEqual(data["total_tags"], 0) - - # Add an issue and tag it so that we can list them - item = pagure.lib.model.Issue( - id=1, - uid="foobar", - project_id=1, - title="issue", - content="a bug report", - user_id=1, # pingou - ) - self.session.add(item) - self.session.commit() - item = pagure.lib.model.TagColored( - tag="tag1", tag_color="DeepBlueSky", project_id=1 - ) - self.session.add(item) - self.session.commit() - item = pagure.lib.model.TagIssueColored( - issue_uid="foobar", tag_id=item.id - ) - self.session.add(item) - self.session.commit() - - output = self.app.get("/api/0/test/tags/") - self.assertEqual(output.status_code, 200) - data = json.loads(output.get_data(as_text=True)) - self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) - self.assertEqual(data["tags"], ["tag1"]) - self.assertEqual(data["total_tags"], 1) - - output = self.app.get("/api/0/test/tags/?pattern=t") - self.assertEqual(output.status_code, 200) - data = json.loads(output.get_data(as_text=True)) - self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) - self.assertEqual(data["tags"], ["tag1"]) - self.assertEqual(data["total_tags"], 1) - - output = self.app.get("/api/0/test/tags/?pattern=p") - self.assertEqual(output.status_code, 200) - data = json.loads(output.get_data(as_text=True)) - self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) - self.assertEqual(data["tags"], []) - self.assertEqual(data["total_tags"], 0) - def test_api_groups(self): """ Test the api_groups function. """ diff --git a/tests/test_pagure_flask_api_project.py b/tests/test_pagure_flask_api_project.py index e522b32..93a59dc 100644 --- a/tests/test_pagure_flask_api_project.py +++ b/tests/test_pagure_flask_api_project.py @@ -49,6 +49,69 @@ class PagureFlaskApiProjecttests(tests.Modeltests): self.gga_patcher.stop() super(PagureFlaskApiProjecttests, self).tearDown() + def test_api_project_tags(self): + """ Test the api_project_tags function. """ + tests.create_projects(self.session) + + output = self.app.get("/api/0/foo/tags/") + self.assertEqual(output.status_code, 404) + expected_rv = { + "error": "Project not found", + "error_code": "ENOPROJECT", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) + self.assertEqual(data["tags"], []) + self.assertEqual(data["total_tags"], 0) + + # Add an issue and tag it so that we can list them + item = pagure.lib.model.Issue( + id=1, + uid="foobar", + project_id=1, + title="issue", + content="a bug report", + user_id=1, # pingou + ) + self.session.add(item) + self.session.commit() + item = pagure.lib.model.TagColored( + tag="tag1", tag_color="DeepBlueSky", project_id=1 + ) + self.session.add(item) + self.session.commit() + item = pagure.lib.model.TagIssueColored( + issue_uid="foobar", tag_id=item.id + ) + self.session.add(item) + self.session.commit() + + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) + self.assertEqual(data["tags"], ["tag1"]) + self.assertEqual(data["total_tags"], 1) + + output = self.app.get("/api/0/test/tags/?pattern=t") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) + self.assertEqual(data["tags"], ["tag1"]) + self.assertEqual(data["total_tags"], 1) + + output = self.app.get("/api/0/test/tags/?pattern=p") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) + self.assertEqual(data["tags"], []) + self.assertEqual(data["total_tags"], 0) + def test_api_git_tags(self): """ Test the api_git_tags method of the flask api. """ tests.create_projects(self.session) From 551a58ffd2361c5fd7bf9977e46653eaa1ee9e27 Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:08 +0000 Subject: [PATCH 4/10] api docs: move project tags docs to project section --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index 825d61d..f072946 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -491,6 +491,7 @@ def api(): api_project_doc = load_doc(project.api_project) api_projects_doc = load_doc(project.api_projects) api_project_watchers_doc = load_doc(project.api_project_watchers) + api_project_tags_doc = load_doc(project.api_project_tags) api_git_tags_doc = load_doc(project.api_git_tags) api_project_git_urls_doc = load_doc(project.api_project_git_urls) api_git_branches_doc = load_doc(project.api_git_branches) @@ -573,15 +574,10 @@ def api(): 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(project.api_project_tags) api_error_codes_doc = load_doc(api_error_codes) extras = [api_whoami_doc, api_version_doc, api_error_codes_doc] - if pagure_config.get("ENABLE_TICKETS", True): - extras.append(api_project_tags_doc) - return flask.render_template( "api.html", version=pagure.__api_version__, @@ -591,6 +587,7 @@ def api(): api_modify_project_doc, api_project_doc, api_projects_doc, + api_project_tags_doc, api_git_tags_doc, api_project_git_urls_doc, api_project_watchers_doc, From 6ed7baeebbbd471fec1b4155ef9a1f60e1bb5e3b Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:09 +0000 Subject: [PATCH 5/10] models: to_json method on TagColored --- diff --git a/pagure/lib/model.py b/pagure/lib/model.py index 970dbe4..b9c198a 100644 --- a/pagure/lib/model.py +++ b/pagure/lib/model.py @@ -1819,6 +1819,14 @@ class TagColored(BASE): ), ) + def to_json(self): + output = { + "tag": self.tag, + "tag_description": self.tag_description, + "tag_color": self.tag_color, + } + return output + def __repr__(self): return "TagColored(id: %s, tag:%s, tag_description:%s, color:%s)" % ( self.id, From 415570e484aed02c0c8fc54271d05411382b5a20 Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:09 +0000 Subject: [PATCH 6/10] tests: move project tags tests to their own file --- diff --git a/tests/test_pagure_flask_api_project.py b/tests/test_pagure_flask_api_project.py index 93a59dc..e522b32 100644 --- a/tests/test_pagure_flask_api_project.py +++ b/tests/test_pagure_flask_api_project.py @@ -49,69 +49,6 @@ class PagureFlaskApiProjecttests(tests.Modeltests): self.gga_patcher.stop() super(PagureFlaskApiProjecttests, self).tearDown() - def test_api_project_tags(self): - """ Test the api_project_tags function. """ - tests.create_projects(self.session) - - output = self.app.get("/api/0/foo/tags/") - self.assertEqual(output.status_code, 404) - expected_rv = { - "error": "Project not found", - "error_code": "ENOPROJECT", - } - data = json.loads(output.get_data(as_text=True)) - self.assertDictEqual(data, expected_rv) - - output = self.app.get("/api/0/test/tags/") - self.assertEqual(output.status_code, 200) - data = json.loads(output.get_data(as_text=True)) - self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) - self.assertEqual(data["tags"], []) - self.assertEqual(data["total_tags"], 0) - - # Add an issue and tag it so that we can list them - item = pagure.lib.model.Issue( - id=1, - uid="foobar", - project_id=1, - title="issue", - content="a bug report", - user_id=1, # pingou - ) - self.session.add(item) - self.session.commit() - item = pagure.lib.model.TagColored( - tag="tag1", tag_color="DeepBlueSky", project_id=1 - ) - self.session.add(item) - self.session.commit() - item = pagure.lib.model.TagIssueColored( - issue_uid="foobar", tag_id=item.id - ) - self.session.add(item) - self.session.commit() - - output = self.app.get("/api/0/test/tags/") - self.assertEqual(output.status_code, 200) - data = json.loads(output.get_data(as_text=True)) - self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) - self.assertEqual(data["tags"], ["tag1"]) - self.assertEqual(data["total_tags"], 1) - - output = self.app.get("/api/0/test/tags/?pattern=t") - self.assertEqual(output.status_code, 200) - data = json.loads(output.get_data(as_text=True)) - self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) - self.assertEqual(data["tags"], ["tag1"]) - self.assertEqual(data["total_tags"], 1) - - output = self.app.get("/api/0/test/tags/?pattern=p") - self.assertEqual(output.status_code, 200) - data = json.loads(output.get_data(as_text=True)) - self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) - self.assertEqual(data["tags"], []) - self.assertEqual(data["total_tags"], 0) - def test_api_git_tags(self): """ Test the api_git_tags method of the flask api. """ tests.create_projects(self.session) diff --git a/tests/test_pagure_flask_api_project_tags.py b/tests/test_pagure_flask_api_project_tags.py new file mode 100644 index 0000000..5a7b2ea --- /dev/null +++ b/tests/test_pagure_flask_api_project_tags.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +""" + Authors: + Julen Landa Alustiza +""" + +from __future__ import unicode_literals, absolute_import + +import json +import sys +import os + +sys.path.insert( + 0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") +) + +import tests +import pagure.lib.query + + +class PagureFlaskApiProjectTagstests(tests.Modeltests): + """ Tests for the flask API of pagure project tags """ + + def test_api_project_tags_no_project(self): + """ Test the api_project_tags function. """ + output = self.app.get("/api/0/foo/tags/") + self.assertEqual(output.status_code, 404) + expected_rv = { + "error": "Project not found", + "error_code": "ENOPROJECT", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tags(self): + """ Test the api_project_tags function. """ + tests.create_projects(self.session) + + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) + self.assertEqual(data["tags"], []) + self.assertEqual(data["total_tags"], 0) + + # Add a tag so that we can list it + item = pagure.lib.model.TagColored( + tag="tag1", tag_color="DeepBlueSky", project_id=1 + ) + self.session.add(item) + self.session.commit() + + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) + self.assertEqual(data["tags"], ["tag1"]) + self.assertEqual(data["total_tags"], 1) + + output = self.app.get("/api/0/test/tags/?pattern=t") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) + self.assertEqual(data["tags"], ["tag1"]) + self.assertEqual(data["total_tags"], 1) + + output = self.app.get("/api/0/test/tags/?pattern=p") + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) + self.assertEqual(data["tags"], []) + self.assertEqual(data["total_tags"], 0) From 021675890d90194b1116979aa80fca4903b2659a Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:09 +0000 Subject: [PATCH 7/10] api/project: new project tag endpoint --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index f072946..cd91e58 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -492,6 +492,7 @@ def api(): api_projects_doc = load_doc(project.api_projects) api_project_watchers_doc = load_doc(project.api_project_watchers) api_project_tags_doc = load_doc(project.api_project_tags) + api_project_tags_new_doc = load_doc(project.api_project_tags_new) api_git_tags_doc = load_doc(project.api_git_tags) api_project_git_urls_doc = load_doc(project.api_project_git_urls) api_git_branches_doc = load_doc(project.api_git_branches) @@ -588,6 +589,7 @@ def api(): api_project_doc, api_projects_doc, api_project_tags_doc, + api_project_tags_new_doc, api_git_tags_doc, api_project_git_urls_doc, api_project_watchers_doc, diff --git a/pagure/api/project.py b/pagure/api/project.py index 5f0bf30..17cd5da 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -103,6 +103,89 @@ def api_project_tags(repo, username=None, namespace=None): ) +@API.route("//tags/new", methods=["POST"]) +@API.route("///tags/new", methods=["POST"]) +@API.route("/fork///tags/new", methods=["POST"]) +@API.route("/fork////tags/new", methods=["POST"]) +@api_login_required(acls=["modify_project"]) +@api_method +def api_project_tags_new(repo, username=None, namespace=None): + """ + Create a new tag on a project + ------------------------------ + + Create a new tag on the project's issues and pull requests. + + :: + + POST /api/0//tags/new + POST /api/0///tags/new + + :: + + POST /api/0/fork///tags/new + POST /api/0/fork////tags/new + + Input + ^^^^^ + + +-------------------+--------+-------------+---------------------------+ + | Key | Type | Optionality | Description | + +===================+========+=============+===========================+ + | 'tag' | string | Mandatory | The name of the tag | + +-------------------+--------+-------------+---------------------------+ + | 'tag_color' | string | Mandatory | The color of the tag | + +-------------------+--------+-------------+---------------------------+ + | 'tag_description' | string | Optional | | The description of the | + | | | | tag | + +-------------------+--------+-------------+---------------------------+ + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + "tag": { + "tag": "tag1", + "tag_color": "DeepBlueSky", + "tag_description": "Our blue tag" + }, + "message": "Tag created" + } + + """ + output = {} + repo = _get_repo(repo, username, namespace) + _check_token(repo, project_token=False) + + form = pagure.forms.ApiAddIssueTagForm(csrf_enabled=False) + if form.validate_on_submit(): + tag_name = form.tag.data + tag_description = form.tag_description.data + tag_color = form.tag_color.data + try: + tag = pagure.lib.query.new_tag( + flask.g.session, tag_name, tag_description, tag_color, repo.id + ) + flask.g.session.commit() + output["message"] = "Tag created" + output["tag"] = tag.to_json() + + except SQLAlchemyError as err: + 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("//git/tags") @API.route("///git/tags") @API.route("/fork///git/tags") diff --git a/pagure/forms.py b/pagure/forms.py index eb8db07..46cfd10 100644 --- a/pagure/forms.py +++ b/pagure/forms.py @@ -366,6 +366,26 @@ class AddIssueTagForm(DeleteIssueTagForm): ) +class ApiAddIssueTagForm(PagureForm): + """ Form to add a tag to a project from the API endpoint """ + + tag = wtforms.StringField( + "Tag", + [ + wtforms.validators.DataRequired(), + wtforms.validators.Regexp(TAGS_REGEX, flags=re.IGNORECASE), + wtforms.validators.Length(max=255), + ], + ) + + tag_description = wtforms.StringField( + "Tag Description", [wtforms.validators.Optional()] + ) + tag_color = wtforms.StringField( + "Tag Color", [wtforms.validators.DataRequired()] + ) + + class StatusForm(PagureForm): """ Form to add/change the status of an issue. """ diff --git a/tests/test_pagure_flask_api_project_tags.py b/tests/test_pagure_flask_api_project_tags.py index 5a7b2ea..c355df9 100644 --- a/tests/test_pagure_flask_api_project_tags.py +++ b/tests/test_pagure_flask_api_project_tags.py @@ -71,3 +71,149 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests): self.assertEqual(sorted(data.keys()), ["tags", "total_tags"]) self.assertEqual(data["tags"], []) self.assertEqual(data["total_tags"], 0) + + def test_api_project_tags_new_wrong_token(self): + """ Test the api_tags_new method of the flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + headers = {"Authorization": "token aaa"} + output = self.app.post("/api/0/test/tags/new", headers=headers) + self.assertEqual(output.status_code, 401) + expected_rv = { + "error": "Invalid or expired token. Please visit " + "http://localhost.localdomain/settings#nav-api-tab to get or renew " + "your API token.", + "error_code": "EINVALIDTOK", + "errors": "Invalid token", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tags_new_wrong_project(self): + """ Test the api_tags_new method of the flask api. """ + + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.post("/api/0/foo/tags/new", headers=headers) + self.assertEqual(output.status_code, 404) + expected_rv = { + "error": "Project not found", + "error_code": "ENOPROJECT", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tags_new_wrong_acls(self): + """ Test the api_tags_new method of the flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session, acl_name="create_project") + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.post("/api/0/test/tags/new", headers=headers) + self.assertEqual(output.status_code, 401) + expected_rv = { + "error": "Invalid or expired token. Please visit " + "http://localhost.localdomain/settings#nav-api-tab to get or renew " + "your API token.", + "error_code": "EINVALIDTOK", + "errors": "Missing ACLs: modify_project", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tags_new_no_input(self): + """ Test the api_tags_new method of the flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.post("/api/0/test/tags/new", headers=headers) + self.assertEqual(output.status_code, 400) + expected_rv = { + "error": "Invalid or incomplete input submitted", + "error_code": "EINVALIDREQ", + "errors": { + "tag": ["This field is required."], + "tag_color": ["This field is required."], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tags_new(self): + """ Test the api_tags_new method of the flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + expected_rv = {"tags": [], "total_tags": 0} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + data = {"tag": "blue", "tag_color": "DeepBlueSky"} + + output = self.app.post( + "/api/0/test/tags/new", headers=headers, data=data + ) + self.assertEqual(output.status_code, 200) + expected_rv = { + "message": "Tag created", + "tag": { + "tag": "blue", + "tag_description": "", + "tag_color": "DeepBlueSky", + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + expected_rv = {"tags": ["blue"], "total_tags": 1} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tags_new_existing_tag(self): + """ Test the api_tags_new method of the flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + # Add an issue and tag it so that we can list them + item = pagure.lib.model.TagColored( + tag="blue", tag_color="DeepBlueSky", project_id=1 + ) + self.session.add(item) + self.session.commit() + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + expected_rv = {"tags": ["blue"], "total_tags": 1} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + data = {"tag": "blue", "tag_color": "DeepBlueSky"} + + output = self.app.post( + "/api/0/test/tags/new", headers=headers, data=data + ) + self.assertEqual(output.status_code, 400) + expected_rv = { + "error": "An error occurred at the database level and prevent " + "the action from reaching completion", + "error_code": "EDBERROR", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + expected_rv = {"tags": ["blue"], "total_tags": 1} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) From 7f84efd28ff41441eaf53e1c79d6b08f915ca648 Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:09 +0000 Subject: [PATCH 8/10] api.utils: _get_project_tag helper method --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index cd91e58..e8c3364 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -127,6 +127,7 @@ class APIERROR(enum.Enum): EPLUGINDISABLED = "Plugin disabled" EPLUGINCHANGENOTALLOWED = "This plugin cannot be changed" EPLUGINNOTINSTALLED = "Project doesn't have this plugin installed" + ENOTAG = "Tag not found" def get_authorized_api_project(session, repo, user=None, namespace=None): diff --git a/pagure/api/utils.py b/pagure/api/utils.py index 30754a1..b1d1dd6 100644 --- a/pagure/api/utils.py +++ b/pagure/api/utils.py @@ -264,3 +264,21 @@ def _check_plugin(repo, plugin): ) return plugin + + +def _get_project_tag(project_id, tag_name): + """Check if tag exists and get tag obj + : param project_id: id of the project + : param tag_name: name of the tag + : raises pagure.exceptions.APIError: when tag_name doesn't exist on + project with id = project_id + : return tag object + """ + tag = pagure.lib.query.get_colored_tag( + flask.g.session, tag_name, project_id + ) + + if tag is None: + raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOTAG) + + return tag diff --git a/tests/test_pagure_flask_api.py b/tests/test_pagure_flask_api.py index a08ed1f..9cf4d61 100644 --- a/tests/test_pagure_flask_api.py +++ b/tests/test_pagure_flask_api.py @@ -180,7 +180,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), 41) + self.assertEqual(len(data), 42) self.assertEqual( sorted(data.keys()), sorted( @@ -226,6 +226,7 @@ class PagureFlaskApitests(tests.SimplePagureTest): "EPLUGINDISABLED", "EPLUGINCHANGENOTALLOWED", "EPLUGINNOTINSTALLED", + "ENOTAG", ] ), ) From f67d25696fb2317a0e0f2153082592c341286f4e Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:09 +0000 Subject: [PATCH 9/10] api/project: delete project tag endpoint --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index e8c3364..3133150 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -494,6 +494,7 @@ def api(): api_project_watchers_doc = load_doc(project.api_project_watchers) api_project_tags_doc = load_doc(project.api_project_tags) api_project_tags_new_doc = load_doc(project.api_project_tags_new) + api_project_tag_delete_doc = load_doc(project.api_project_tag_delete) api_git_tags_doc = load_doc(project.api_git_tags) api_project_git_urls_doc = load_doc(project.api_project_git_urls) api_git_branches_doc = load_doc(project.api_git_branches) @@ -591,6 +592,7 @@ def api(): api_projects_doc, api_project_tags_doc, api_project_tags_new_doc, + api_project_tag_delete_doc, api_git_tags_doc, api_project_git_urls_doc, api_project_watchers_doc, diff --git a/pagure/api/project.py b/pagure/api/project.py index 17cd5da..28aace9 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -34,7 +34,7 @@ from pagure.api import ( get_page, get_per_page, ) -from pagure.api.utils import _get_repo, _check_token +from pagure.api.utils import _get_repo, _check_token, _get_project_tag from pagure.config import config as pagure_config @@ -186,6 +186,59 @@ def api_project_tags_new(repo, username=None, namespace=None): return jsonout +@API.route("//tag/", methods=["DELETE"]) +@API.route("///tag/", methods=["DELETE"]) +@API.route("/fork///tag/", methods=["DELETE"]) +@API.route("/fork////tag/", methods=["DELETE"]) +@api_login_required(acls=["modify_project"]) +@api_method +def api_project_tag_delete(repo, tag, username=None, namespace=None): + """ + Delete a tag on a project + ------------------------- + + Delete a tag on project's issues and pull requests. + + :: + + DELETE /api/0//tag/ + DELETE /api/0///tag/ + + :: + + DELETE /api/0/fork///tag/ + DELETE /api/0/fork////tag/ + + Sample response + ^^^^^^^^^^^^^^^ + + :: + { + "message": "Tag blue has been deleted" + } + + """ + output = {} + repo = _get_repo(repo, username, namespace) + _check_token(repo) + tag = _get_project_tag(repo.id, tag) + tags = tag.tag + + try: + msgs = pagure.lib.query.remove_tags( + flask.g.session, repo, tags, user=flask.g.fas_user.username + ) + flask.g.session.commit() + output["message"] = msgs[0] + 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("//git/tags") @API.route("///git/tags") @API.route("/fork///git/tags") diff --git a/tests/test_pagure_flask_api_project_tags.py b/tests/test_pagure_flask_api_project_tags.py index c355df9..03d4028 100644 --- a/tests/test_pagure_flask_api_project_tags.py +++ b/tests/test_pagure_flask_api_project_tags.py @@ -217,3 +217,144 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests): expected_rv = {"tags": ["blue"], "total_tags": 1} data = json.loads(output.get_data(as_text=True)) self.assertDictEqual(data, expected_rv) + + def test_api_project_tag_delete_wrong_token(self): + """ Test the api_project_tag_delete method of flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + headers = {"Authorization": "token aaa"} + output = self.app.delete("/api/0/test/tag/blue", headers=headers) + self.assertEqual(output.status_code, 401) + expected_rv = { + "error": "Invalid or expired token. Please visit " + "http://localhost.localdomain/settings#nav-api-tab to get or renew " + "your API token.", + "error_code": "EINVALIDTOK", + "errors": "Invalid token", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tag_delete_wrong_project(self): + """ Test the api_project_tag_delete method of flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.delete("/api/0/foo/tag/blue", headers=headers) + self.assertEqual(output.status_code, 404) + expected_rv = { + "error": "Project not found", + "error_code": "ENOPROJECT", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tag_delete_wrong_tag(self): + """ Test the api_project_tag_delete method of flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.delete("/api/0/test/tag/blue", headers=headers) + self.assertEqual(output.status_code, 404) + expected_rv = {"error": "Tag not found", "error_code": "ENOTAG"} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tag_delete(self): + """ Test the api_project_tag_delete method of flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + item = pagure.lib.model.TagColored( + tag="blue", tag_color="DeepBlueSky", project_id=1 + ) + self.session.add(item) + self.session.commit() + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + expected_rv = {"tags": ["blue"], "total_tags": 1} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.delete("/api/0/test/tag/blue", headers=headers) + self.assertEqual(output.status_code, 200) + expected_rv = {"message": "Tag: blue has been deleted"} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + expected_rv = {"tags": [], "total_tags": 0} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tag_delete_with_assigned_issue_and_pr(self): + """ Test the api_project_tag_delete method of flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + # Add a tag + item = pagure.lib.model.TagColored( + tag="blue", tag_color="DeepBlueSky", project_id=1 + ) + self.session.add(item) + self.session.commit() + + # Add a tagged issue + item = pagure.lib.model.Issue( + id=1, + uid="foobar", + project_id=1, + title="issue", + content="a bug report", + user_id=1, # pingou + ) + self.session.add(item) + self.session.commit() + item = pagure.lib.model.TagIssueColored(issue_uid="foobar", tag_id=1) + self.session.add(item) + self.session.commit() + + # Add a tagged pull request + item = pagure.lib.model.PullRequest( + id=1, + uid="barfoo", + project_id=1, + branch="master", + branch_from="master", + title="pull request", + allow_rebase=False, + user_id=1, # pingou + ) + self.session.add(item) + self.session.commit() + item = pagure.lib.model.TagPullRequest(request_uid="barfoo", tag_id=1) + self.session.add(item) + self.session.commit() + + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + expected_rv = {"tags": ["blue"], "total_tags": 1} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.delete("/api/0/test/tag/blue", headers=headers) + self.assertEqual(output.status_code, 200) + expected_rv = {"message": "Tag: blue has been deleted"} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + output = self.app.get("/api/0/test/tags/") + self.assertEqual(output.status_code, 200) + expected_rv = {"tags": [], "total_tags": 0} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) From f6b1f497a6866c91a139dfd629efd7d0fffe2374 Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:09 +0000 Subject: [PATCH 10/10] api/project: get project tag endpoint --- diff --git a/pagure/api/project.py b/pagure/api/project.py index 28aace9..9b56b9a 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -103,6 +103,47 @@ def api_project_tags(repo, username=None, namespace=None): ) +@API.route("//tag/", methods=["GET"]) +@API.route("///tag/", methods=["GET"]) +@API.route("/fork///tag/", methods=["GET"]) +@API.route("/fork////tag/", methods=["GET"]) +@api_method +def api_project_tag_view(repo, tag, username=None, namespace=None): + """ + View a tag of a project + ----------------------- + View a tag on project's issues or pull requests. + + :: + + GET /api/0//tag/ + GET /api/0//tag/ + + :: + GET /api/0/fork///tag/ + GET /api/0/fork///tag/ + + Sample response + ---------------- + + :: + + { + "tag": "tag1", + "tag_color": "DeepBlueSky" + "tag_description": "Our blue tag" + } + + """ + repo = _get_repo(repo, username, namespace) + _check_token(repo, project_token=False) + tag = _get_project_tag(repo.id, tag) + output = tag.to_json() + + jsonout = flask.jsonify(output) + return jsonout + + @API.route("//tags/new", methods=["POST"]) @API.route("///tags/new", methods=["POST"]) @API.route("/fork///tags/new", methods=["POST"]) diff --git a/tests/test_pagure_flask_api_project_tags.py b/tests/test_pagure_flask_api_project_tags.py index 03d4028..6b57a47 100644 --- a/tests/test_pagure_flask_api_project_tags.py +++ b/tests/test_pagure_flask_api_project_tags.py @@ -358,3 +358,52 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests): expected_rv = {"tags": [], "total_tags": 0} data = json.loads(output.get_data(as_text=True)) self.assertDictEqual(data, expected_rv) + + def test_api_project_tag_view_no_project(self): + """ Test the api_project_tag_view method of the flask api. """ + output = self.app.get("/api/0/foo/tag/tag1") + self.assertEqual(output.status_code, 404) + expected_rv = { + "error": "Project not found", + "error_code": "ENOPROJECT", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tag_view_wrong_tag(self): + """ Test the api_project_tag_view method of the flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + item = pagure.lib.model.TagColored( + tag="blue", tag_color="DeepBlueSky", project_id=1 + ) + self.session.add(item) + self.session.commit() + output = self.app.get("/api/0/test/tag/tag1") + self.assertEqual(output.status_code, 404) + expected_rv = {"error": "Tag not found", "error_code": "ENOTAG"} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_project_tag_view(self): + """ Test the api_project_tag_view method of the flask api. """ + tests.create_projects(self.session) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + item = pagure.lib.model.TagColored( + tag="blue", tag_color="DeepBlueSky", project_id=1 + ) + self.session.add(item) + self.session.commit() + output = self.app.get("/api/0/test/tag/blue") + self.assertEqual(output.status_code, 200) + expected_rv = { + "tag": "blue", + "tag_color": "DeepBlueSky", + "tag_description": "", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv)