Sign In
Sign Up
Sign In
Sign Up
Manage this list
×
Keyboard Shortcuts
Thread View
j
: Next unread message
k
: Previous unread message
j a
: Jump to all threads
j l
: Jump to MailingList overview
2024
April
March
February
January
2023
December
November
October
September
August
July
June
May
April
March
February
January
2022
December
November
October
September
August
July
June
May
April
March
February
January
2021
December
November
October
September
August
July
June
May
April
March
February
January
2020
December
November
October
September
August
July
June
May
April
March
February
January
2019
December
November
October
September
August
July
June
May
April
March
February
January
2018
December
November
October
September
August
July
June
May
April
March
February
January
2017
December
November
October
September
August
July
June
May
April
March
February
January
2016
December
November
October
September
August
July
June
May
April
March
February
January
2015
December
November
October
September
August
July
June
May
April
March
February
January
2014
December
November
October
September
August
July
June
May
April
March
February
January
2013
December
November
October
September
List overview
Download
copr-commits
August 2015
----- 2024 -----
April 2024
March 2024
February 2024
January 2024
----- 2023 -----
December 2023
November 2023
October 2023
September 2023
August 2023
July 2023
June 2023
May 2023
April 2023
March 2023
February 2023
January 2023
----- 2022 -----
December 2022
November 2022
October 2022
September 2022
August 2022
July 2022
June 2022
May 2022
April 2022
March 2022
February 2022
January 2022
----- 2021 -----
December 2021
November 2021
October 2021
September 2021
August 2021
July 2021
June 2021
May 2021
April 2021
March 2021
February 2021
January 2021
----- 2020 -----
December 2020
November 2020
October 2020
September 2020
August 2020
July 2020
June 2020
May 2020
April 2020
March 2020
February 2020
January 2020
----- 2019 -----
December 2019
November 2019
October 2019
September 2019
August 2019
July 2019
June 2019
May 2019
April 2019
March 2019
February 2019
January 2019
----- 2018 -----
December 2018
November 2018
October 2018
September 2018
August 2018
July 2018
June 2018
May 2018
April 2018
March 2018
February 2018
January 2018
----- 2017 -----
December 2017
November 2017
October 2017
September 2017
August 2017
July 2017
June 2017
May 2017
April 2017
March 2017
February 2017
January 2017
----- 2016 -----
December 2016
November 2016
October 2016
September 2016
August 2016
July 2016
June 2016
May 2016
April 2016
March 2016
February 2016
January 2016
----- 2015 -----
December 2015
November 2015
October 2015
September 2015
August 2015
July 2015
June 2015
May 2015
April 2015
March 2015
February 2015
January 2015
----- 2014 -----
December 2014
November 2014
October 2014
September 2014
August 2014
July 2014
June 2014
May 2014
April 2014
March 2014
February 2014
January 2014
----- 2013 -----
December 2013
November 2013
October 2013
September 2013
copr-commits@lists.fedorahosted.org
4 participants
101 discussions
Start a n
N
ew thread
[copr] master: [frontend][api] Added PUT method to /builds/<id> resource (5ae4b8e)
by vgologuz@fedoraproject.org
31 Aug '15
31 Aug '15
Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master >--------------------------------------------------------------- commit 5ae4b8e4fd7b70de86563d3105432763d4b76dc1 Author: Valentin Gologuzov <vgologuz(a)redhat.com> Date: Mon Aug 31 16:25:32 2015 +0200 [frontend][api] Added PUT method to /builds/<id> resource >--------------------------------------------------------------- frontend/coprs_frontend/coprs/exceptions.py | 4 + frontend/coprs_frontend/coprs/helpers.py | 1 + .../coprs_frontend/coprs/logic/builds_logic.py | 3 + .../coprs/rest_api/resources/build.py | 22 ++++- .../coprs/rest_api/resources/project.py | 2 - .../coprs_frontend/tests/test_api/test_build_r.py | 110 ++++++++++++++++++-- .../test_views/test_coprs_ns/test_coprs_builds.py | 10 ++- 7 files changed, 140 insertions(+), 12 deletions(-) diff --git a/frontend/coprs_frontend/coprs/exceptions.py b/frontend/coprs_frontend/coprs/exceptions.py index 8806c1c..8216e3a 100644 --- a/frontend/coprs_frontend/coprs/exceptions.py +++ b/frontend/coprs_frontend/coprs/exceptions.py @@ -18,6 +18,10 @@ class InsufficientRightsException(BaseException): pass +class RequestCannotBeExecuted(Exception): + pass + + class ActionInProgressException(BaseException): def __init__(self, msg, action): diff --git a/frontend/coprs_frontend/coprs/helpers.py b/frontend/coprs_frontend/coprs/helpers.py index c5d614f..7d253af 100644 --- a/frontend/coprs_frontend/coprs/helpers.py +++ b/frontend/coprs_frontend/coprs/helpers.py @@ -161,6 +161,7 @@ def chroot_to_branch(chroot): os = "el" return "{}{}".format(os, version) + def branch_to_os_version(branch): os = None version = None diff --git a/frontend/coprs_frontend/coprs/logic/builds_logic.py b/frontend/coprs_frontend/coprs/logic/builds_logic.py index 250f82e..48a11da 100644 --- a/frontend/coprs_frontend/coprs/logic/builds_logic.py +++ b/frontend/coprs_frontend/coprs/logic/builds_logic.py @@ -408,6 +408,9 @@ class BuildsLogic(object): if not user.can_build_in(build.copr): raise exceptions.InsufficientRightsException( "You are not allowed to cancel this build.") + if not build.cancelable: + raise exceptions.RequestCannotBeExecuted( + "Cannot cancel build {}".format(build.id)) build.canceled = True for chroot in build.build_chroots: chroot.status = 2 # canceled diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py index 80e5b17..a12a96e 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py @@ -6,7 +6,7 @@ from flask import url_for, make_response # from flask_restful_swagger import swagger from coprs import db, models -from coprs.exceptions import ActionInProgressException, InsufficientRightsException +from coprs.exceptions import ActionInProgressException, InsufficientRightsException, RequestCannotBeExecuted from coprs.logic.coprs_logic import CoprsLogic from coprs.logic.builds_logic import BuildsLogic from coprs.logic.users_logic import UsersLogic @@ -193,6 +193,24 @@ class BuildR(Resource): return "", 204 + @rest_api_auth_required + def put(self, build_id): + build = get_one_safe(BuildsLogic.get(build_id), + "Not found build with id: {}".format(build_id)) + """:type : models.Build """ + build_dict = mm_deserialize(BuildSchema(), flask.request.data).data + try: + if not build.canceled and build_dict["state"] == "canceled": + BuildsLogic.cancel_build(flask.g.user, build) + db.session.commit() + except (RequestCannotBeExecuted, ActionInProgressException) as err: + db.session.rollback() + raise CannotProcessRequest("Cannot update build due to: {}" + .format(err)) + except InsufficientRightsException as err: + raise AccessForbidden("Failed to update build: {}".format(err)) - + resp = make_response("", 201) + resp.headers["Location"] = url_for(".buildr", build_id=build_id) + return resp diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py index 7a3f293..92a2d3c 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/project.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py @@ -80,8 +80,6 @@ class ProjectListR(Resource): owner = flask.g.user result = mm_deserialize(ProjectSchema(), flask.request.data) - if result.errors: - return "Failed to parse request", 400 # todo check that chroots are available req = result.data diff --git a/frontend/coprs_frontend/tests/test_api/test_build_r.py b/frontend/coprs_frontend/tests/test_api/test_build_r.py index 406b7a0..a2067eb 100644 --- a/frontend/coprs_frontend/tests/test_api/test_build_r.py +++ b/frontend/coprs_frontend/tests/test_api/test_build_r.py @@ -3,8 +3,9 @@ from cStringIO import StringIO import json import math import random +from marshmallow import pprint -from coprs.helpers import BuildSourceEnum +from coprs.helpers import BuildSourceEnum, StatusEnum from coprs.logic.coprs_logic import CoprsLogic from coprs.logic.builds_logic import BuildsLogic from tests.coprs_test_case import CoprsTestCase @@ -366,7 +367,6 @@ class TestBuildResource(CoprsTestCase): def test_delete_build_ok(self, f_users, f_coprs, f_mock_chroots, f_builds,f_users_api, ): - self.db.session.commit() b_id = self.b1.id href = "/api_2/builds/{}".format(b_id) @@ -408,10 +408,106 @@ class TestBuildResource(CoprsTestCase): login=login, token=token, ) assert r.status_code == 400 - print(r.data) - # test_put_build_wrong_user - # test_put_build_not_found - # test_put_cancel_build - # test_put_cancel_build_wrong_state + def test_put_build_wrong_user( + self, f_users, f_coprs, + f_mock_chroots, f_builds,f_users_api, ): + + login = self.u2.api_login + token = self.u2.api_token + + for bc in self.b1_bc: + bc.status = StatusEnum("pending") + bc.ended_on = None + + self.b1.ended_on = None + self.db.session.add_all(self.b1_bc) + self.db.session.add(self.b1) + + self.db.session.commit() + + href = "/api_2/builds/{}".format(self.b1.id) + build_dict = { + "state": "canceled" + } + r = self.request_rest_api_with_auth( + href, + method="put", + login=login, token=token, + content=build_dict + ) + assert r.status_code == 403 + + def test_put_build_not_found( + self, f_users, f_coprs, + f_mock_chroots, f_builds,f_users_api, ): + + self.db.session.commit() + href = "/api_2/builds/{}".format(10005000) + build_dict = { + "state": "canceled" + } + r = self.request_rest_api_with_auth( + href, + method="put", + content=build_dict + ) + assert r.status_code == 404 + + def test_put_cancel_build( + self, f_users, f_coprs, + f_mock_chroots, f_builds,f_users_api, ): + + for bc in self.b1_bc: + bc.status = StatusEnum("pending") + bc.ended_on = None + + self.b1.ended_on = None + self.db.session.add_all(self.b1_bc) + self.db.session.add(self.b1) + + self.db.session.commit() + + href = "/api_2/builds/{}".format(self.b1.id) + build_dict = { + "state": "canceled" + } + r = self.request_rest_api_with_auth( + href, + method="put", + content=build_dict + ) + assert r.status_code == 201 + + r2 = self.tc.get(r.headers["Location"]) + assert r2.status_code == 200 + obj = json.loads(r2.data) + assert obj["build"]["state"] == "canceled" + + def test_put_cancel_build_wrong_state( + self, f_users, f_coprs, + f_mock_chroots, f_builds,f_users_api, ): + + self.b1.ended_on = None + old_state = self.b1.state + self.db.session.add_all(self.b1_bc) + self.db.session.add(self.b1) + + self.db.session.commit() + + href = "/api_2/builds/{}".format(self.b1.id) + build_dict = { + "state": "canceled" + } + r = self.request_rest_api_with_auth( + href, + method="put", + content=build_dict + ) + assert r.status_code == 400 + + r2 = self.tc.get(href) + assert r2.status_code == 200 + obj = json.loads(r2.data) + assert obj["build"]["state"] == old_state diff --git a/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_builds.py b/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_builds.py index aafc17c..7d0e1be 100644 --- a/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_builds.py +++ b/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_builds.py @@ -1,5 +1,6 @@ import json from coprs import models +from coprs.helpers import StatusEnum from tests.coprs_test_case import CoprsTestCase, TransactionDecorator @@ -89,6 +90,10 @@ class TestCoprCancelBuild(CoprsTestCase): f_mock_chroots, f_builds, f_db): + for bc in self.b1_bc: + bc.status = StatusEnum("pending") + bc.ended_on = None + self.db.session.add_all(self.b1_bc) self.db.session.add_all([self.u1, self.c1, self.b1]) self.test_client.post("/coprs/{0}/{1}/cancel_build/{2}/" .format(self.u1.name, self.c1.name, self.b1.id), @@ -102,7 +107,10 @@ class TestCoprCancelBuild(CoprsTestCase): f_coprs, f_mock_chroots, f_builds, f_db): - + for bc in self.b1_bc: + bc.status = StatusEnum("pending") + bc.ended_on = None + self.db.session.add_all(self.b1_bc) self.db.session.add_all([self.u1, self.c1, self.b1]) self.test_client.post("/coprs/{0}/{1}/cancel_build/{2}/" .format(self.u1.name, self.c1.name, self.b1.id),
1
0
0
0
[copr] master: [frontend][api] Added DELETE method to /builds/<id> resource (c8a01c8)
by vgologuz@fedoraproject.org
31 Aug '15
31 Aug '15
Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master >--------------------------------------------------------------- commit c8a01c8354fdfc991b09f96d33f9489147385ae2 Author: Valentin Gologuzov <vgologuz(a)redhat.com> Date: Mon Aug 31 14:42:51 2015 +0200 [frontend][api] Added DELETE method to /builds/<id> resource >--------------------------------------------------------------- .../coprs/rest_api/resources/build.py | 31 ++++---- .../coprs_frontend/tests/test_api/test_build_r.py | 79 +++++++++++++++++--- 2 files changed, 86 insertions(+), 24 deletions(-) diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py index 5c2a2b5..80e5b17 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py @@ -172,24 +172,27 @@ class BuildListR(Resource): class BuildR(Resource): def get(self, build_id): - build = get_one_safe(BuildsLogic.get(build_id), "Not found build with id: {}".format(build_id)) return render_build(build) + @rest_api_auth_required + def delete(self, build_id): + build = get_one_safe(BuildsLogic.get(build_id), + "Not found build with id: {}".format(build_id)) + try: + BuildsLogic.delete_build(flask.g.user, build) + db.session.commit() + except ActionInProgressException as err: + db.session.rollback() + raise CannotProcessRequest("Cannot delete build due to: {}" + .format(err)) + except InsufficientRightsException as err: + raise AccessForbidden("Failed to delete build: {}".format(err)) + + return "", 204 + + -# to get build details and cancel individual build chroots -# class BuildChrootR(Resource): -# def get(self, owner, project, name): -# copr = get_one_safe(CoprsLogic.get(flask.g.user, owner, project), -# "Copr {}/{} not found".format(owner, project)) -# chroot = get_one_safe(CoprChrootsLogic.get(copr, name)) -# -# return { -# "chroot": chroot.to_dict(), -# "links": { -# "self": bp_url_for(BuildChrootR.endpoint, owner=owner, project=project, name=name) -# } -# } diff --git a/frontend/coprs_frontend/tests/test_api/test_build_r.py b/frontend/coprs_frontend/tests/test_api/test_build_r.py index 0d9c9b9..406b7a0 100644 --- a/frontend/coprs_frontend/tests/test_api/test_build_r.py +++ b/frontend/coprs_frontend/tests/test_api/test_build_r.py @@ -1,20 +1,13 @@ # coding: utf-8 from cStringIO import StringIO - import json -from marshmallow import pprint import math +import random -import pytest -import sqlalchemy -from coprs import models from coprs.helpers import BuildSourceEnum - -from coprs.logic.users_logic import UsersLogic from coprs.logic.coprs_logic import CoprsLogic from coprs.logic.builds_logic import BuildsLogic - -from tests.coprs_test_case import CoprsTestCase, TransactionDecorator +from tests.coprs_test_case import CoprsTestCase class TestBuildResource(CoprsTestCase): @@ -343,7 +336,7 @@ class TestBuildResource(CoprsTestCase): ) assert r0.status_code == 400 - def test_get_one(self, f_users, f_coprs, f_builds, f_db, + def test_get_one_build(self, f_users, f_coprs, f_builds, f_db, f_users_api, f_mock_chroots): build_id_list = [b.id for b in self.basic_builds] @@ -356,3 +349,69 @@ class TestBuildResource(CoprsTestCase): obj = json.loads(r.data) assert obj["build"]["id"] == b_id assert obj["_links"]["self"]["href"] == href + + def test_get_one_build_not_found(self, f_users, f_coprs, f_builds, f_db, + f_users_api, f_mock_chroots): + build_id_list = [b.id for b in self.basic_builds] + max_id = max(build_id_list) + 1 + self.db.session.commit() + + for _ in range(10): + fake_id = random.randint(max_id, max_id * 10) + href = "/api_2/builds/{}".format(fake_id) + + r = self.tc.get(href) + assert r.status_code == 404 + + def test_delete_build_ok(self, f_users, f_coprs, + f_mock_chroots, f_builds,f_users_api, ): + + + self.db.session.commit() + b_id = self.b1.id + href = "/api_2/builds/{}".format(b_id) + r = self.request_rest_api_with_auth( + href, + method="delete", + ) + assert r.status_code == 204 + + r2 = self.tc.get(href) + assert r2.status_code == 404 + + def test_delete_build_wrong_user(self, f_users, f_coprs, + f_mock_chroots, f_builds,f_users_api, ): + + login = self.u2.api_login + token = self.u2.api_token + self.db.session.commit() + b_id = self.b1.id + href = "/api_2/builds/{}".format(b_id) + r = self.request_rest_api_with_auth( + href, + method="delete", + login=login, token=token, + ) + assert r.status_code == 403 + + def test_delete_build_in_progress(self, f_users, f_coprs, + f_mock_chroots, f_builds,f_users_api, ): + + login = self.u2.api_login + token = self.u2.api_token + self.db.session.commit() + b_id = self.b3.id + href = "/api_2/builds/{}".format(b_id) + r = self.request_rest_api_with_auth( + href, + method="delete", + login=login, token=token, + ) + assert r.status_code == 400 + print(r.data) + + # test_put_build_wrong_user + # test_put_build_not_found + # test_put_cancel_build + # test_put_cancel_build_wrong_state +
1
0
0
0
[copr] master: [frontend][api] Update tests relatedto BuildsLogic (cd3f8bb)
by vgologuz@fedoraproject.org
31 Aug '15
31 Aug '15
Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master >--------------------------------------------------------------- commit cd3f8bb070a91077418c03fd4c14687555a9b3b7 Author: Valentin Gologuzov <vgologuz(a)redhat.com> Date: Mon Aug 31 13:43:28 2015 +0200 [frontend][api] Update tests relatedto BuildsLogic >--------------------------------------------------------------- frontend/coprs_frontend/tests/coprs_test_case.py | 17 +++++++---- .../tests/test_logic/test_builds_logic.py | 6 ++- .../test_backend_ns/test_backend_general.py | 25 ++++------------ .../test_views/test_coprs_ns/test_coprs_builds.py | 32 ++++++++++++++++++- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/frontend/coprs_frontend/tests/coprs_test_case.py b/frontend/coprs_frontend/tests/coprs_test_case.py index dedc68b..a6dff96 100644 --- a/frontend/coprs_frontend/tests/coprs_test_case.py +++ b/frontend/coprs_frontend/tests/coprs_test_case.py @@ -225,13 +225,14 @@ class CoprsTestCase(object): buildchroot = models.BuildChroot( build=build, mock_chroot=chroot, - status=status) + status=status, + git_hash="12345", + ) - if build is self.b1: - buildchroot.started_on = 139086644000 - if build is self.b2: + if build is [self.b1, self.b2]: buildchroot.started_on = 139086644000 buildchroot.ended_on = 149086644000 + build.ended_on = 149086644000 build_chroots.append(buildchroot) self.db.session.add(buildchroot) @@ -265,7 +266,9 @@ class CoprsTestCase(object): buildchroot = models.BuildChroot( build=self.b_few_chroots, mock_chroot=chroot, - status=self.status_by_chroot[chroot.name]) + status=self.status_by_chroot[chroot.name], + git_hash="12345", + ) self.db.session.add(buildchroot) self.db.session.add(self.b_few_chroots) @@ -303,7 +306,9 @@ class CoprsTestCase(object): buildchroot = models.BuildChroot( build=self.b_many_chroots, mock_chroot=chroot, - status=self.status_by_chroot[chroot.name]) + status=self.status_by_chroot[chroot.name], + git_hash="12345", + ) self.db.session.add(buildchroot) self.db.session.add(self.b_many_chroots) diff --git a/frontend/coprs_frontend/tests/test_logic/test_builds_logic.py b/frontend/coprs_frontend/tests/test_logic/test_builds_logic.py index b99955d..97d3ab2 100644 --- a/frontend/coprs_frontend/tests/test_logic/test_builds_logic.py +++ b/frontend/coprs_frontend/tests/test_logic/test_builds_logic.py @@ -143,6 +143,7 @@ class TestBuildsLogic(CoprsTestCase): self.b4.pkgs = "
http://example.com/copr-keygen-1.58-1.fc20.src.rpm
" self.b4.ended_on = time.time() + expected_dir = self.b4.result_dir_name for bc in self.b4_bc: bc.status = StatusEnum("succeeded") bc.ended_on = time.time() @@ -166,7 +167,7 @@ class TestBuildsLogic(CoprsTestCase): action = ActionsLogic.get_many().one() delete_data = json.loads(action.data) assert "chroots" in delete_data - assert "copr-keygen-1.58-1.fc20" == delete_data["src_pkg_name"] + assert delete_data["result_dir_name"] == expected_dir assert expected_chroots_to_delete == set(delete_data["chroots"]) with pytest.raises(NoResultFound): @@ -176,6 +177,7 @@ class TestBuildsLogic(CoprsTestCase): self, f_users, f_coprs, f_mock_chroots, f_builds, f_db): self.b1.pkgs = "
http://example.com/copr-keygen-1.58-1.fc20.src.rpm
" + expected_dir = self.b1.result_dir_name self.db.session.add(self.b1) self.db.session.commit() @@ -191,7 +193,7 @@ class TestBuildsLogic(CoprsTestCase): action = ActionsLogic.get_many().one() delete_data = json.loads(action.data) assert "chroots" in delete_data - assert "copr-keygen-1.58-1.fc20" == delete_data["src_pkg_name"] + assert delete_data["result_dir_name"] == expected_dir assert expected_chroots_to_delete == set(delete_data["chroots"]) with pytest.raises(NoResultFound): diff --git a/frontend/coprs_frontend/tests/test_views/test_backend_ns/test_backend_general.py b/frontend/coprs_frontend/tests/test_views/test_backend_ns/test_backend_general.py index ca4ce18..3b8a748 100644 --- a/frontend/coprs_frontend/tests/test_views/test_backend_ns/test_backend_general.py +++ b/frontend/coprs_frontend/tests/test_views/test_backend_ns/test_backend_general.py @@ -55,6 +55,8 @@ class TestUpdateBuilds(CoprsTestCase): { "id": 1, "copr_id": 2, + "chroot": "fedora-18-x86_64", + "status": 6, "started_on": 139086644000 }, { @@ -69,11 +71,13 @@ class TestUpdateBuilds(CoprsTestCase): "id": 123321, "copr_id": 1, "status": 0, + "chroot": "fedora-18-x86_64", "ended_on": 139086644000 }, { "id": 1234321, "copr_id": 2, + "chroot": "fedora-18-x86_64", "results": "
http://server/results/foo/bar/
", "started_on": 139086644000 } @@ -86,23 +90,7 @@ class TestUpdateBuilds(CoprsTestCase): data="") assert "You have to provide the correct password" in r.data - def test_update_build_started(self, f_users, f_coprs, f_mock_chroots, f_builds, f_db): - self.b1.started_on = None - self.db.session.add(self.b1) - self.db.session.commit() - - r = self.tc.post("/backend/update/", - content_type="application/json", - headers=self.auth_header, - data=self.data1) - assert json.loads(r.data)["updated_builds_ids"] == [1] - assert json.loads(r.data)["non_existing_builds_ids"] == [] - - updated = self.models.Build.query.filter( - self.models.Build.id == 1).one() - - assert updated.results == "
http://server/results/foo/bar/
" - assert updated.chroots_started_on == {'fedora-18-x86_64': 139086644000} + # todo: add test for `backend/starting_build/` def test_update_build_ended(self, f_users, f_coprs, f_mock_chroots, f_builds, f_db): @@ -114,10 +102,9 @@ class TestUpdateBuilds(CoprsTestCase): assert json.loads(r.data)["updated_builds_ids"] == [1] assert json.loads(r.data)["non_existing_builds_ids"] == [] - # import ipdb; ipdb.set_trace() updated = self.models.Build.query.filter( self.models.Build.id == 1).one() - # import ipdb; ipdb.set_trace() + assert updated.status == 1 assert updated.chroots_ended_on == {'fedora-18-x86_64': 149086644000} diff --git a/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_builds.py b/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_builds.py index 7bb818f..aafc17c 100644 --- a/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_builds.py +++ b/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_builds.py @@ -115,12 +115,15 @@ class TestCoprCancelBuild(CoprsTestCase): class TestCoprDeleteBuild(CoprsTestCase): @TransactionDecorator("u1") - def test_copr_build_submitter_can_delete_build(self, f_users, + def test_copr_build_submitter_can_delete_build_old(self, f_users, f_coprs, f_mock_chroots, - f_builds, f_db): + f_builds): pkgs = "
http://example.com/one.src.rpm
" self.b1.pkgs = pkgs + for bc in self.b1_bc: + bc.git_hash = None + self.db.session.add_all(self.b1_bc) self.db.session.add_all([self.u1, self.c1, self.b1]) self.db.session.commit() @@ -139,6 +142,31 @@ class TestCoprDeleteBuild(CoprsTestCase): assert act.old_value == "user1/foocopr" assert json.loads(act.data)["src_pkg_name"] == self.b1.src_pkg_name + @TransactionDecorator("u1") + def test_copr_build_submitter_can_delete_build(self, f_users, + f_coprs, f_mock_chroots, + f_builds): + self.db.session.add_all([self.u1, self.c1, self.b1]) + self.db.session.commit() + expected_dir = self.b1.result_dir_name + b_id = self.b1.id + url = "/coprs/{0}/{1}/delete_build/{2}/".format(self.u1.name, self.c1.name, b_id) + + r = self.test_client.post( + url, data={}, follow_redirects=True) + assert r.status_code == 200 + + b = ( + self.models.Build.query + .filter(self.models.Build.id == b_id) + .first() + ) + assert b is None + act = self.models.Action.query.first() + assert act.object_type == "build" + assert act.old_value == "user1/foocopr" + assert json.loads(act.data)["result_dir_name"] == expected_dir + @TransactionDecorator("u2") def test_copr_build_non_submitter_cannot_delete_build(self, f_users, f_coprs,
1
0
0
0
[copr] master: [frontend][api] Implemented and tested build creation. (POST to /builds) (6548b5c)
by vgologuz@fedoraproject.org
28 Aug '15
28 Aug '15
Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master >--------------------------------------------------------------- commit 6548b5cdf68a15f7e50b907ef708de792c8aed47 Author: Valentin Gologuzov <vgologuz(a)redhat.com> Date: Fri Aug 28 12:12:21 2015 +0200 [frontend][api] Implemented and tested build creation. (POST to /builds) >--------------------------------------------------------------- .../coprs_frontend/coprs/logic/builds_logic.py | 49 +++++-- frontend/coprs_frontend/coprs/logic/coprs_logic.py | 12 +- .../coprs/rest_api/resources/build.py | 39 ++++- frontend/coprs_frontend/coprs/rest_api/schemas.py | 2 +- .../coprs/views/coprs_ns/coprs_builds.py | 57 +++++--- .../coprs_frontend/tests/test_api/test_build_r.py | 152 +++++++++++++++++++- 6 files changed, 264 insertions(+), 47 deletions(-) diff --git a/frontend/coprs_frontend/coprs/logic/builds_logic.py b/frontend/coprs_frontend/coprs/logic/builds_logic.py index cd21a6d..250f82e 100644 --- a/frontend/coprs_frontend/coprs/logic/builds_logic.py +++ b/frontend/coprs_frontend/coprs/logic/builds_logic.py @@ -174,10 +174,45 @@ class BuildsLogic(object): return models.Build.query.get(build_id) @classmethod + def create_new_from_url(cls, user, copr, srpm_url, + chroot_names=None, **build_options): + """ + :type user: models.User + :type copr: models.Copr + + :type chroot_names: List[str] + + :rtype: models.Build + """ + # check which chroots we need + chroots = [] + for chroot in copr.active_chroots: + if chroot.name in chroot_names: + chroots.append(chroot) + + source_type = helpers.BuildSourceEnum("srpm_link") + source_json = json.dumps({"url": srpm_url}) + + # try: + build = cls.add( + user=user, + pkgs=srpm_url, + copr=copr, + chroots=chroots, + source_type=source_type, + source_json=source_json, + enable_net=build_options.get("enabled_net", True)) + + if user.proven: + if "timeout" in build_options: + build.timeout = build_options["timeout"] + + return build + + @classmethod def create_new_from_upload(cls, user, copr, f_uploader, orig_filename, chroot_names=None, **build_options): """ - :type user: models.User :type copr: models.Copr :param f_uploader(file_path): function which stores data at the given `file_path` @@ -195,7 +230,6 @@ class BuildsLogic(object): tmp_dir=tmp_name, srpm=filename) - # import ipdb; ipdb.set_trace() # check which chroots we need chroots = [] for chroot in copr.active_chroots: @@ -218,19 +252,16 @@ class BuildsLogic(object): if user.proven: if "timeout" in build_options: build.timeout = build_options["timeout"] - except (ActionInProgressException, InsufficientRightsException) as e: - db.session.rollback() - shutil.rmtree(tmp) + + except Exception: + shutil.rmtree(tmp) # todo: maybe we should delete in some cleanup procedure? raise - else: - flask.flash("New build has been created.") return build @classmethod def add(cls, user, pkgs, copr, source_type=None, source_json=None, - repos=None, chroots=None, - memory_reqs=None, timeout=None, enable_net=True, + repos=None, chroots=None, timeout=None, enable_net=True, git_hashes=None): if chroots is None: chroots = [] diff --git a/frontend/coprs_frontend/coprs/logic/coprs_logic.py b/frontend/coprs_frontend/coprs/logic/coprs_logic.py index 35e9b96..0957462 100644 --- a/frontend/coprs_frontend/coprs/logic/coprs_logic.py +++ b/frontend/coprs_frontend/coprs/logic/coprs_logic.py @@ -209,6 +209,13 @@ class CoprsLogic(object): copr, "Can't delete this project," " another operation is in progress: {action}") + cls.create_delete_action(copr) + copr.deleted = True + + return copr + + @classmethod + def create_delete_action(cls, copr): action = models.Action(action_type=helpers.ActionTypeEnum("delete"), object_type="copr", object_id=copr.id, @@ -216,11 +223,8 @@ class CoprsLogic(object): copr.name), new_value="", created_on=int(time.time())) - copr.deleted = True - db.session.add(action) - - return copr + return action @classmethod def exists_for_user(cls, user, coprname, incl_deleted=False): diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py index c5eab02..5c2a2b5 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py @@ -54,7 +54,7 @@ class BuildListR(Resource): else: query = BuildsLogic.get_multiple() - if "limit" in req_args: + if req_args["limit"] is not None: limit = req_args["limit"] if limit <= 0 or limit > 100: limit = 100 @@ -63,7 +63,7 @@ class BuildListR(Resource): query = query.limit(limit) - if "offset" in req_args: + if req_args["offset"] is not None: query = query.offset(req_args["offset"]) builds = query.all() @@ -84,11 +84,34 @@ class BuildListR(Resource): """ :return: if of the created build or raise Exception """ - build_data = mm_deserialize(BuildCreateFromUrlSchema(), req.data) - raise NotImplementedError() + build_params = mm_deserialize(BuildCreateFromUrlSchema(), req.data).data + project_id = build_params["project_id"] + project = get_one_safe(CoprsLogic.get_by_id(project_id)) + """:type : models.Copr """ + chroot_names = build_params.pop("chroots") + srpm_url = build_params.pop("srpm_url") + try: + build = BuildsLogic.create_new_from_url( + flask.g.user, project, + srpm_url=srpm_url, + chroot_names=chroot_names, + **build_params + ) + db.session.commit() + except ActionInProgressException as err: + db.session.rollback() + raise CannotProcessRequest("Cannot create new build due to: {}" + .format(err)) + except InsufficientRightsException as err: + db.session.rollback() + raise AccessForbidden("User {} cannon create build in project {}: {}" + .format(flask.g.user.username, + project.full_name, err)) + return build.id - def handle_post_multipart(self, req): + @staticmethod + def handle_post_multipart(req): """ :return: if of the created build or raise Exception """ @@ -118,12 +141,14 @@ class BuildListR(Resource): ) db.session.commit() except ActionInProgressException as err: + db.session.rollback() raise CannotProcessRequest("Cannot create new build due to: {}" .format(err)) except InsufficientRightsException as err: - raise AccessForbidden("User {} cannon create build in project {}" + db.session.rollback() + raise AccessForbidden("User {} cannon create build in project {}: {}" .format(flask.g.user.username, - project.full_name)) + project.full_name, err)) return build.id diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py index 057660b..26f4452 100644 --- a/frontend/coprs_frontend/coprs/rest_api/schemas.py +++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py @@ -105,4 +105,4 @@ class BuildCreateSchema(BuildSchema): class BuildCreateFromUrlSchema(BuildCreateSchema): - source_url = fields.Url() + srpm_url = fields.Url(required=True, validate=lambda u: u.startswith("http")) diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py index 40ef9bb..2dffef3 100644 --- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py +++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py @@ -148,7 +148,10 @@ def copr_new_build_upload(username, coprname): ) db.session.commit() except (ActionInProgressException, InsufficientRightsException) as e: + db.session.rollback() flask.flash(str(e), "error") + else: + flask.flash("New build has been created.") return flask.redirect(flask.url_for("coprs_ns.copr_builds", username=username, @@ -173,31 +176,43 @@ def copr_new_build(username, coprname): if not pkgs: flask.flash("No builds submitted") else: - # check which chroots we need - chroots = [] - for chroot in copr.active_chroots: - if chroot.name in form.selected_chroots: - chroots.append(chroot) + # # check which chroots we need + # chroots = [] + # for chroot in copr.active_chroots: + # if chroot.name in form.selected_chroots: + # chroots.append(chroot) # build each package as a separate build try: for pkg in pkgs: - # create json describing the build source - source_type = helpers.BuildSourceEnum("srpm_link") - source_json = json.dumps({"url": pkg}) - - build = BuildsLogic.add( - user=flask.g.user, - pkgs=pkg, - copr=copr, - chroots=chroots, - source_type=source_type, - source_json=source_json, - enable_net=form.enable_net.data) - - if flask.g.user.proven: - build.memory_reqs = form.memory_reqs.data - build.timeout = form.timeout.data + build_options = { + "enable_net": form.enable_net.data, + "timeout": form.timeout.data, + } + BuildsLogic.create_new_from_url( + flask.g.user, copr, pkg, + chroot_names=form.selected_chroots, + **build_options + ) + + # # create json describing the build source + # source_type = helpers.BuildSourceEnum("srpm_link") + # source_json = json.dumps({"url": pkg}) + + + + # build = BuildsLogic.add( + # user=flask.g.user, + # pkgs=pkg, + # copr=copr, + # chroots=chroots, + # source_type=source_type, + # source_json=source_json, + # enable_net=form.enable_net.data) + # + # if flask.g.user.proven: + # build.memory_reqs = form.memory_reqs.data + # build.timeout = form.timeout.data except (ActionInProgressException, InsufficientRightsException) as e: flask.flash(str(e), "error") diff --git a/frontend/coprs_frontend/tests/test_api/test_build_r.py b/frontend/coprs_frontend/tests/test_api/test_build_r.py index 5aefd36..0d9c9b9 100644 --- a/frontend/coprs_frontend/tests/test_api/test_build_r.py +++ b/frontend/coprs_frontend/tests/test_api/test_build_r.py @@ -7,6 +7,7 @@ import math import pytest import sqlalchemy +from coprs import models from coprs.helpers import BuildSourceEnum from coprs.logic.users_logic import UsersLogic @@ -110,16 +111,57 @@ class TestBuildResource(CoprsTestCase): assert builds[:delta] == obj1["builds"] assert builds[delta:2 * delta] == obj2["builds"] - # todo: implement - def _test_post_json( - self, f_users, f_coprs, f_builds, f_db, f_mock_chroots, + # for coverage + href = "/api_2/builds?limit={}".format(1000000) + r = self.tc.get(href) + assert r.status_code == 200 + + def test_post_bad_content_type( + self, f_users, f_coprs, f_db, f_mock_chroots, + f_mock_chroots_many, f_build_many_chroots, + f_users_api): + chroot_name_list = [c.name for c in self.c1.active_chroots] + self.db.session.commit() + metadata = { + "project_id": 1, + "srpm_url": "
http://example.com/mypkg.src.rpm
", + "chroots": chroot_name_list + } + r0 = self.request_rest_api_with_auth( + "/api_2/builds", + method="post", + content_type="plain/test" + ) + assert r0.status_code == 400 + + def test_post_json_bad_url( + self, f_users, f_coprs, f_db, f_mock_chroots, + f_mock_chroots_many, f_build_many_chroots, + f_users_api): + chroot_name_list = [c.name for c in self.c1.active_chroots] + for url in [None, "", "dsafasdga", "gopher://mp.src.rpm"]: + metadata = { + "project_id": 1, + "srpm_url": url, + "chroots": chroot_name_list + } + self.db.session.commit() + r0 = self.request_rest_api_with_auth( + "/api_2/builds", + method="post", + content=metadata + ) + assert r0.status_code == 400 + + def test_post_json( + self, f_users, f_coprs, f_db, f_mock_chroots, f_mock_chroots_many, f_build_many_chroots, f_users_api): chroot_name_list = [c.name for c in self.c1.active_chroots] metadata = { "project_id": 1, - "url": "
http://example.com/mypkg.src.rpm
", + "srpm_url": "
http://example.com/mypkg.src.rpm
", "chroots": chroot_name_list } self.db.session.commit() @@ -128,8 +170,108 @@ class TestBuildResource(CoprsTestCase): method="post", content=metadata ) - print(r0.data) assert r0.status_code == 201 + r1 = self.tc.get(r0.headers["Location"]) + assert r1.status_code == 200 + build_obj = json.loads(r1.data) + build_dict = build_obj["build"] + assert json.loads(build_dict["source_json"])["url"] == \ + metadata["srpm_url"] + assert build_dict["source_type"] == BuildSourceEnum("srpm_link") + + def test_post_json_on_wrong_user( + self, f_users, f_coprs, f_db, f_mock_chroots, + f_mock_chroots_many, f_build_many_chroots, + f_users_api): + + login = self.u2.api_login + token = self.u2.api_token + + chroot_name_list = [c.name for c in self.c1.active_chroots] + metadata = { + "project_id": 1, + "srpm_url": "
http://example.com/mypkg.src.rpm
", + "chroots": chroot_name_list + } + self.db.session.commit() + r0 = self.request_rest_api_with_auth( + "/api_2/builds", + method="post", + login=login, token=token, + content=metadata, + ) + assert r0.status_code == 403 + + def test_post_json_on_project_during_action( + self, f_users, f_coprs, f_db, f_mock_chroots, + f_mock_chroots_many, f_build_many_chroots, + f_users_api): + + CoprsLogic.create_delete_action(self.c1) + chroot_name_list = [c.name for c in self.c1.active_chroots] + metadata = { + "project_id": 1, + "srpm_url": "
http://example.com/mypkg.src.rpm
", + "chroots": chroot_name_list + } + self.db.session.commit() + r0 = self.request_rest_api_with_auth( + "/api_2/builds", + method="post", + content=metadata, + ) + assert r0.status_code == 400 + + def test_post_multipart_wrong_user( + self, f_users, f_coprs, f_builds, f_db, f_mock_chroots, + f_mock_chroots_many, f_build_many_chroots, + f_users_api): + chroot_name_list = [c.name for c in self.c1.active_chroots] + metadata = { + "project_id": 1, + "enable_net": True, + "chroots": chroot_name_list + } + data = { + "metadata": json.dumps(metadata), + "srpm": (StringIO(u'my file contents'), 'hello world.src.rpm') + } + login = self.u2.api_login + token = self.u2.api_token + self.db.session.commit() + r0 = self.request_rest_api_with_auth( + "/api_2/builds", + method="post", + login=login, token=token, + content_type="multipart/form-data", + data=data + ) + assert r0.status_code == 403 + + def test_post_multipart_on_project_during_action( + self, f_users, f_coprs, f_builds, f_db, f_mock_chroots, + f_mock_chroots_many, f_build_many_chroots, + f_users_api): + + CoprsLogic.create_delete_action(self.c1) + chroot_name_list = [c.name for c in self.c1.active_chroots] + metadata = { + "project_id": 1, + "enable_net": True, + "chroots": chroot_name_list + } + data = { + "metadata": json.dumps(metadata), + "srpm": (StringIO(u'my file contents'), 'hello world.src.rpm') + } + self.db.session.commit() + r0 = self.request_rest_api_with_auth( + "/api_2/builds", + method="post", + content_type="multipart/form-data", + data=data + ) + assert r0.status_code == 400 def test_post_multipart( self, f_users, f_coprs, f_builds, f_db, f_mock_chroots,
1
0
0
0
[copr] master: [frontend][api] Implemented and tested build creation through file upload (e200db6)
by vgologuz@fedoraproject.org
27 Aug '15
27 Aug '15
Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master >--------------------------------------------------------------- commit e200db624cc66a4b4a5e1d7d13ee04b38c7ac06a Author: Valentin Gologuzov <vgologuz(a)redhat.com> Date: Thu Aug 27 17:52:00 2015 +0200 [frontend][api] Implemented and tested build creation through file upload >--------------------------------------------------------------- .../coprs_frontend/coprs/logic/builds_logic.py | 76 ++++++++++++---- .../coprs_frontend/coprs/rest_api/exceptions.py | 5 + .../coprs/rest_api/resources/build.py | 73 ++++++++++++++- .../coprs/rest_api/resources/project.py | 2 - frontend/coprs_frontend/coprs/rest_api/schemas.py | 34 +++++--- .../coprs/views/coprs_ns/coprs_builds.py | 59 ++++--------- frontend/coprs_frontend/tests/coprs_test_case.py | 32 +++++-- .../tests/test_api/test_build_chroot_r.py | 1 + .../coprs_frontend/tests/test_api/test_build_r.py | 95 +++++++++++++++++++- .../tests/test_api/test_project_r.py | 1 + .../tests/test_logic/test_builds_logic.py | 2 - 11 files changed, 291 insertions(+), 89 deletions(-) diff --git a/frontend/coprs_frontend/coprs/logic/builds_logic.py b/frontend/coprs_frontend/coprs/logic/builds_logic.py index 0369205..cd21a6d 100644 --- a/frontend/coprs_frontend/coprs/logic/builds_logic.py +++ b/frontend/coprs_frontend/coprs/logic/builds_logic.py @@ -1,14 +1,17 @@ from collections import defaultdict +import tempfile import urlparse import shutil import json import os import pprint import time +import flask from sqlalchemy import or_ from sqlalchemy import and_ from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.sql import false +from werkzeug.utils import secure_filename from coprs import app from coprs import db @@ -16,7 +19,7 @@ from coprs import exceptions from coprs import models from coprs import helpers from coprs.constants import DEFAULT_BUILD_TIMEOUT, MAX_BUILD_TIMEOUT -from coprs.exceptions import MalformedArgumentException +from coprs.exceptions import MalformedArgumentException, ActionInProgressException, InsufficientRightsException from coprs.helpers import StatusEnum from coprs.logic import coprs_logic @@ -171,6 +174,60 @@ class BuildsLogic(object): return models.Build.query.get(build_id) @classmethod + def create_new_from_upload(cls, user, copr, f_uploader, orig_filename, + chroot_names=None, **build_options): + """ + + :type user: models.User + :type copr: models.Copr + :param f_uploader(file_path): function which stores data at the given `file_path` + :return: + """ + tmp = tempfile.mkdtemp(dir=app.config["SRPM_STORAGE_DIR"]) + tmp_name = os.path.basename(tmp) + filename = secure_filename(orig_filename) + file_path = os.path.join(tmp, filename) + f_uploader(file_path) + + # make the pkg public + pkg_url = "https://{hostname}/tmp/{tmp_dir}/{srpm}".format( + hostname=app.config["PUBLIC_COPR_HOSTNAME"], + tmp_dir=tmp_name, + srpm=filename) + + # import ipdb; ipdb.set_trace() + # check which chroots we need + chroots = [] + for chroot in copr.active_chroots: + if chroot.name in chroot_names: + chroots.append(chroot) + + # create json describing the build source + source_type = helpers.BuildSourceEnum("srpm_upload") + source_json = json.dumps({"tmp": tmp_name, "pkg": filename}) + try: + build = cls.add( + user=user, + pkgs=pkg_url, + copr=copr, + chroots=chroots, + source_type=source_type, + source_json=source_json, + enable_net=build_options.get("enabled_net", True)) + + if user.proven: + if "timeout" in build_options: + build.timeout = build_options["timeout"] + except (ActionInProgressException, InsufficientRightsException) as e: + db.session.rollback() + shutil.rmtree(tmp) + raise + else: + flask.flash("New build has been created.") + + return build + + @classmethod def add(cls, user, pkgs, copr, source_type=None, source_json=None, repos=None, chroots=None, memory_reqs=None, timeout=None, enable_net=True, @@ -187,6 +244,7 @@ class BuildsLogic(object): if not repos: repos = copr.repos + # todo: eliminate pkgs and this check if " " in pkgs or "\n" in pkgs or "\t" in pkgs or pkgs.strip() != pkgs: raise exceptions.MalformedArgumentException("Trying to create a build using src_pkg " "with bad characters. Forgot to split?") @@ -196,18 +254,6 @@ class BuildsLogic(object): source_type = helpers.BuildSourceEnum("srpm_link") source_json = json.dumps({"url":pkgs}) - # We no longer guess package name just from the filename - #package_name = helpers.parse_package_name(os.path.basename(pkgs)) - - # And we no longer assign a package to the build (we don't know the package name, right...) - # This is done after package is imported. - #package = packages_logic.PackagesLogic.get(copr.id, package_name).first() - - #if not package: - # package = packages_logic.PackagesLogic.add(user, copr, package_name) - # db.session.add(package) - # db.session.flush() - build = models.Build( user=user, pkgs=pkgs, @@ -215,14 +261,10 @@ class BuildsLogic(object): repos=repos, source_type=source_type, source_json=source_json, - #package_id=package.id, submitted_on=int(time.time()), enable_net=bool(enable_net), ) - if memory_reqs: - build.memory_reqs = memory_reqs - if timeout: build.timeout = timeout or DEFAULT_BUILD_TIMEOUT diff --git a/frontend/coprs_frontend/coprs/rest_api/exceptions.py b/frontend/coprs_frontend/coprs/rest_api/exceptions.py index 6c96cb2..1752101 100644 --- a/frontend/coprs_frontend/coprs/rest_api/exceptions.py +++ b/frontend/coprs_frontend/coprs/rest_api/exceptions.py @@ -47,6 +47,11 @@ class MalformedRequest(ApiError): super(MalformedRequest, self).__init__(400, data, **kwargs) +class CannotProcessRequest(ApiError): + def __init__(self, data=None, **kwargs): + super(CannotProcessRequest, self).__init__(400, data, **kwargs) + + class ServerError(ApiError): def __init__(self, data=None, **kwargs): super(ServerError, self).__init__(500, data, **kwargs) diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py index 33fba81..c5eab02 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py @@ -1,17 +1,21 @@ # coding: utf-8 import flask -from flask import url_for +from flask import url_for, make_response # from flask_restful_swagger import swagger +from coprs import db, models +from coprs.exceptions import ActionInProgressException, InsufficientRightsException from coprs.logic.coprs_logic import CoprsLogic from coprs.logic.builds_logic import BuildsLogic from coprs.logic.users_logic import UsersLogic +from coprs.rest_api.exceptions import MalformedRequest, CannotProcessRequest, AccessForbidden +from coprs.rest_api.resources.project import rest_api_auth_required -from coprs.rest_api.schemas import BuildSchema +from coprs.rest_api.schemas import BuildSchema, BuildCreateSchema, BuildCreateFromUrlSchema -from coprs.rest_api.util import get_one_safe +from coprs.rest_api.util import get_one_safe, mm_deserialize from flask_restful import Resource, reqparse @@ -76,6 +80,69 @@ class BuildListR(Resource): }, } + def handle_post_json(self, req): + """ + :return: if of the created build or raise Exception + """ + build_data = mm_deserialize(BuildCreateFromUrlSchema(), req.data) + raise NotImplementedError() + + + def handle_post_multipart(self, req): + """ + :return: if of the created build or raise Exception + """ + try: + metadata = req.form["metadata"] + except KeyError: + raise MalformedRequest("Missing build metadata in the request") + + if "srpm" not in req.files: + raise MalformedRequest("Missing srpm file in the request") + srpm_handle = req.files["srpm"] + + build_params = mm_deserialize(BuildCreateSchema(), metadata).data + project_id = build_params["project_id"] + + project = get_one_safe(CoprsLogic.get_by_id(project_id)) + """:type : models.Copr """ + + chroot_names = build_params.pop("chroots") + try: + build = BuildsLogic.create_new_from_upload( + flask.g.user, project, + f_uploader=lambda path: srpm_handle.save(path), + orig_filename=srpm_handle.filename, + chroot_names=chroot_names, + **build_params + ) + db.session.commit() + except ActionInProgressException as err: + raise CannotProcessRequest("Cannot create new build due to: {}" + .format(err)) + except InsufficientRightsException as err: + raise AccessForbidden("User {} cannon create build in project {}" + .format(flask.g.user.username, + project.full_name)) + + return build.id + + @rest_api_auth_required + def post(self): + + req = flask.request + if "application/json" in req.content_type: + build_id = self.handle_post_json(req) + elif "multipart/form-data" in req.content_type : + build_id = self.handle_post_multipart(req) + else: + raise MalformedRequest("Got unexpected content type: {}" + .format(req.content_type)) + resp = make_response("", 201) + resp.headers["Location"] = url_for(".buildr", build_id=build_id) + + return resp + class BuildR(Resource): diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py index 3799ae0..7a3f293 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/project.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py @@ -16,8 +16,6 @@ from coprs.logic.helpers import slice_query from coprs.logic.users_logic import UsersLogic from coprs.logic.coprs_logic import CoprsLogic from coprs.exceptions import ActionInProgressException, InsufficientRightsException -from .build import BuildListR -# from .chroot import CoprChrootListR, CoprChrootR from coprs.rest_api.schemas import ProjectSchema from ..exceptions import ObjectAlreadyExists, AuthFailed from ..util import get_one_safe, json_loads_safe, mm_deserialize, render_allowed_method, mm_serialize_one diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py index 0872ee4..057660b 100644 --- a/frontend/coprs_frontend/coprs/rest_api/schemas.py +++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py @@ -71,6 +71,7 @@ class BuildChrootSchema(Schema): started_on = fields.Int(dump_only=True) ended_on = fields.Int(dump_only=True) git_hash = fields.Str(dump_only=True) + name = fields.Str(dump_only=True) class BuildSchema(Schema): @@ -78,23 +79,30 @@ class BuildSchema(Schema): id = fields.Int(dump_only=True) state = fields.Str() - pkgs = fields.Str() - build_packages = fields.Str() - pkg_version = fields.Str() + pkgs = fields.Str(dump_only=True) + build_packages = fields.Str(dump_only=True) + pkg_version = fields.Str(dump_only=True) + + repos = SpaceSeparatedList(dump_only=True) + + submitted_on = fields.Int(dump_only=True) + started_on = fields.Int(dump_only=True) + ended_on = fields.Int(dump_only=True) + + results = fields.Str(dump_only=True) + timeout = fields.Int(dump_only=True) - repos_list = fields.List(fields.Str()) - repos = fields.Str() # todo: replace with SpaceSeparatedList + enable_net = fields.Bool(dump_only=True) - submitted_on = fields.Int() - started_on = fields.Int() - ended_on = fields.Int() + source_type = fields.Int(dump_only=True) + source_json = fields.Str(dump_only=True) - results = fields.Str() - timeout = fields.Int() +class BuildCreateSchema(BuildSchema): + project_id = fields.Int(required=True) + chroots = fields.List(fields.Str()) enable_net = fields.Bool() - source_type = fields.Int() - source_json = fields.Str() - # owner = fields.Function(lambda b: b.copr.owner.username, dump_only=True) +class BuildCreateFromUrlSchema(BuildCreateSchema): + source_url = fields.Url() diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py index 457753b..40ef9bb 100644 --- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py +++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py @@ -16,12 +16,13 @@ from coprs import helpers from coprs.logic import builds_logic from coprs.logic import coprs_logic from coprs.logic import packages_logic +from coprs.logic.builds_logic import BuildsLogic from coprs.views.misc import login_required, page_not_found from coprs.views.coprs_ns import coprs_ns from coprs.exceptions import (ActionInProgressException, - InsufficientRightsException, MalformedArgumentException) + InsufficientRightsException,) @coprs_ns.route("/build/<int:build_id>/") @@ -132,50 +133,22 @@ def copr_new_build_upload(username, coprname): form = forms.BuildFormUploadFactory.create_form_cls(copr.active_chroots)() if form.validate_on_submit(): - tmp = tempfile.mkdtemp(dir=app.config["SRPM_STORAGE_DIR"]) - tmp_name = os.path.basename(tmp) - filename = secure_filename(form.pkgs.data.filename) - file_path = os.path.join(tmp, filename) - form.pkgs.data.save(file_path) - - # make the pkg public - pkg_url = "https://{hostname}/tmp/{tmp_dir}/{srpm}".format( - hostname=app.config["PUBLIC_COPR_HOSTNAME"], - tmp_dir=tmp_name, - srpm=filename) - - # check which chroots we need - chroots = [] - for chroot in copr.active_chroots: - if chroot.name in form.selected_chroots: - chroots.append(chroot) - - # create json describing the build source - source_type = helpers.BuildSourceEnum("srpm_upload") - source_json = json.dumps({"tmp": tmp_name, "pkg": filename}) - - # create a new build - try: - build = builds_logic.BuildsLogic.add( - user=flask.g.user, - pkgs=pkg_url, - copr=copr, - chroots=chroots, - source_type=source_type, - source_json=source_json, - enable_net=form.enable_net.data) - - if flask.g.user.proven: - build.memory_reqs = form.memory_reqs.data - build.timeout = form.timeout.data + build_options = { + "enable_net": form.enable_net.data, + "timeout": form.timeout.data, + } + try: + BuildsLogic.create_new_from_upload( + flask.g.user, copr, + f_uploader=lambda path: form.pkgs.data.save(path), + orig_filename=form.pkgs.data.filename, + chroot_names=form.selected_chroots, + **build_options + ) + db.session.commit() except (ActionInProgressException, InsufficientRightsException) as e: flask.flash(str(e), "error") - db.session.rollback() - shutil.rmtree(tmp) - else: - flask.flash("New build has been created.") - db.session.commit() return flask.redirect(flask.url_for("coprs_ns.copr_builds", username=username, @@ -213,7 +186,7 @@ def copr_new_build(username, coprname): source_type = helpers.BuildSourceEnum("srpm_link") source_json = json.dumps({"url": pkg}) - build = builds_logic.BuildsLogic.add( + build = BuildsLogic.add( user=flask.g.user, pkgs=pkg, copr=copr, diff --git a/frontend/coprs_frontend/tests/coprs_test_case.py b/frontend/coprs_frontend/tests/coprs_test_case.py index ca1156c..dedc68b 100644 --- a/frontend/coprs_frontend/tests/coprs_test_case.py +++ b/frontend/coprs_frontend/tests/coprs_test_case.py @@ -360,7 +360,11 @@ class CoprsTestCase(object): created_on=int(time.time())) self.db.session.add_all([self.a1, self.a2, self.a3]) - def request_rest_api_with_auth(self, url, login=None, token=None, content=None, method="GET"): + def request_rest_api_with_auth(self, url, + login=None, token=None, + content=None, method="GET", + headers=None, data=None, + content_type="application/json"): """ :rtype: flask.wrappers.Response Requires f_users_api fixture @@ -370,22 +374,34 @@ class CoprsTestCase(object): if token is None: token = self.user_api_creds["user1"]["token"] - userstring = "{}:{}".format(login, token) - base64string_user = base64.b64encode(userstring) - base64string = "Basic " + base64string_user + req_headers = { + "Authorization": self._get_auth_string(login, token), + } + if headers: + req_headers.update(headers) kwargs = dict( method=method, - content_type="application/json", - headers={ - "Authorization": base64string - } + content_type=content_type, + headers=req_headers, + buffered=True, ) + if content is not None and data is not None: + raise RuntimeError("Don't specify content and data together") + if content: kwargs["data"] = json.dumps(content) + if data: + kwargs["data"] = data return self.tc.open(url, **kwargs) + def _get_auth_string(self, login, token): + userstring = "{}:{}".format(login, token) + base64string_user = base64.b64encode(userstring) + base64string = "Basic " + base64string_user + return base64string + class TransactionDecorator(object): diff --git a/frontend/coprs_frontend/tests/test_api/test_build_chroot_r.py b/frontend/coprs_frontend/tests/test_api/test_build_chroot_r.py index 6322504..5709b1e 100644 --- a/frontend/coprs_frontend/tests/test_api/test_build_chroot_r.py +++ b/frontend/coprs_frontend/tests/test_api/test_build_chroot_r.py @@ -48,6 +48,7 @@ class TestBuildChrootResource(CoprsTestCase): "git_hash", "started_on", "ended_on", + "name" ] bc = self.b1_bc[0] expected_dict = { diff --git a/frontend/coprs_frontend/tests/test_api/test_build_r.py b/frontend/coprs_frontend/tests/test_api/test_build_r.py index 0885f1b..5aefd36 100644 --- a/frontend/coprs_frontend/tests/test_api/test_build_r.py +++ b/frontend/coprs_frontend/tests/test_api/test_build_r.py @@ -1,4 +1,5 @@ # coding: utf-8 +from cStringIO import StringIO import json from marshmallow import pprint @@ -6,6 +7,7 @@ import math import pytest import sqlalchemy +from coprs.helpers import BuildSourceEnum from coprs.logic.users_logic import UsersLogic from coprs.logic.coprs_logic import CoprsLogic @@ -108,8 +110,99 @@ class TestBuildResource(CoprsTestCase): assert builds[:delta] == obj1["builds"] assert builds[delta:2 * delta] == obj2["builds"] + # todo: implement + def _test_post_json( + self, f_users, f_coprs, f_builds, f_db, f_mock_chroots, + f_mock_chroots_many, f_build_many_chroots, + f_users_api): + + chroot_name_list = [c.name for c in self.c1.active_chroots] + metadata = { + "project_id": 1, + "url": "
http://example.com/mypkg.src.rpm
", + "chroots": chroot_name_list + } + self.db.session.commit() + r0 = self.request_rest_api_with_auth( + "/api_2/builds", + method="post", + content=metadata + ) + print(r0.data) + assert r0.status_code == 201 + + def test_post_multipart( + self, f_users, f_coprs, f_builds, f_db, f_mock_chroots, + f_mock_chroots_many, f_build_many_chroots, + f_users_api): + chroot_name_list = [c.name for c in self.c1.active_chroots] + metadata = { + "project_id": 1, + "enable_net": True, + "chroots": chroot_name_list + } + data = { + "metadata": json.dumps(metadata), + "srpm": (StringIO(u'my file contents'), 'hello world.src.rpm') + } + self.db.session.commit() + r0 = self.request_rest_api_with_auth( + "/api_2/builds", + method="post", + content_type="multipart/form-data", + data=data + ) + assert r0.status_code == 201 + r1 = self.tc.get(r0.headers["Location"]) + assert r1.status_code == 200 + build_obj = json.loads(r1.data) + + assert build_obj["build"]["source_type"] == BuildSourceEnum("srpm_upload") + + chroots_href = build_obj["_links"]["chroots"]["href"] + r2 = self.tc.get(chroots_href) + build_chroots_obj = json.loads(r2.data) + build_chroots_names = set([bc["chroot"]["name"] for bc in + build_chroots_obj["chroots"]]) + assert set(chroot_name_list) == build_chroots_names + assert len(chroot_name_list) == len(build_chroots_obj["chroots"]) + + def test_post_multipart_missing_file( + self,f_users, f_coprs, f_builds, f_db, + f_users_api, f_mock_chroots): + + metadata = { + "enable_net": True + } + data = { + "metadata": json.dumps(metadata), + } + self.db.session.commit() + r0 = self.request_rest_api_with_auth( + "/api_2/builds", + method="post", + content_type="multipart/form-data", + data=data + ) + assert r0.status_code == 400 + + def test_post_multipart_missing_metadata( + self,f_users, f_coprs, f_builds, f_db, + f_users_api, f_mock_chroots): + data = { + "srpm": (StringIO(u'my file contents'), 'hello world.src.rpm') + } + self.db.session.commit() + r0 = self.request_rest_api_with_auth( + "/api_2/builds", + method="post", + content_type="multipart/form-data", + data=data + ) + assert r0.status_code == 400 + def test_get_one(self, f_users, f_coprs, f_builds, f_db, - f_users_api, f_mock_chroots): + f_users_api, f_mock_chroots): build_id_list = [b.id for b in self.basic_builds] self.db.session.commit() diff --git a/frontend/coprs_frontend/tests/test_api/test_project_r.py b/frontend/coprs_frontend/tests/test_api/test_project_r.py index 71e2cc9..902b551 100644 --- a/frontend/coprs_frontend/tests/test_api/test_project_r.py +++ b/frontend/coprs_frontend/tests/test_api/test_project_r.py @@ -36,6 +36,7 @@ class TestProjectResource(CoprsTestCase): r = self.request_rest_api_with_auth("/api_2/projects", content=body, method="post") assert r.status_code == 201 + # todo: test header Location copr_dict = json.loads(r.data) assert copr_dict["copr"]["id"] == 1 r2 = self.tc.get("/api_2/projects/1/chroots") diff --git a/frontend/coprs_frontend/tests/test_logic/test_builds_logic.py b/frontend/coprs_frontend/tests/test_logic/test_builds_logic.py index 7c88ff9..b99955d 100644 --- a/frontend/coprs_frontend/tests/test_logic/test_builds_logic.py +++ b/frontend/coprs_frontend/tests/test_logic/test_builds_logic.py @@ -42,7 +42,6 @@ class TestBuildsLogic(CoprsTestCase): pkgs="blah", copr=self.c1, repos="repos", - memory_reqs=3000, timeout=5000) b = BuildsLogic.add(**params) @@ -57,7 +56,6 @@ class TestBuildsLogic(CoprsTestCase): pkgs="blah blah", copr=self.c1, repos="repos", - memory_reqs=3000, timeout=5000) with pytest.raises(MalformedArgumentException):
1
0
0
0
[copr] master: [frontend][api] Added tests for Build[List]Resource for the GET method (39d3a1d)
by vgologuz@fedoraproject.org
27 Aug '15
27 Aug '15
Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master >--------------------------------------------------------------- commit 39d3a1d94c852b8d7711763dd0e3ca7a90e4a115 Author: Valentin Gologuzov <vgologuz(a)redhat.com> Date: Thu Aug 27 14:20:39 2015 +0200 [frontend][api] Added tests for Build[List]Resource for the GET method >--------------------------------------------------------------- .../coprs/rest_api/resources/build.py | 29 +++--- frontend/coprs_frontend/coprs/rest_api/schemas.py | 2 +- frontend/coprs_frontend/tests/coprs_test_case.py | 7 +- .../coprs_frontend/tests/test_api/test_build_r.py | 109 ++++++++++++++++++-- 4 files changed, 118 insertions(+), 29 deletions(-) diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py index 01f274e..33fba81 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py @@ -16,6 +16,17 @@ from coprs.rest_api.util import get_one_safe from flask_restful import Resource, reqparse +def render_build(build): + return { + "build": BuildSchema().dump(build)[0], + "_links": { + "self": {"href": url_for(".buildr", build_id=build.id)}, + "project": {"href": url_for(".projectr", project_id=build.copr_id)}, + "chroots": {"href": url_for(".buildchrootlistr", build_id=build.id)} + } + } + + class BuildListR(Resource): def get(self): @@ -33,7 +44,7 @@ class BuildListR(Resource): if req_args["project_id"] is not None: copr = get_one_safe(CoprsLogic.get_by_id(req_args["project_id"])) query = BuildsLogic.get_multiple_by_copr(copr) - elif req_args["owner" ] is not None: + elif req_args["owner"] is not None: user = get_one_safe(UsersLogic.get(req_args["owner"])) query = BuildsLogic.get_multiple_by_owner(user) else: @@ -58,12 +69,7 @@ class BuildListR(Resource): return { "builds": [ - { - "build": BuildSchema().dump(build)[0], - "_links": { - "self": {"href": url_for(".buildr", build_id=build.id)}, - } - } for build in builds + render_build(build) for build in builds ], "_links": { "self": {"href": url_for(".buildlistr", **self_params)}, @@ -78,14 +84,7 @@ class BuildR(Resource): build = get_one_safe(BuildsLogic.get(build_id), "Not found build with id: {}".format(build_id)) - return { - "build": BuildSchema().dump(build)[0], - "_links": { - "self": {"href": url_for(".buildr", build_id=build_id)}, - "project": {"href": url_for(".projectr", project_id=build.copr_id)}, - "chroots": {"href": url_for(".buildchrootlistr", build_id=build_id)} - } - } + return render_build(build) # to get build details and cancel individual build chroots diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py index 4371f79..0872ee4 100644 --- a/frontend/coprs_frontend/coprs/rest_api/schemas.py +++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py @@ -75,7 +75,7 @@ class BuildChrootSchema(Schema): class BuildSchema(Schema): - id = fields.Int() + id = fields.Int(dump_only=True) state = fields.Str() pkgs = fields.Str() diff --git a/frontend/coprs_frontend/tests/coprs_test_case.py b/frontend/coprs_frontend/tests/coprs_test_case.py index 0bee47d..ca1156c 100644 --- a/frontend/coprs_frontend/tests/coprs_test_case.py +++ b/frontend/coprs_frontend/tests/coprs_test_case.py @@ -101,7 +101,9 @@ class CoprsTestCase(object): proven=False, mail="baz(a)bar.bar") - self.db.session.add_all([self.u1, self.u2, self.u3]) + self.basic_user_list = [self.u1, self.u2, self.u3] + + self.db.session.add_all(self.basic_user_list) @pytest.fixture def f_users_api(self): @@ -122,7 +124,8 @@ class CoprsTestCase(object): self.c2 = models.Copr(name=u"foocopr", owner=self.u2) self.c3 = models.Copr(name=u"barcopr", owner=self.u2) - self.db.session.add_all([self.c1, self.c2, self.c3]) + self.basic_coprs_list = [self.c1, self.c2, self.c3] + self.db.session.add_all(self.basic_coprs_list) @pytest.fixture def f_mock_chroots(self): diff --git a/frontend/coprs_frontend/tests/test_api/test_build_r.py b/frontend/coprs_frontend/tests/test_api/test_build_r.py index 52f18fa..0885f1b 100644 --- a/frontend/coprs_frontend/tests/test_api/test_build_r.py +++ b/frontend/coprs_frontend/tests/test_api/test_build_r.py @@ -2,35 +2,122 @@ import json from marshmallow import pprint +import math import pytest import sqlalchemy from coprs.logic.users_logic import UsersLogic from coprs.logic.coprs_logic import CoprsLogic +from coprs.logic.builds_logic import BuildsLogic from tests.coprs_test_case import CoprsTestCase, TransactionDecorator class TestBuildResource(CoprsTestCase): + @staticmethod + def extract_build_ids(response_object): + return set([ + b_dict["build"]["id"] + for b_dict in response_object["builds"] + ]) + def test_collection_ok(self, f_users, f_coprs, f_builds, f_db, f_users_api, f_mock_chroots): - # project_id = self.c1.id - - href = "/api_2/builds/chroots" - expected_len = len(self.basic_builds) + href = "/api_2/builds" self.db.session.commit() r = self.tc.get(href) + assert r.status_code == 200 + obj = json.loads(r.data) + + # not a pure test, but we test API here + builds = BuildsLogic.get_multiple().all() + expected_ids = set([b.id for b in builds]) + + assert expected_ids == self.extract_build_ids(obj) + + def test_collection_by_owner(self, f_users, f_coprs, f_builds, f_db, + f_users_api, f_mock_chroots): - def test_collection_ok_by_username( - self, f_users, f_coprs, f_builds, f_db, - f_users_api, f_mock_chroots): + names_list = [user.username for user in self.basic_user_list] + for user_name in names_list: + href = "/api_2/builds?owner={}".format(user_name) + self.db.session.commit() + r = self.tc.get(href) + assert r.status_code == 200 + obj = json.loads(r.data) - # project_id = self.c1.id + # not a pure test, but we test API here + builds = [ + b for b in BuildsLogic.get_multiple().all() + if b.copr.owner.username == user_name + ] + expected_ids = set([b.id for b in builds]) + assert expected_ids == self.extract_build_ids(obj) - href = "/api_2/builds?owner={}".format(self.u1.username) - expected_len = 2 + def test_collection_by_project_id(self, f_users, f_coprs, f_builds, f_db, + f_users_api, f_mock_chroots): + + project_id_list = [copr.id for copr in self.basic_coprs_list] + for id_ in project_id_list: + href = "/api_2/builds?project_id={}".format(id_) + self.db.session.commit() + r = self.tc.get(href) + assert r.status_code == 200 + obj = json.loads(r.data) + + # not a pure test, but we test API here + builds = [ + b for b in BuildsLogic.get_multiple().all() + if b.copr.id == id_ + ] + expected_ids = set([b.id for b in builds]) + assert expected_ids == self.extract_build_ids(obj) + + def test_collection_limit_offset(self, f_users, f_coprs, f_builds, f_db, + f_users_api, f_mock_chroots): self.db.session.commit() - r = self.tc.get(href) + builds = BuildsLogic.get_multiple().all() + total = len(builds) + + # test limit + for lim in range(1, total + 1): + href = "/api_2/builds?limit={}".format(lim) + r = self.tc.get(href) + assert r.status_code == 200 + obj = json.loads(r.data) + builds = obj["builds"] + assert len(builds) == lim + + if lim > 2: + delta = int(math.floor(lim / 2)) + href1 = "/api_2/builds?limit={}".format(delta) + href2 = "/api_2/builds?limit={0}&offset={0}".format(delta) + + r1 = self.tc.get(href1) + r2 = self.tc.get(href2) + + assert r1.status_code == 200 + assert r2.status_code == 200 + + obj1 = json.loads(r1.data) + obj2 = json.loads(r2.data) + + assert builds[:delta] == obj1["builds"] + assert builds[delta:2 * delta] == obj2["builds"] + + def test_get_one(self, f_users, f_coprs, f_builds, f_db, + f_users_api, f_mock_chroots): + + build_id_list = [b.id for b in self.basic_builds] + self.db.session.commit() + + for b_id in build_id_list: + href = "/api_2/builds/{}".format(b_id) + r = self.tc.get(href) + assert r.status_code == 200 + obj = json.loads(r.data) + assert obj["build"]["id"] == b_id + assert obj["_links"]["self"]["href"] == href
1
0
0
0
[copr] master: [frontend][api] Updated and added tests for BuildChroot resource (ed6d74e)
by vgologuz@fedoraproject.org
26 Aug '15
26 Aug '15
Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master >--------------------------------------------------------------- commit ed6d74ef21a1de107382cb356713afb5f8142b1c Author: Valentin Gologuzov <vgologuz(a)redhat.com> Date: Wed Aug 26 16:02:13 2015 +0200 [frontend][api] Updated and added tests for BuildChroot resource >--------------------------------------------------------------- .../coprs_frontend/coprs/logic/builds_logic.py | 19 ++++- .../coprs/rest_api/resources/build_chroot.py | 68 +++++++++------ frontend/coprs_frontend/coprs/rest_api/schemas.py | 6 +- frontend/coprs_frontend/tests/coprs_test_case.py | 1 + .../tests/test_api/test_build_chroot_r.py | 91 ++++++++++++++++++++ .../coprs_frontend/tests/test_api/test_build_r.py | 36 ++++++++ .../tests/test_api/test_mock_chroot_r.py | 9 +-- 7 files changed, 194 insertions(+), 36 deletions(-) diff --git a/frontend/coprs_frontend/coprs/logic/builds_logic.py b/frontend/coprs_frontend/coprs/logic/builds_logic.py index be28dbd..0369205 100644 --- a/frontend/coprs_frontend/coprs/logic/builds_logic.py +++ b/frontend/coprs_frontend/coprs/logic/builds_logic.py @@ -7,6 +7,7 @@ import pprint import time from sqlalchemy import or_ from sqlalchemy import and_ +from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.sql import false from coprs import app @@ -15,12 +16,15 @@ from coprs import exceptions from coprs import models from coprs import helpers from coprs.constants import DEFAULT_BUILD_TIMEOUT, MAX_BUILD_TIMEOUT +from coprs.exceptions import MalformedArgumentException from coprs.helpers import StatusEnum from coprs.logic import coprs_logic from coprs.logic import users_logic from coprs.logic import packages_logic from coprs.logic.actions_logic import ActionsLogic +from coprs.models import BuildChroot +from .coprs_logic import MockChrootsLogic log = app.logger @@ -382,6 +386,18 @@ class BuildsLogic(object): return None +class BuildChrootsLogic(object): + @classmethod + def get_by_build_id_and_name(cls, build_id, name): + mc = MockChrootsLogic.get_from_name(name).one() + + return ( + BuildChroot.query + .filter(BuildChroot.build_id == build_id) + .filter(BuildChroot.mock_chroot_id == mc.id) + ) + + class BuildsMonitorLogic(object): @classmethod def get_monitor_data(cls, copr): @@ -390,7 +406,8 @@ class BuildsMonitorLogic(object): for pkg in copr_packages: chroots = {} for ch in copr.active_chroots: - query = (models.BuildChroot.query.join(models.Build) + query = ( + models.BuildChroot.query.join(models.Build) .filter(models.Build.package_id == pkg.id) .filter(models.BuildChroot.mock_chroot_id == ch.id) .filter(models.BuildChroot.status != helpers.StatusEnum("canceled"))) diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py index 9baf263..751ecaa 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py @@ -6,7 +6,11 @@ from flask import url_for from flask_restful import Resource, reqparse from marshmallow import Schema, fields, pprint -from coprs.logic.builds_logic import BuildsLogic + +from ... import models +from coprs.exceptions import MalformedArgumentException +from coprs.logic.builds_logic import BuildsLogic, BuildChrootsLogic +from coprs.rest_api.exceptions import MalformedRequest from coprs.rest_api.schemas import MockChrootSchema, CoprChrootSchema, BuildChrootSchema from coprs.views.misc import api_login_required @@ -15,23 +19,31 @@ from coprs.logic.coprs_logic import MockChrootsLogic, CoprChrootsLogic, CoprsLog from ..util import get_one_safe, json_loads_safe, mm_deserialize, mm_serialize_one +def render_build_chroot(chroot): + """ + :type chroot: models.BuildChroot + """ + return { + "chroot": mm_serialize_one(BuildChrootSchema, chroot), + "_links": { + "project": {"href": url_for(".projectr", project_id=chroot.build.copr_id)}, + "self": {"href": url_for(".buildchrootr", + build_id=chroot.build.id, + name=chroot.name)}, + } + } + + class BuildChrootListR(Resource): def get(self, build_id): build = get_one_safe(BuildsLogic.get(build_id), - "Not found build with id: {}".format(build_id)) + "Build {} Not found".format(build_id)) return { "chroots": [ - { - "chroot": mm_serialize_one(BuildChrootSchema, chroot), - "_links": { - "project": {"href": url_for(".projectr", project_id=build.copr_id)}, - "self": {"href": url_for(".buildchrootr", - build_id=build.id, - name=chroot.name)}, - } - } for chroot in build.build_chroots - ], + render_build_chroot(chroot) + for chroot in build.build_chroots + ], "_links": { "self": {"href": url_for(".buildchrootlistr", build_id=build_id)} } @@ -39,17 +51,21 @@ class BuildChrootListR(Resource): class BuildChrootR(Resource): - pass - # def get(self, project_id, name): - # copr = get_one_safe(CoprsLogic.get_by_id(int(project_id))) - # chroot = CoprChrootsLogic.get_by_name_safe(copr, name) - # - # return { - # "chroot": mm_serialize_one(CoprChrootSchema, chroot), - # "_links": { - # "project": {"href": url_for(".projectr", project_id=copr.id)}, - # "self": {"href": url_for(".projectchrootr", - # project_id=project_id, - # name=chroot.name)}, - # } - # } + + @staticmethod + def _get_chroot_safe(build_id, name): + try: + chroot = get_one_safe( + BuildChrootsLogic.get_by_build_id_and_name(build_id, name), + "Build chroot {} for build {} not found" + ) + except MalformedArgumentException as err: + raise MalformedRequest("Bad mock chroot name: {}".format(err)) + return chroot + + def get(self, build_id, name): + chroot = self._get_chroot_safe(build_id, name) + return render_build_chroot(chroot) + + # todo: add put method: allows only to pass status: cancelled to cancel build + diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py index 160ef14..4371f79 100644 --- a/frontend/coprs_frontend/coprs/rest_api/schemas.py +++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py @@ -76,12 +76,14 @@ class BuildChrootSchema(Schema): class BuildSchema(Schema): id = fields.Int() + state = fields.Str() + pkgs = fields.Str() build_packages = fields.Str() pkg_version = fields.Str() repos_list = fields.List(fields.Str()) - repos = fields.Str() # legacy + repos = fields.Str() # todo: replace with SpaceSeparatedList submitted_on = fields.Int() started_on = fields.Int() @@ -94,3 +96,5 @@ class BuildSchema(Schema): source_type = fields.Int() source_json = fields.Str() + + # owner = fields.Function(lambda b: b.copr.owner.username, dump_only=True) diff --git a/frontend/coprs_frontend/tests/coprs_test_case.py b/frontend/coprs_frontend/tests/coprs_test_case.py index 52fceb2..0bee47d 100644 --- a/frontend/coprs_frontend/tests/coprs_test_case.py +++ b/frontend/coprs_frontend/tests/coprs_test_case.py @@ -205,6 +205,7 @@ class CoprsTestCase(object): self.b4 = models.Build( copr=self.c2, package=self.p2, user=self.u2, submitted_on=100) + self.basic_builds = [self.b1, self.b2, self.b3, self.b4] self.b1_bc = [] self.b2_bc = [] self.b3_bc = [] diff --git a/frontend/coprs_frontend/tests/test_api/test_build_chroot_r.py b/frontend/coprs_frontend/tests/test_api/test_build_chroot_r.py new file mode 100644 index 0000000..6322504 --- /dev/null +++ b/frontend/coprs_frontend/tests/test_api/test_build_chroot_r.py @@ -0,0 +1,91 @@ +# coding: utf-8 +import copy + +import json +from marshmallow import pprint + +import pytest +import sqlalchemy + +from coprs.logic.users_logic import UsersLogic +from coprs.logic.coprs_logic import CoprsLogic + +from tests.coprs_test_case import CoprsTestCase, TransactionDecorator + + +class TestBuildChrootResource(CoprsTestCase): + + def test_collection_ok(self, f_users, f_coprs, f_mock_chroots, f_builds, f_db, + f_users_api): + + # project_id = self.c1.id + # import ipdb; ipdb.set_trace() + href = "/api_2/builds/1/chroots" + bc_list = copy.deepcopy(self.b1_bc) + + self.db.session.commit() + + r0 = self.tc.get(href) + assert r0.status_code == 200 + obj = json.loads(r0.data) + assert len(obj["chroots"]) == len(bc_list) + assert obj["_links"]["self"]["href"] == href + + def test_post_not_allowed(self, f_users, f_coprs, f_mock_chroots, f_builds, f_db, + f_users_api): + self.db.session.commit() + r0 = self.request_rest_api_with_auth( + "/api_2/builds/1/chroots", + method="post", + content={}, + ) + assert r0.status_code == 405 + + def test_get_one(self, f_users, f_coprs, f_mock_chroots, f_builds, f_db, + f_users_api): + expected_fields = [ + "state", + "git_hash", + "started_on", + "ended_on", + ] + bc = self.b1_bc[0] + expected_dict = { + f: getattr(bc, f) + for f in expected_fields + } + href = "/api_2/builds/1/chroots/{}".format(bc.name) + self.db.session.commit() + + r0 = self.tc.get(href) + assert r0.status_code == 200 + obj = json.loads(r0.data) + assert obj["chroot"] == expected_dict + + def test_get_one_bad_name(self, f_users, f_coprs, f_mock_chroots, f_builds, f_db, + f_users_api): + + href = "/api_2/builds/1/chroots/surely_bad_chroot_name" + self.db.session.commit() + + r0 = self.tc.get(href) + assert r0.status_code == 400 + + def get_test_not_found( + self, f_users, f_coprs, f_mock_chroots, f_builds, f_db, f_users_api): + + bc = self.b1_bc[0] + href = "/api_2/builds/48545656/chroots/{}".format(bc.name) + href2 = "/api_2/builds/1/chroots/{}".format(bc.name) + self.db.session.commit() + + r0 = self.tc.get(href) + assert r0.status_code == 404 + + r1 = self.tc.get(href2) + assert r1.status_code == 404 + + + # def test_put_cancel_build_chroot + + diff --git a/frontend/coprs_frontend/tests/test_api/test_build_r.py b/frontend/coprs_frontend/tests/test_api/test_build_r.py new file mode 100644 index 0000000..52f18fa --- /dev/null +++ b/frontend/coprs_frontend/tests/test_api/test_build_r.py @@ -0,0 +1,36 @@ +# coding: utf-8 + +import json +from marshmallow import pprint + +import pytest +import sqlalchemy + +from coprs.logic.users_logic import UsersLogic +from coprs.logic.coprs_logic import CoprsLogic + +from tests.coprs_test_case import CoprsTestCase, TransactionDecorator + + +class TestBuildResource(CoprsTestCase): + + def test_collection_ok(self, f_users, f_coprs, f_builds, f_db, + f_users_api, f_mock_chroots): + + # project_id = self.c1.id + + href = "/api_2/builds/chroots" + expected_len = len(self.basic_builds) + self.db.session.commit() + r = self.tc.get(href) + + def test_collection_ok_by_username( + self, f_users, f_coprs, f_builds, f_db, + f_users_api, f_mock_chroots): + + # project_id = self.c1.id + + href = "/api_2/builds?owner={}".format(self.u1.username) + expected_len = 2 + self.db.session.commit() + r = self.tc.get(href) diff --git a/frontend/coprs_frontend/tests/test_api/test_mock_chroot_r.py b/frontend/coprs_frontend/tests/test_api/test_mock_chroot_r.py index 3c4061c..beb3568 100644 --- a/frontend/coprs_frontend/tests/test_api/test_mock_chroot_r.py +++ b/frontend/coprs_frontend/tests/test_api/test_mock_chroot_r.py @@ -36,17 +36,10 @@ class TestMockChrootResource(CoprsTestCase): assert len(obj["chroots"]) == expected_len def test_post_not_allowed(self, f_mock_chroots, f_db, f_users, f_users_api): - data = { - u'arch': u'i386', - u'is_active': True, - u'name': u'fedora-99-i386', - u'os_release': u'fedora', - u'os_version': u'99' - } r0 = self.request_rest_api_with_auth( "/api_2/mock_chroots", method="post", - content=data, + content={}, ) assert r0.status_code == 405
1
0
0
0
[copr] master: [frontend][api] update MockChroot Resource, covered with tests (c726d16)
by vgologuz@fedoraproject.org
26 Aug '15
26 Aug '15
Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master >--------------------------------------------------------------- commit c726d16b20e8daeb56b159521721877ac05c58c1 Author: Valentin Gologuzov <vgologuz(a)redhat.com> Date: Wed Aug 26 14:21:59 2015 +0200 [frontend][api] update MockChroot Resource, covered with tests >--------------------------------------------------------------- .../coprs/rest_api/resources/mock_chroot.py | 44 +++++++++------ frontend/coprs_frontend/tests/coprs_test_case.py | 1 + .../tests/test_api/test_mock_chroot_r.py | 60 ++++++++++++++++++++ ..._project_chroot.py => test_project_chroot_r.py} | 0 4 files changed, 88 insertions(+), 17 deletions(-) diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py index 0ffa083..0ee1d0d 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py @@ -14,21 +14,38 @@ from coprs.logic.coprs_logic import MockChrootsLogic, CoprChrootsLogic, CoprsLog from ..util import get_one_safe, json_loads_safe, mm_deserialize +def render_mock_chroot(chroot): + return { + "chroot": MockChrootSchema().dump(chroot)[0], + "_links": { + "self": {"href": url_for(".mockchrootr", name=chroot.name)}, + "all_chroots": {"href": url_for(".mockchrootlistr")} + }, + } + + class MockChrootListR(Resource): + def get(self): - # todo: add argument active_only - chroots = MockChrootsLogic.get_multiple(active_only=False).all() + parser = reqparse.RequestParser() + parser.add_argument('active_only', type=bool) + req_args = parser.parse_args() + active_only = False + if req_args["active_only"]: + active_only = True + + chroots = MockChrootsLogic.get_multiple(active_only=active_only).all() + + self_extra = {} + if active_only: + self_extra["active_only"] = active_only return { "_links": { - "self": {"href": url_for(".mockchrootlistr")}, + "self": {"href": url_for(".mockchrootlistr", **self_extra)}, }, "chroots": [ - { - "chroot": MockChrootSchema().dump(chroot)[0], - "_links": { - "self": {"href": url_for(".mockchrootr", name=chroot.name)}, - } - } for chroot in chroots + render_mock_chroot(chroot) + for chroot in chroots ] } @@ -36,11 +53,4 @@ class MockChrootListR(Resource): class MockChrootR(Resource): def get(self, name): chroot = get_one_safe(MockChrootsLogic.get_from_name(name)) - return { - "chroot": MockChrootSchema().dump(chroot)[0], - "_links": { - "self": {"href": url_for(".mockchrootr", name=chroot.name)}, - "all_chroots": {"href": url_for(".mockchrootlistr")} - }, - } - + return render_mock_chroot(chroot) diff --git a/frontend/coprs_frontend/tests/coprs_test_case.py b/frontend/coprs_frontend/tests/coprs_test_case.py index 37db540..52fceb2 100644 --- a/frontend/coprs_frontend/tests/coprs_test_case.py +++ b/frontend/coprs_frontend/tests/coprs_test_case.py @@ -135,6 +135,7 @@ class CoprsTestCase(object): self.mc4 = models.MockChroot( os_release="fedora", os_version="rawhide", arch="i386", is_active=True) + self.mc_basic_list = [self.mc1, self.mc2, self.mc3, self.mc4] # only bind to coprs if the test has used the f_coprs fixture if hasattr(self, "c1"): cc1 = models.CoprChroot() diff --git a/frontend/coprs_frontend/tests/test_api/test_mock_chroot_r.py b/frontend/coprs_frontend/tests/test_api/test_mock_chroot_r.py new file mode 100644 index 0000000..3c4061c --- /dev/null +++ b/frontend/coprs_frontend/tests/test_api/test_mock_chroot_r.py @@ -0,0 +1,60 @@ +# coding: utf-8 + +import json +from marshmallow import pprint + +import pytest +import sqlalchemy + +from coprs.logic.users_logic import UsersLogic +from coprs.logic.coprs_logic import CoprsLogic + +from tests.coprs_test_case import CoprsTestCase, TransactionDecorator + + +class TestMockChrootResource(CoprsTestCase): + + def test_collection(self, f_mock_chroots, f_db): + href = "/api_2/mock_chroots" + r = self.tc.get(href) + assert r.status_code == 200 + obj = json.loads(r.data) + assert obj["_links"]["self"]["href"] == href + assert len(obj["chroots"]) == len(self.mc_basic_list) + + def test_collection_only_active(self, f_mock_chroots, f_db): + expected_len = len(self.mc_basic_list) - 1 + self.mc4.is_active = False + self.db.session.add(self.mc4) + self.db.session.commit() + + href = "/api_2/mock_chroots?active_only=True" + r = self.tc.get(href) + assert r.status_code == 200 + obj = json.loads(r.data) + assert obj["_links"]["self"]["href"] == href + assert len(obj["chroots"]) == expected_len + + def test_post_not_allowed(self, f_mock_chroots, f_db, f_users, f_users_api): + data = { + u'arch': u'i386', + u'is_active': True, + u'name': u'fedora-99-i386', + u'os_release': u'fedora', + u'os_version': u'99' + } + r0 = self.request_rest_api_with_auth( + "/api_2/mock_chroots", + method="post", + content=data, + ) + assert r0.status_code == 405 + + def test_get_one(self, f_mock_chroots, f_db): + chroot_name = self.mc1.name + href = "/api_2/mock_chroots/{}".format(chroot_name) + r = self.tc.get(href) + assert r.status_code == 200 + obj = json.loads(r.data) + assert obj["_links"]["self"]["href"] == href + assert obj["chroot"]["name"] == chroot_name diff --git a/frontend/coprs_frontend/tests/test_api/test_project_chroot.py b/frontend/coprs_frontend/tests/test_api/test_project_chroot_r.py similarity index 100% rename from frontend/coprs_frontend/tests/test_api/test_project_chroot.py rename to frontend/coprs_frontend/tests/test_api/test_project_chroot_r.py
1
0
0
0
[copr] master: [frontend][api] require marshmallow >= 2; minor test update (32c392c)
by vgologuz@fedoraproject.org
26 Aug '15
26 Aug '15
Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master >--------------------------------------------------------------- commit 32c392cbfecf6574910d948132ae831ff3ec3713 Author: Valentin Gologuzov <vgologuz(a)redhat.com> Date: Wed Aug 26 13:38:14 2015 +0200 [frontend][api] require marshmallow >= 2; minor test update >--------------------------------------------------------------- frontend/copr-frontend.spec | 4 ++-- .../coprs/rest_api/resources/project_chroot.py | 5 +++-- frontend/coprs_frontend/coprs/rest_api/util.py | 4 ++-- .../tests/test_api/test_project_chroot.py | 9 +++++++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/frontend/copr-frontend.spec b/frontend/copr-frontend.spec index def204d..4a6b670 100644 --- a/frontend/copr-frontend.spec +++ b/frontend/copr-frontend.spec @@ -54,7 +54,7 @@ Requires: pytz Requires: python-six Requires: python-netaddr Requires: python-flask-restful -Requires: python-marshmallow +Requires: python-marshmallow >= 2.0.0 # for tests: Requires: pytest Requires: python-flexmock @@ -88,7 +88,7 @@ BuildRequires: python-decorator BuildRequires: python-markdown BuildRequires: pytz BuildRequires: python-flask-restful -BuildRequires: python-marshmallow +BuildRequires: python-marshmallow >= 2.0.0 %description COPR is lightweight build system. It allows you to create new project in WebUI, diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py index 8958b96..6e03ee1 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py @@ -1,6 +1,9 @@ # coding: utf-8 import json +import logging +log = logging.getLogger(__name__) + import flask from flask import url_for, make_response from flask_restful import Resource, reqparse @@ -121,8 +124,6 @@ class ProjectChrootR(Resource): chroot = self._get_chroot_safe(copr, name) chroot_data = mm_deserialize(CoprChrootSchema(), flask.request.data) - if "name" in chroot_data.data: - chroot_data.data.pop("name") # todo: why it's here? bug in marshmallow? try: updated_chroot = CoprChrootsLogic.update_chroot( user=flask.g.user, diff --git a/frontend/coprs_frontend/coprs/rest_api/util.py b/frontend/coprs_frontend/coprs/rest_api/util.py index c74b2cb..a7e77b6 100644 --- a/frontend/coprs_frontend/coprs/rest_api/util.py +++ b/frontend/coprs_frontend/coprs/rest_api/util.py @@ -34,8 +34,8 @@ def json_loads_safe(raw, data_on_error=None): "Failed to deserialize json string") -def mm_deserialize(schema, obj_dict): - result = schema.loads(obj_dict) +def mm_deserialize(schema, json_string): + result = schema.loads(json_string) if result.errors: raise MalformedRequest(data="Failed to parse request: {}" .format(result.errors)) diff --git a/frontend/coprs_frontend/tests/test_api/test_project_chroot.py b/frontend/coprs_frontend/tests/test_api/test_project_chroot.py index d28b892..3f84822 100644 --- a/frontend/coprs_frontend/tests/test_api/test_project_chroot.py +++ b/frontend/coprs_frontend/tests/test_api/test_project_chroot.py @@ -103,6 +103,15 @@ class TestProjectChrootResource(CoprsTestCase): assert r3.status_code == 200 assert_content(json.loads(r3.data)["chroot"]) + # test put with excessive name field + data["name"] = chroot_name + r4 = self.request_rest_api_with_auth( + "/api_2/projects/1/chroots/{}".format(chroot_name), + method="put", + content=data + ) + assert r4.status_code == 200 + def test_put_erasing(self, f_users, f_coprs, f_db, f_users_api, f_mock_chroots): chroot_name = self.mc1.name self.db.session.commit()
1
0
0
0
[copr] master: typo (9a9aba4)
by asamalik@fedoraproject.org
26 Aug '15
26 Aug '15
Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master >--------------------------------------------------------------- commit 9a9aba44125210e0420fdb1fe61e3917cc74c719 Author: Adam Samalik <asamalik(a)redhat.com> Date: Wed Aug 26 12:19:24 2015 +0200 typo >--------------------------------------------------------------- .../coprs/views/coprs_ns/coprs_general.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py index dd299dc..5a85b87 100644 --- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py +++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py @@ -155,7 +155,7 @@ def copr_new(username): ) db.session.commit() - flask.flash("New project has ben created successfully.", "success") + flask.flash("New project has been created successfully.", "success") if form.initial_pkgs.data: pkgs = form.initial_pkgs.data.replace("\n", " ").split(" ")
1
0
0
0
← Newer
1
2
3
4
...
11
Older →
Jump to page:
1
2
3
4
5
6
7
8
9
10
11
Results per page:
10
25
50
100
200