From 31280551ec2fdf282152abae2ff9d44f20faaf92 Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jan 11 2020 10:43:54 +0000 Subject: api: add update issue endpoint --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index d13e8ad..f09a209 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -518,6 +518,7 @@ def api(): issues = [] if pagure_config.get("ENABLE_TICKETS", True): issues.append(load_doc(issue.api_new_issue)) + issues.append(load_doc(issue.api_issue_update)) issues.append(load_doc(issue.api_view_issues)) issues.append(load_doc(issue.api_view_issue)) issues.append(load_doc(issue.api_view_issue_comment)) diff --git a/pagure/api/issue.py b/pagure/api/issue.py index 59c7185..4f7eaa8 100644 --- a/pagure/api/issue.py +++ b/pagure/api/issue.py @@ -579,6 +579,122 @@ def api_view_issue(repo, issueid, username=None, namespace=None): return jsonout +@API.route("//issue/", methods=["POST"]) +@API.route("///issue/", methods=["POST"]) +@API.route("/fork///issue/", methods=["POST"]) +@API.route( + "/fork////issue/", methods=["POST"] +) +@api_login_required(acls=["issue_update"]) +@api_method +def api_issue_update(repo, issueid, username=None, namespace=None): + """ + Update issue information + ------------------------ + Update the title and issue content of an existing issue. + + :: + + POST /api/0//issue/ + POST /api/0///issue/ + + :: + + POST /api/0/fork///issue/ + POST /api/0/fork////issue/ + + Input + ^^^^^ + + +-------------------+--------+-------------+---------------------------+ + | Key | Type | Optionality | Description | + +===================+========+=============+===========================+ + | ``title`` | string | Mandatory | The title of the issue | + +-------------------+--------+-------------+---------------------------+ + | ``issue_content`` | string | Mandatory | | The description of the | + | | | | issue | + +-------------------+--------+-------------+---------------------------+ + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + "issue": { + "assignee": null, + "blocks": [], + "close_status": null, + "closed_at": null, + "closed_by": null, + "comments": [], + "content": "This issue needs attention", + "custom_fields": [], + "date_created": "1479458613", + "depends": [], + "id": 1, + "milestone": null, + "priority": null, + "private": false, + "status": "Open", + "tags": [], + "title": "test issue", + "user": { + "fullname": "PY C", + "name": "pingou" + } + }, + "message": "Issue edited" + } + + """ + + output = {} + repo = _get_repo(repo, username, namespace) + _check_issue_tracker(repo) + _check_token(repo) + + issue_id = issue_uid = None + try: + issue_id = int(issueid) + except (ValueError, TypeError): + issue_uid = issueid + + issue = _get_issue(repo, issue_id, issueuid=issue_uid) + _check_private_issue_access(issue) + + form = pagure.forms.IssueFormSimplied(csrf_enabled=False) + + if form.validate_on_submit(): + title = form.title.data.strip() + content = form.issue_content.data + + try: + pagure.lib.query.edit_issue( + session=flask.g.session, + issue=issue, + user=flask.g.fas_user.username, + title=title, + content=content, + ) + flask.g.session.commit() + + output["message"] = "Issue edited" + output["issue"] = issue.to_json(public=True) + 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("//issue//comment/") @API.route("///issue//comment/") @API.route("/fork///issue//comment/") diff --git a/tests/test_pagure_flask_api_issue_update.py b/tests/test_pagure_flask_api_issue_update.py new file mode 100644 index 0000000..60d175f --- /dev/null +++ b/tests/test_pagure_flask_api_issue_update.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- + +""" + Authors: + Julen Landa Alustiza +""" + +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 PagureFlaskApiIssueUpdatetests(tests.Modeltests): + """ Tests for the flask API of pagure for updating an issue """ + + def setUp(self): + """ Set up the environnment, ran before every tests. """ + super(PagureFlaskApiIssueUpdatetests, self).setUp() + + pagure.config.config["TICKETS_FOLDER"] = None + tests.create_projects(self.session) + tests.create_tokens(self.session) + + def test_api_issue_update_wrong_token(self): + """ Test the api_issue_update method of flask API """ + tests.create_tokens_acl(self.session) + headers = {"Authorization": "token aaa"} + output = self.app.post("/api/0/foo/issue/1", 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_issue_update_wrong_project(self): + """ Test the api_issue_update method of flask API """ + tests.create_tokens_acl(self.session) + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.post("/api/0/foo/issue/1", 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_issue_update_wrong_acls(self): + """ Test the api_issue_update method of flask API """ + tests.create_tokens_acl(self.session, acl_name="issue_create") + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.post("/api/0/test/issue/1", 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: issue_update", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + @patch.dict("pagure.config.config", {"ENABLE_TICKETS": False}) + def test_api_issue_update_instance_tickets_disabled(self): + """ Test the api_issue_update method of flask API """ + tests.create_tokens_acl(self.session) + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.post("/api/0/test/issue/1", headers=headers) + self.assertEqual(output.status_code, 404) + expected_rv = { + "error": "Issue tracker disabled", + "error_code": "ETRACKERDISABLED", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_issue_update_project_tickets_disabled(self): + """ Test the api_issue_update method of flask API """ + tests.create_tokens_acl(self.session) + # disable tickets on this repo + repo = pagure.lib.query.get_authorized_project(self.session, "test") + settings = repo.settings + settings["issue_tracker"] = False + repo.settings = settings + self.session.add(repo) + self.session.commit() + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.post("/api/0/test/issue/1", headers=headers) + self.assertEqual(output.status_code, 404) + expected_rv = { + "error": "Issue tracker disabled", + "error_code": "ETRACKERDISABLED", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_issue_update_project_read_only_issue_tracker(self): + """ Test the api_issue_update method of flask API """ + tests.create_tokens_acl(self.session) + # set read only issue tracke on this repo + repo = pagure.lib.query.get_authorized_project(self.session, "test") + settings = repo.settings + settings["issue_tracker_read_only"] = True + repo.settings = settings + self.session.add(repo) + self.session.commit() + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.post("/api/0/test/issue/1", headers=headers) + self.assertEqual(output.status_code, 401) + expected_rv = { + "error": "The issue tracker of this project is read-only", + "error_code": "ETRACKERREADONLY", + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_issue_update_wrong_issue(self): + """ Test the api_issue_update method of flask API """ + tests.create_tokens_acl(self.session) + tests.create_projects_git(os.path.join(self.path, "tickets")) + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.post("/api/0/test/issue/1", headers=headers) + self.assertEqual(output.status_code, 404) + expected_rv = {"error": "Issue not found", "error_code": "ENOISSUE"} + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_issue_update_no_input(self): + """ Test the api_issue_update method of flask API """ + tests.create_tokens_acl(self.session) + tests.create_projects_git(os.path.join(self.path, "tickets")) + headers = {"Authorization": "token aaabbbcccddd"} + + # Create an issue + repo = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.new_issue( + session=self.session, + repo=repo, + title="Test issue #1", + content="We should work on this", + user="pingou", + private=False, + ) + self.session.commit() + self.assertEqual(msg.title, "Test issue #1") + headers = {"Authorization": "token aaabbbcccddd"} + output = self.app.post("/api/0/test/issue/1", headers=headers) + self.assertEqual(output.status_code, 400) + expected_rv = { + "error": "Invalid or incomplete input submitted", + "error_code": "EINVALIDREQ", + "errors": { + "issue_content": ["This field is required."], + "title": ["This field is required."], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_issue_update_partial_input(self): + """ Test the api_issue_update method of flask API """ + tests.create_tokens_acl(self.session) + tests.create_projects_git(os.path.join(self.path, "tickets")) + headers = {"Authorization": "token aaabbbcccddd"} + + # Create an issue + repo = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.new_issue( + session=self.session, + repo=repo, + title="Test issue #1", + content="We should work on this", + user="pingou", + private=False, + ) + self.session.commit() + self.assertEqual(msg.title, "Test issue #1") + headers = {"Authorization": "token aaabbbcccddd"} + # missing issue_content + data = {"title": "New title"} + output = self.app.post( + "/api/0/test/issue/1", data=data, headers=headers + ) + self.assertEqual(output.status_code, 400) + expected_rv = { + "error": "Invalid or incomplete input submitted", + "error_code": "EINVALIDREQ", + "errors": {"issue_content": ["This field is required."]}, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + # missing title + data = {"issue_content": "New content"} + output = self.app.post( + "/api/0/test/issue/1", data=data, headers=headers + ) + self.assertEqual(output.status_code, 400) + expected_rv = { + "error": "Invalid or incomplete input submitted", + "error_code": "EINVALIDREQ", + "errors": {"title": ["This field is required."]}, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_api_issue_update(self): + """ Test the api_issue_update method of flask API """ + tests.create_tokens_acl(self.session) + tests.create_projects_git(os.path.join(self.path, "tickets")) + headers = {"Authorization": "token aaabbbcccddd"} + + # Create an issue + repo = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.new_issue( + session=self.session, + repo=repo, + title="Test issue #1", + content="We should work on this", + user="pingou", + private=False, + ) + self.session.commit() + self.assertEqual(msg.title, "Test issue #1") + headers = {"Authorization": "token aaabbbcccddd"} + data = {"title": "New title", "issue_content": "New content"} + output = self.app.post( + "/api/0/test/issue/1", data=data, headers=headers + ) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(data["message"], "Issue edited") + self.assertEqual(data["issue"]["title"], "New title") + self.assertEqual(data["issue"]["content"], "New content") + output = self.app.get("/api/0/test/issue/1", headers=headers) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(data["title"], "New title") + self.assertEqual(data["content"], "New content")