From b95e0fc459d2423c9ac07a35d52ad5659bb7c745 Mon Sep 17 00:00:00 2001 From: petak5 Date: Mon, 29 Jun 2020 19:52:38 +0200 Subject: [PATCH 01/19] Optionally can get JWT timeout time from environment file --- src/api/views/oauthView.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/api/views/oauthView.py b/src/api/views/oauthView.py index d6c89c5..77d720e 100644 --- a/src/api/views/oauthView.py +++ b/src/api/views/oauthView.py @@ -16,6 +16,9 @@ app.register_blueprint(github_blueprint, url_prefix="/login") app.config['JWT_SECRET_KEY'] = environ.get("JWT_SECRET_KEY") +# JWT token expiration time in seconds - default is 15 minutes +if environ.get("JWT_ACCESS_TOKEN_EXPIRES"): + app.config["JWT_ACCESS_TOKEN_EXPIRES"] = environ.get("JWT_ACCESS_TOKEN_EXPIRES") jwt = JWTManager(app) @jwt.user_identity_loader @@ -80,7 +83,7 @@ def register_callback(blueprint): redirect_token = f"?state={session.pop('state')}" if user is None: user, msg, code = userController.create_user(github_id=id, name=session.pop('username', "Anton")) - + if user is None: redirect_token += f"&msg={msg}&code={code}" else: From 22954665a8238ab9fbd2166587c41e64a5597bc1 Mon Sep 17 00:00:00 2001 From: petak5 Date: Mon, 29 Jun 2020 19:53:06 +0200 Subject: [PATCH 02/19] Formatting fixes --- src/api/controllers/userController.py | 2 +- src/api/views/userView.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/controllers/userController.py b/src/api/controllers/userController.py index da4f01e..59032e4 100644 --- a/src/api/controllers/userController.py +++ b/src/api/controllers/userController.py @@ -108,7 +108,7 @@ def get_all_links(self, user_id): user = User.query.filter_by(id=user_id).first() if user is None: return None, "User not found", 404 - + return user.links, "OK", 200 def delete_link(self, user_id, **kwargs): diff --git a/src/api/views/userView.py b/src/api/views/userView.py index c21143e..b126432 100644 --- a/src/api/views/userView.py +++ b/src/api/views/userView.py @@ -88,7 +88,7 @@ def delete_user(): 404: description: User the token belonged to doesn't exist anymore """ - + return wrap_response(*userController.delete_user(get_jwt_identity())) # User Link From 27571b53bfa23f9c0be4b5022b27017fee5b9c7d Mon Sep 17 00:00:00 2001 From: petak5 Date: Mon, 29 Jun 2020 19:54:19 +0200 Subject: [PATCH 03/19] Don't create new DB session --- src/api/controllers/userController.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/controllers/userController.py b/src/api/controllers/userController.py index 59032e4..0c54a65 100644 --- a/src/api/controllers/userController.py +++ b/src/api/controllers/userController.py @@ -4,7 +4,7 @@ from flask import jsonify class UserController: - session = db.session() + session = db.session # User def create_user(self, **kwargs): From 1a7feea8ce0ac0f9b6ab472533c3f7cc219380aa Mon Sep 17 00:00:00 2001 From: petak5 Date: Mon, 29 Jun 2020 19:57:41 +0200 Subject: [PATCH 04/19] Add info about setting JWT token timeout to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 78cccd5..6a5d4d9 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ Run the server: Your `.env` file should now look something like [example.env](https://github.com/ProgrammingBuddies/programmingbuddies-api/blob/develop/example.env) +- Optionally you can set the JWT token timeout + - `JWT_ACCESS_TOKEN_EXPIRES` - time in seconds + ### Testing - to run multiple tests just specify the directory which contains them for example `pipenv run pytest tests/` From 38b6c9958814d38fb1d76d8d3b7d48005ada7a09 Mon Sep 17 00:00:00 2001 From: petak5 Date: Mon, 29 Jun 2020 21:49:33 +0200 Subject: [PATCH 05/19] Fixed and made more verbose JWT token timeout environment variable setting --- src/api/views/oauthView.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/views/oauthView.py b/src/api/views/oauthView.py index 77d720e..b69ed9b 100644 --- a/src/api/views/oauthView.py +++ b/src/api/views/oauthView.py @@ -17,8 +17,8 @@ app.config['JWT_SECRET_KEY'] = environ.get("JWT_SECRET_KEY") # JWT token expiration time in seconds - default is 15 minutes -if environ.get("JWT_ACCESS_TOKEN_EXPIRES"): - app.config["JWT_ACCESS_TOKEN_EXPIRES"] = environ.get("JWT_ACCESS_TOKEN_EXPIRES") +if environ.get("JWT_ACCESS_TOKEN_EXPIRES").isdigit(): + app.config["JWT_ACCESS_TOKEN_EXPIRES"] = int(environ.get("JWT_ACCESS_TOKEN_EXPIRES")) jwt = JWTManager(app) @jwt.user_identity_loader From 23d8aecd85574cd998966d3a510a3fc5780ae56c Mon Sep 17 00:00:00 2001 From: petak5 Date: Mon, 29 Jun 2020 21:56:20 +0200 Subject: [PATCH 06/19] Refactored create project route --- src/api/controllers/projectController.py | 22 ++++++++++++++++------ src/api/views/projectView.py | 15 ++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/api/controllers/projectController.py b/src/api/controllers/projectController.py index c8d69f9..294d21d 100644 --- a/src/api/controllers/projectController.py +++ b/src/api/controllers/projectController.py @@ -1,18 +1,28 @@ -from api.models import db, Project, UserHasProject, ProjectLink, ProjectFeedback +from api.models import db, User, Project, UserHasProject, ProjectLink, ProjectFeedback class ProjectController: - session = db.session() + session = db.session # Project - def create_project(self, **kwargs): + def create_project(self, user_id, **kwargs): try: + user = User.query.filter_by(id=user_id).first() + + if user == None: + return None, "User not found", 404 + project = Project(**kwargs) - self.session.add(project) + + userHasProject = UserHasProject(role=1) + userHasProject.project = project + userHasProject.user = user + + self.session.add(userHasProject) self.session.commit() - return project + return project, "OK", 201 except: - return None + return None, "Project creation failed", 400 def update_project(self, id, **kwargs): project = Project.query.filter_by(id=id).first() diff --git a/src/api/views/projectView.py b/src/api/views/projectView.py index 587a6f4..f10ba0d 100644 --- a/src/api/views/projectView.py +++ b/src/api/views/projectView.py @@ -1,9 +1,13 @@ from flask import request, jsonify +from flask_jwt_extended import get_jwt_identity, jwt_required from api import app +from api.utils import wrap_response, body_required from api.controllers import projectController # Project -@app.route("/projects", methods=['POST']) +@app.route("/project", methods=['POST']) +@jwt_required +@body_required def create_project(): """ Create project @@ -65,13 +69,10 @@ def create_project(): description: Project created successfully 400: description: Failed to create project + 404: + description: User doesn't exist """ - project = projectController.create_project(**request.get_json()) - - if project == None: - return "Failed to create project.", 400 - else: - return jsonify(project.as_dict()), 201 + return wrap_response(*projectController.create_project(get_jwt_identity(), **request.get_json())) @app.route("/projects/", methods=['PUT']) def update_project(id): From 5536927e75a0364b60ebd5ea7375cc2a121e9773 Mon Sep 17 00:00:00 2001 From: petak5 Date: Fri, 3 Jul 2020 07:30:39 +0200 Subject: [PATCH 07/19] Fix typos in response messages --- src/api/controllers/userController.py | 12 ++++++------ src/api/views/userView.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/api/controllers/userController.py b/src/api/controllers/userController.py index 0c54a65..aa165c2 100644 --- a/src/api/controllers/userController.py +++ b/src/api/controllers/userController.py @@ -16,17 +16,17 @@ def create_user(self, **kwargs): return user, "OK", 200 except: self.session.rollback() - return None, "Forbidden Attributes", 400 + return None, "Forbidden attributes", 400 def update_user(self, id, **kwargs): user = User.query.filter_by(id=id).first() if user == None: - return None, "user not found", 404 + return None, "User not found", 404 for key, value in kwargs.items(): if not hasattr(user, key): - return None, "forbidden attribute", 400 + return None, f"Forbidden attribute '{key}'", 400 for key, value in kwargs.items(): setattr(user, key, value) @@ -39,7 +39,7 @@ def get_user(self, **kwargs): user = User.query.filter_by(**kwargs).first() if user is None: - return None, "User Not Found", 404 + return None, "User not found", 404 return user, "OK", 200 @@ -60,7 +60,7 @@ def delete_user(self, id): user = User.query.filter_by(id=id).first() if user == None: - return None, "user not found", 404 + return None, "User not found", 404 db.session.delete(user) db.session.commit() @@ -81,7 +81,7 @@ def create_link(self, user_id, **kwargs): return None, "Forbidden attributes used in request. only name and url allowed.", 400 except: self.session.rollback() - return None, "link creation failed", 500 + return None, "Link creation failed", 500 def update_link(self, user_id, **kwargs): if not 'id' in kwargs: diff --git a/src/api/views/userView.py b/src/api/views/userView.py index b126432..c9b77e8 100644 --- a/src/api/views/userView.py +++ b/src/api/views/userView.py @@ -152,7 +152,7 @@ def update_user_link(): description: (optional) Name of the user link url: type: string - description: (optional)Url of the user link + description: (optional) Url of the user link responses: 200: description: User link updated successfully From f266f5e398bbafa1aa837d0756ed50f427c41df2 Mon Sep 17 00:00:00 2001 From: petak5 Date: Fri, 3 Jul 2020 07:35:55 +0200 Subject: [PATCH 08/19] Update project route refactor --- src/api/controllers/projectController.py | 33 +++++++++++------------ src/api/views/projectView.py | 34 +++++++++--------------- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/api/controllers/projectController.py b/src/api/controllers/projectController.py index 294d21d..f867309 100644 --- a/src/api/controllers/projectController.py +++ b/src/api/controllers/projectController.py @@ -24,35 +24,34 @@ def create_project(self, user_id, **kwargs): except: return None, "Project creation failed", 400 - def update_project(self, id, **kwargs): - project = Project.query.filter_by(id=id).first() + def update_project(self, user_id, **kwargs): + if 'user_id' in kwargs: + return None, "Failed to update project. Request body can not specify user's id.", 400 - if project == None: - return None - - for key, value in kwargs.items(): - if not hasattr(project, key): - return None - - for key, value in kwargs.items(): - setattr(project, key, value) + if 'id' not in kwargs: + return None, "Failed to update project. Request body must specify project's id.", 400 - db.session.commit() + user = User.query.filter_by(id=user_id).first() - return project + if user == None: + return None, "User not found", 404 - def update_project(self, id, **kwargs): - project = Project.query.filter_by(id=id).first() + project = Project.query.filter_by(id=kwargs["id"]).first() if project == None: - return project + return None, "Project not found", 404 + + # Note that we are updating the id too, but to the same id because we used it to query the user with it + for key, value in kwargs.items(): + if not hasattr(project, key): + return None, f"Forbidden attribute '{key}'", 400 for key, value in kwargs.items(): setattr(project, key, value) db.session.commit() - return project + return project, "OK", 200 def get_project(self, **kwargs): project = Project.query.filter_by(**kwargs).first() diff --git a/src/api/views/projectView.py b/src/api/views/projectView.py index f10ba0d..c46ac5b 100644 --- a/src/api/views/projectView.py +++ b/src/api/views/projectView.py @@ -11,6 +11,7 @@ def create_project(): """ Create project + Current user creates project --- tags: - Project @@ -36,16 +37,16 @@ def create_project(): description: Description of the project languages: type: string - description: List of programming languages the project uses + description: (Optional) List of programming languages the project uses development_status: type: integer description: Development status of the project creation_date: type: string - description: Creation date of the project + description: (Optional) Creation date of the project release_date: type: string - description: Release date of the project + description: (Optional) Release date of the project repository: type: string description: Url of the project's repository @@ -72,22 +73,19 @@ def create_project(): 404: description: User doesn't exist """ - return wrap_response(*projectController.create_project(get_jwt_identity(), **request.get_json())) + return wrap_response(*projectController.create_project(user_id=get_jwt_identity(), **request.get_json())) -@app.route("/projects/", methods=['PUT']) -def update_project(id): +@app.route("/project", methods=['PUT']) +@jwt_required +@body_required +def update_project(): """ Update project - Updates project with `id` using the data in request body + Updates current user's project with the data in request body --- tags: - Project parameters: - - in: path - name: id - type: integer - required: true - description: Id of project to update - in: body name: Project required: true @@ -99,16 +97,10 @@ def update_project(id): description: Project updated successfully 400: description: Failed to update project + 404: + description: Current user or requested project not found """ - if 'id' in request.get_json(): - return "Failed to update project. Request body can not specify project's id.", 501 - - project = projectController.update_project(id, **request.get_json()) - - if project == None: - return "Failed to update project.", 400 - else: - return jsonify(project.as_dict()), 200 + return wrap_response(*projectController.update_project(user_id=get_jwt_identity(), **request.get_json())) @app.route("/projects/", methods=['GET']) def get_project(id): From beccc654d8f96bad47879ba7fe7bc2ed87855a28 Mon Sep 17 00:00:00 2001 From: petak5 Date: Fri, 3 Jul 2020 07:56:53 +0200 Subject: [PATCH 09/19] Get projects routes refactor --- src/api/controllers/projectController.py | 7 +++++-- src/api/views/projectView.py | 13 ++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/api/controllers/projectController.py b/src/api/controllers/projectController.py index f867309..96b6554 100644 --- a/src/api/controllers/projectController.py +++ b/src/api/controllers/projectController.py @@ -56,12 +56,15 @@ def update_project(self, user_id, **kwargs): def get_project(self, **kwargs): project = Project.query.filter_by(**kwargs).first() - return project + if project: + return project, "OK", 200 + else: + return None, "Project not found", 404 def get_all_projects(self, **kwargs): all_projects = Project.query.all() - return all_projects + return all_projects, "OK", 200 def delete_project(self, id): # Remove all project's links diff --git a/src/api/views/projectView.py b/src/api/views/projectView.py index c46ac5b..e8d645d 100644 --- a/src/api/views/projectView.py +++ b/src/api/views/projectView.py @@ -122,12 +122,7 @@ def get_project(id): 404: description: Project not found """ - project = projectController.get_project(id=id) - - if project: - return jsonify(project.as_dict()), 200 - else: - return "", 404 + return wrap_response(*projectController.get_project(id=id)) @app.route("/projects", methods=['GET']) def get_all_projects(): @@ -141,11 +136,7 @@ def get_all_projects(): 200: description: List of projects """ - all_projects = projectController.get_all_projects() - - projects = [ project.as_dict() for project in all_projects ] - - return jsonify(projects), 200 + return wrap_response(*projectController.get_all_projects()) @app.route("/projects/", methods=['DELETE']) def delete_project(id): From 8f4a0423b7f362c01a65a377901cd5c65d42ea63 Mon Sep 17 00:00:00 2001 From: petak5 Date: Fri, 3 Jul 2020 08:44:50 +0200 Subject: [PATCH 10/19] Delete project refactor --- src/api/controllers/projectController.py | 27 ++++++++++++++++++------ src/api/views/projectView.py | 21 +++++++++--------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/api/controllers/projectController.py b/src/api/controllers/projectController.py index 96b6554..260c121 100644 --- a/src/api/controllers/projectController.py +++ b/src/api/controllers/projectController.py @@ -1,6 +1,7 @@ from api.models import db, User, Project, UserHasProject, ProjectLink, ProjectFeedback class ProjectController: + # TODO: remove this (also from userController)? session = db.session # Project @@ -66,24 +67,38 @@ def get_all_projects(self, **kwargs): return all_projects, "OK", 200 - def delete_project(self, id): + def delete_project(self, user_id, **kwargs): + if len(kwargs) != 1: + return None, "Failed to update project. Request body must contain only project's id.", 400 + + if 'id' not in kwargs: + return None, "Failed to update project. Request body must specify project's id.", 400 + + project_id = kwargs["id"] + + userHasProject = UserHasProject.query.filter_by(user_id=user_id, project_id=project_id).first() + # TODO: verify that the user owns the project (or has neccessary rights) + if not userHasProject: + return None, "Failed to update project. Current user does not belong to specified project, or the project does not exist.", 404 + # Remove all project's links - for link in ProjectLink.query.filter_by(project_id=id).all(): + for link in ProjectLink.query.filter_by(project_id=project_id).all(): db.session.delete(link) # Remove project from all users - for project in UserHasProject.query.filter_by(project_id=id).all(): + for project in UserHasProject.query.filter_by(project_id=project_id).all(): db.session.delete(project) - project = Project.query.filter_by(id=id).first() + project = Project.query.filter_by(id=project_id).first() if project == None: - return project + # TODO: db.session.rollback? + return None, "Project not found", 404 db.session.delete(project) db.session.commit() - return project + return project, "OK", 200 # Project Link def create_link(self, project_id, **kwargs): diff --git a/src/api/views/projectView.py b/src/api/views/projectView.py index e8d645d..f28010a 100644 --- a/src/api/views/projectView.py +++ b/src/api/views/projectView.py @@ -138,16 +138,18 @@ def get_all_projects(): """ return wrap_response(*projectController.get_all_projects()) -@app.route("/projects/", methods=['DELETE']) -def delete_project(id): +@app.route("/project", methods=['DELETE']) +@jwt_required +@body_required +def delete_project(): """ Delete project - Deletes project with `id` + Deletes current user's project with the id in request body --- tags: - Project parameters: - - in: path + - in: body name: id type: integer required: true @@ -156,14 +158,11 @@ def delete_project(id): 200: description: Project deleted successfully 400: - description: Project not found + description: Failed to delete project + 404: + description: Current user is not a member of requested project or the project was not found """ - project = projectController.delete_project(id) - - if project: - return "", 202 - else: - return "", 404 + return wrap_response(*projectController.delete_project(user_id=get_jwt_identity(), **request.get_json())) # Project Link @app.route("/projects//links", methods=['POST']) From 8716f16178ece0d7c947738161db82d937edb5fb Mon Sep 17 00:00:00 2001 From: petak5 Date: Fri, 3 Jul 2020 08:49:59 +0200 Subject: [PATCH 11/19] Fix possible error with multiple parameters with the same name being passed to function --- src/api/controllers/projectController.py | 6 +++--- src/api/views/projectView.py | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/api/controllers/projectController.py b/src/api/controllers/projectController.py index 260c121..fb7a5b8 100644 --- a/src/api/controllers/projectController.py +++ b/src/api/controllers/projectController.py @@ -69,17 +69,17 @@ def get_all_projects(self, **kwargs): def delete_project(self, user_id, **kwargs): if len(kwargs) != 1: - return None, "Failed to update project. Request body must contain only project's id.", 400 + return None, "Failed to delete project. Request body must contain only project's id.", 400 if 'id' not in kwargs: - return None, "Failed to update project. Request body must specify project's id.", 400 + return None, "Failed to delete project. Request body must specify project's id.", 400 project_id = kwargs["id"] userHasProject = UserHasProject.query.filter_by(user_id=user_id, project_id=project_id).first() # TODO: verify that the user owns the project (or has neccessary rights) if not userHasProject: - return None, "Failed to update project. Current user does not belong to specified project, or the project does not exist.", 404 + return None, "Failed to delete project. Current user does not belong to specified project, or the project does not exist.", 404 # Remove all project's links for link in ProjectLink.query.filter_by(project_id=project_id).all(): diff --git a/src/api/views/projectView.py b/src/api/views/projectView.py index f28010a..339b60d 100644 --- a/src/api/views/projectView.py +++ b/src/api/views/projectView.py @@ -100,6 +100,9 @@ def update_project(): 404: description: Current user or requested project not found """ + if "user_id" in request.get_json(): + return wrap_response(None, "Failed to update project. Request body must not contain 'user_id'.", 400) + return wrap_response(*projectController.update_project(user_id=get_jwt_identity(), **request.get_json())) @app.route("/projects/", methods=['GET']) @@ -162,6 +165,9 @@ def delete_project(): 404: description: Current user is not a member of requested project or the project was not found """ + if "user_id" in request.get_json(): + return wrap_response(None, "Failed to delete project. Request body must not contain 'user_id'.", 400) + return wrap_response(*projectController.delete_project(user_id=get_jwt_identity(), **request.get_json())) # Project Link From 90575b22dc0f748e7914068dc2d3297980050ab9 Mon Sep 17 00:00:00 2001 From: petak5 Date: Fri, 3 Jul 2020 12:53:40 +0200 Subject: [PATCH 12/19] Add session rollback to create project controller when failed --- src/api/controllers/projectController.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/controllers/projectController.py b/src/api/controllers/projectController.py index fb7a5b8..07c6d99 100644 --- a/src/api/controllers/projectController.py +++ b/src/api/controllers/projectController.py @@ -23,6 +23,7 @@ def create_project(self, user_id, **kwargs): return project, "OK", 201 except: + self.session.rollback() return None, "Project creation failed", 400 def update_project(self, user_id, **kwargs): From bce06a14f1501389475b732129ea029301f49cdf Mon Sep 17 00:00:00 2001 From: petak5 Date: Fri, 3 Jul 2020 13:13:10 +0200 Subject: [PATCH 13/19] Update project tests after routes refactor --- tests/api/views/test_projectView.py | 73 ++++++++++++++++++----------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/tests/api/views/test_projectView.py b/tests/api/views/test_projectView.py index eb328d4..9990572 100644 --- a/tests/api/views/test_projectView.py +++ b/tests/api/views/test_projectView.py @@ -1,72 +1,91 @@ from tests.conftest import client from tests import db, Project, ProjectLink -from tests.api import create_project_for_test_cases, create_project_link_for_test_cases +from tests.api import create_project_for_test_cases, create_project_link_for_test_cases, create_access_token_for_test_cases class TestProjectView(object): - valid_data = { + # valid data for user creation + valid_user_data = { + 'name': 'L Jone', + 'bio': 'coding...', + 'languages': 'FR', + 'interests': 'Nothing', + 'location': 'X', + 'occupation': 'cashier' + } + + valid_project_data = { 'name': 'PB api', 'description': 'A cool project', + 'development_status': 1, 'repository': 'http://github.xy.com' } def test_create_project(self, client): - response = client.post('/projects', json={"name": "Project"}) + token, _ = create_access_token_for_test_cases(self.valid_user_data) + + response = client.post('/project', headers={"Authorization": f"Bearer {token}"}, json={"name": "Project"}) assert response.status_code == 400 - response = client.post('/projects', json=self.valid_data) - assert response.status_code == 201 + response = client.post('/project', headers={"Authorization": f"Bearer {token}"}) + assert response.status_code == 400 - # response = client.post('/projects') - # assert response.status_code == 400 + response = client.post('/project') + assert response.status_code == 401 + + response = client.post('/project', headers={"Authorization": f"Bearer {token}"}, json=self.valid_project_data) + assert response.status_code == 201 def test_update_project(self, client): + token, _ = create_access_token_for_test_cases(self.valid_user_data) # project id doesn't exist - response = client.post('/projects/0', json={'name': 'Updated PB'}) - - # notice: should return 404 when doesen't exist insted of 400 + response = client.put('/project', headers={"Authorization": f"Bearer {token}"}, json={'id': 0, 'name': 'Updated PB'}) assert response.status_code == 404 - project_id = create_project_for_test_cases(self.valid_data) - response = client.post('/projects/{}'.format(project_id), json={'description': 'updated desc'}) + project = create_project_for_test_cases(self.valid_project_data) + project_id = project["id"] + + response = client.put('/project', headers={"Authorization": f"Bearer {token}"}, json={'id': project_id, 'description': 'updated desc'}) project = Project.query.filter_by(id=project_id).first() assert project.description == 'updated desc' def test_delete_project(self, client): - response = client.delete('/projects/0') + token, _ = create_access_token_for_test_cases(self.valid_user_data) + + response = client.delete('/project', headers={"Authorization": f"Bearer {token}"}, json={"id": 0}) assert response.status_code == 404 - project1 = create_project_for_test_cases(self.valid_data) + project1 = create_project_for_test_cases(self.valid_project_data) - self.valid_data["name"] = "p2 name" - project2 = create_project_for_test_cases(self.valid_data) + self.valid_project_data["name"] = "p2 name" + project2 = create_project_for_test_cases(self.valid_project_data) - response = client.delete('/projects/{}'.format(project1["id"])) + response = client.delete('/project', headers={"Authorization": f"Bearer {token}"}, json={"id": project1["id"]}) assert response.status_code == 202 def test_get_project(self, client): response = client.get('/projects/{}'.format(0)) assert response.status_code == 404 - project = create_project_for_test_cases(self.valid_data) + project = create_project_for_test_cases(self.valid_project_data) response = client.get('/projects/{}'.format(project["id"])) assert response.status_code == 200 - assert response.get_json()["name"] == project["name"] + assert response.get_json()["data"]["name"] == project["name"] def test_get_all_projects(self, client): - project1 = create_project_for_test_cases(self.valid_data) + project1 = create_project_for_test_cases(self.valid_project_data) - self.valid_data["name"] = "allproject2" - project2 = create_project_for_test_cases(self.valid_data) + self.valid_project_data["name"] = "allproject2" + project2 = create_project_for_test_cases(self.valid_project_data) response = client.get('/projects') assert response.status_code == 200 r = response.get_json() - assert [r[0]["name"], r[1]["name"]] == [project1["name"], project2["name"]] + assert [r["data"][0]["name"], r["data"][1]["name"]] == [project1["name"], project2["name"]] def test_create_project_link(self, client): - project = create_project_for_test_cases(self.valid_data) + project = create_project_for_test_cases(self.valid_project_data) url = '/projects/{}/links'.format(project["id"]) response = client.post(url, json={"user_id": 0}) @@ -76,7 +95,7 @@ def test_create_project_link(self, client): assert response.status_code == 201 def test_update_project_link(self, client): - p1 = create_project_for_test_cases(self.valid_data) + p1 = create_project_for_test_cases(self.valid_project_data) p1_link = create_project_link_for_test_cases( { "name": "Plink", @@ -99,7 +118,7 @@ def test_update_project_link(self, client): assert response.get_json()["name"] == "Nlink" def test_get_all_project_links(self, client): - p1 = create_project_for_test_cases(self.valid_data) + p1 = create_project_for_test_cases(self.valid_project_data) p_link1 = create_project_link_for_test_cases( { "name": "Plink", @@ -123,7 +142,7 @@ def test_delete_project_link(self, client): response = client.delete(url.format(0, 0)) assert response.status_code == 404 - p1 = create_project_for_test_cases(self.valid_data) + p1 = create_project_for_test_cases(self.valid_project_data) p_link1 = create_project_link_for_test_cases( { "name": "Plink", From 6fc6801387bed2c010df0d893e2059e6ba44341a Mon Sep 17 00:00:00 2001 From: petak5 Date: Fri, 3 Jul 2020 15:58:55 +0200 Subject: [PATCH 14/19] Fixed delete project test --- tests/__init__.py | 2 +- tests/api/__init__.py | 12 +++++++++++- tests/api/views/test_projectView.py | 8 +++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 56f3d94..25d093a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,6 +6,6 @@ from api import app from api.models import db -from api.models import User, Project, UserFeedback, ProjectFeedback, UserLink, ProjectLink +from api.models import User, Project, UserHasProject, UserFeedback, ProjectFeedback, UserLink, ProjectLink sys.path.insert(0, os.getcwd()+'/tests') diff --git a/tests/api/__init__.py b/tests/api/__init__.py index bd7fb7c..814b07e 100644 --- a/tests/api/__init__.py +++ b/tests/api/__init__.py @@ -11,7 +11,17 @@ """ from flask_jwt_extended import create_access_token -from tests import db, Project, User, UserLink, ProjectLink, UserFeedback +from tests import db, Project, User, UserHasProject, UserLink, ProjectLink, UserFeedback + +""" Create relationship between user and project """ +def user_join_project_for_test_cases(user, project): + userHasProject = UserHasProject(role=1) + userHasProject.project = Project.query.filter_by(id=project["id"]).first() + userHasProject.user = user + # This line is not needed for some reason + # SQLAlchemy is doing some magic and I don't know shit about fuck + #user.projects.append(userHasProject) + db.session.commit() def create_project_for_test_cases(data): new_project = Project(**data) diff --git a/tests/api/views/test_projectView.py b/tests/api/views/test_projectView.py index 9990572..5cb48f1 100644 --- a/tests/api/views/test_projectView.py +++ b/tests/api/views/test_projectView.py @@ -1,6 +1,6 @@ from tests.conftest import client from tests import db, Project, ProjectLink -from tests.api import create_project_for_test_cases, create_project_link_for_test_cases, create_access_token_for_test_cases +from tests.api import create_project_for_test_cases, create_project_link_for_test_cases, create_access_token_for_test_cases, user_join_project_for_test_cases class TestProjectView(object): @@ -51,18 +51,20 @@ def test_update_project(self, client): assert project.description == 'updated desc' def test_delete_project(self, client): - token, _ = create_access_token_for_test_cases(self.valid_user_data) + token, user = create_access_token_for_test_cases(self.valid_user_data) response = client.delete('/project', headers={"Authorization": f"Bearer {token}"}, json={"id": 0}) assert response.status_code == 404 project1 = create_project_for_test_cases(self.valid_project_data) + user_join_project_for_test_cases(user, project1) self.valid_project_data["name"] = "p2 name" project2 = create_project_for_test_cases(self.valid_project_data) + user_join_project_for_test_cases(user, project2) response = client.delete('/project', headers={"Authorization": f"Bearer {token}"}, json={"id": project1["id"]}) - assert response.status_code == 202 + assert response.status_code == 200 def test_get_project(self, client): response = client.get('/projects/{}'.format(0)) From 946bebcae68f05f34ef3f4b991acfb470b61a27d Mon Sep 17 00:00:00 2001 From: petak5 Date: Sat, 4 Jul 2020 09:11:32 +0200 Subject: [PATCH 15/19] Fix usage message for tests --- tests/runtests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/runtests.py b/tests/runtests.py index 82a6c8b..70d7f78 100644 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -1,6 +1,6 @@ response = """ -Use: pipenv run pytest src/tests/ +Usage: pipenv run pytest tests/ """ From da079a9e64b8611e7b42250640b46412fd27d063 Mon Sep 17 00:00:00 2001 From: petak5 Date: Thu, 9 Jul 2020 22:38:13 +0200 Subject: [PATCH 16/19] Get project id from path in delete project route --- src/api/controllers/projectController.py | 17 +++++------------ src/api/views/projectView.py | 14 +++++--------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/api/controllers/projectController.py b/src/api/controllers/projectController.py index 07c6d99..b957bfc 100644 --- a/src/api/controllers/projectController.py +++ b/src/api/controllers/projectController.py @@ -68,29 +68,22 @@ def get_all_projects(self, **kwargs): return all_projects, "OK", 200 - def delete_project(self, user_id, **kwargs): - if len(kwargs) != 1: - return None, "Failed to delete project. Request body must contain only project's id.", 400 + def delete_project(self, user_id, id): - if 'id' not in kwargs: - return None, "Failed to delete project. Request body must specify project's id.", 400 - - project_id = kwargs["id"] - - userHasProject = UserHasProject.query.filter_by(user_id=user_id, project_id=project_id).first() + userHasProject = UserHasProject.query.filter_by(user_id=user_id, project_id=id).first() # TODO: verify that the user owns the project (or has neccessary rights) if not userHasProject: return None, "Failed to delete project. Current user does not belong to specified project, or the project does not exist.", 404 # Remove all project's links - for link in ProjectLink.query.filter_by(project_id=project_id).all(): + for link in ProjectLink.query.filter_by(project_id=id).all(): db.session.delete(link) # Remove project from all users - for project in UserHasProject.query.filter_by(project_id=project_id).all(): + for project in UserHasProject.query.filter_by(project_id=id).all(): db.session.delete(project) - project = Project.query.filter_by(id=project_id).first() + project = Project.query.filter_by(id=id).first() if project == None: # TODO: db.session.rollback? diff --git a/src/api/views/projectView.py b/src/api/views/projectView.py index 339b60d..af20275 100644 --- a/src/api/views/projectView.py +++ b/src/api/views/projectView.py @@ -141,18 +141,17 @@ def get_all_projects(): """ return wrap_response(*projectController.get_all_projects()) -@app.route("/project", methods=['DELETE']) +@app.route("/project/", methods=['DELETE']) @jwt_required -@body_required -def delete_project(): +def delete_project(id): """ Delete project - Deletes current user's project with the id in request body + Deletes current user's project with the `id` --- tags: - Project parameters: - - in: body + - in: path name: id type: integer required: true @@ -165,10 +164,7 @@ def delete_project(): 404: description: Current user is not a member of requested project or the project was not found """ - if "user_id" in request.get_json(): - return wrap_response(None, "Failed to delete project. Request body must not contain 'user_id'.", 400) - - return wrap_response(*projectController.delete_project(user_id=get_jwt_identity(), **request.get_json())) + return wrap_response(*projectController.delete_project(user_id=get_jwt_identity(), id=id)) # Project Link @app.route("/projects//links", methods=['POST']) From 93196a56ad6756e82ce3165f67b6f64acda6d6ac Mon Sep 17 00:00:00 2001 From: petak5 Date: Thu, 9 Jul 2020 22:39:44 +0200 Subject: [PATCH 17/19] Change get project(s) routes to `/project/` and `/project/all` --- src/api/views/projectView.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/views/projectView.py b/src/api/views/projectView.py index af20275..ccb4bae 100644 --- a/src/api/views/projectView.py +++ b/src/api/views/projectView.py @@ -105,7 +105,7 @@ def update_project(): return wrap_response(*projectController.update_project(user_id=get_jwt_identity(), **request.get_json())) -@app.route("/projects/", methods=['GET']) +@app.route("/project/", methods=['GET']) def get_project(id): """ Get project @@ -127,7 +127,7 @@ def get_project(id): """ return wrap_response(*projectController.get_project(id=id)) -@app.route("/projects", methods=['GET']) +@app.route("/project/all", methods=['GET']) def get_all_projects(): """ Get all projects From e20bb77ef2b6e4b7c306d355a7ea4b7e9781ccf5 Mon Sep 17 00:00:00 2001 From: petak5 Date: Thu, 9 Jul 2020 23:01:59 +0200 Subject: [PATCH 18/19] Fix changed get project(s) route URL path in test --- tests/api/views/test_projectView.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/api/views/test_projectView.py b/tests/api/views/test_projectView.py index 5cb48f1..5021b55 100644 --- a/tests/api/views/test_projectView.py +++ b/tests/api/views/test_projectView.py @@ -67,11 +67,11 @@ def test_delete_project(self, client): assert response.status_code == 200 def test_get_project(self, client): - response = client.get('/projects/{}'.format(0)) + response = client.get('/project/{}'.format(0)) assert response.status_code == 404 project = create_project_for_test_cases(self.valid_project_data) - response = client.get('/projects/{}'.format(project["id"])) + response = client.get('/project/{}'.format(project["id"])) assert response.status_code == 200 assert response.get_json()["data"]["name"] == project["name"] @@ -80,7 +80,7 @@ def test_get_all_projects(self, client): self.valid_project_data["name"] = "allproject2" project2 = create_project_for_test_cases(self.valid_project_data) - response = client.get('/projects') + response = client.get('/project/all') assert response.status_code == 200 r = response.get_json() From 2355a1472e97609dd541485c30cac17cad988546 Mon Sep 17 00:00:00 2001 From: petak5 Date: Thu, 9 Jul 2020 23:14:08 +0200 Subject: [PATCH 19/19] Fix changed delete project route URL path in test --- tests/api/views/test_projectView.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api/views/test_projectView.py b/tests/api/views/test_projectView.py index 5021b55..366613f 100644 --- a/tests/api/views/test_projectView.py +++ b/tests/api/views/test_projectView.py @@ -53,7 +53,7 @@ def test_update_project(self, client): def test_delete_project(self, client): token, user = create_access_token_for_test_cases(self.valid_user_data) - response = client.delete('/project', headers={"Authorization": f"Bearer {token}"}, json={"id": 0}) + response = client.delete('/project/{}'.format(0), headers={"Authorization": f"Bearer {token}"}) assert response.status_code == 404 project1 = create_project_for_test_cases(self.valid_project_data) @@ -63,7 +63,7 @@ def test_delete_project(self, client): project2 = create_project_for_test_cases(self.valid_project_data) user_join_project_for_test_cases(user, project2) - response = client.delete('/project', headers={"Authorization": f"Bearer {token}"}, json={"id": project1["id"]}) + response = client.delete('/project/{}'.format(project1["id"]), headers={"Authorization": f"Bearer {token}"}) assert response.status_code == 200 def test_get_project(self, client):