From 021675890d90194b1116979aa80fca4903b2659a Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 10 2020 11:25:09 +0000 Subject: 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)