From f16fc8b78d8f35e8cd20055867c6eb51bbea28c0 Mon Sep 17 00:00:00 2001 From: Deji Kadri Date: Mon, 18 Jan 2021 00:00:44 -0500 Subject: [PATCH 1/9] added repo, controller, blueprint and test for skill refs PA-16 --- app/blueprints/skill_blueprint.py | 5 +++++ app/controllers/skill_controller.py | 6 ++++++ app/repositories/skill_repo.py | 17 +++-------------- .../endpoints/test_skill_endpoints.py | 7 +++++++ 4 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 app/blueprints/skill_blueprint.py create mode 100644 app/controllers/skill_controller.py create mode 100644 tests/integration/endpoints/test_skill_endpoints.py diff --git a/app/blueprints/skill_blueprint.py b/app/blueprints/skill_blueprint.py new file mode 100644 index 0000000..e3074fc --- /dev/null +++ b/app/blueprints/skill_blueprint.py @@ -0,0 +1,5 @@ +from app.blueprints.base_blueprint import Blueprint, BaseBlueprint, request, Security, Auth + +url_prefix = '{}/skills'.format(BaseBlueprint.base_url_prefix) +skill_blueprint = Blueprint('skill', __name__, url_prefix=url_prefix) + \ No newline at end of file diff --git a/app/controllers/skill_controller.py b/app/controllers/skill_controller.py new file mode 100644 index 0000000..2fd89eb --- /dev/null +++ b/app/controllers/skill_controller.py @@ -0,0 +1,6 @@ +from app.controllers.base_controller import BaseController + +class SkillController(BaseController): + def __init__(self, request): + BaseController.__init__(self, request) + \ No newline at end of file diff --git a/app/repositories/skill_repo.py b/app/repositories/skill_repo.py index c158368..fa5c9d3 100644 --- a/app/repositories/skill_repo.py +++ b/app/repositories/skill_repo.py @@ -1,18 +1,7 @@ from app.repositories.base_repo import BaseRepo from app.models.skill import Skill - class SkillRepo(BaseRepo): - def __init__(self): - BaseRepo.__init__(self, Skill) - - def new_skill(self, name, skill_category_id, is_active=True, is_deleted=False): - new_skill = Skill( - name=name, - skill_category_id=skill_category_id, - is_active=is_active, - is_deleted=is_deleted, - ) - - new_skill.save() - return new_skill + + def __init__(self): + BaseRepo.__init__(self, Skill) \ No newline at end of file diff --git a/tests/integration/endpoints/test_skill_endpoints.py b/tests/integration/endpoints/test_skill_endpoints.py new file mode 100644 index 0000000..662911f --- /dev/null +++ b/tests/integration/endpoints/test_skill_endpoints.py @@ -0,0 +1,7 @@ +from tests.base_test_case import BaseTestCase + +class TestSkillEndpoints(BaseTestCase): + + def setUp(self): + self.BaseSetUp() + \ No newline at end of file From 5a9e2f836245e8264fe3e9194283d4e32ff4f470 Mon Sep 17 00:00:00 2001 From: Deji Kadri Date: Mon, 18 Jan 2021 01:46:47 -0500 Subject: [PATCH 2/9] Flesh out list and create methods for repo, controller, blueprint refs PA-16 --- app/blueprints/skill_blueprint.py | 32 +++++++++++++++++++++++++---- app/controllers/skill_controller.py | 32 ++++++++++++++++++++++++++++- app/repositories/skill_repo.py | 8 +++++++- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/app/blueprints/skill_blueprint.py b/app/blueprints/skill_blueprint.py index e3074fc..9c9d15a 100644 --- a/app/blueprints/skill_blueprint.py +++ b/app/blueprints/skill_blueprint.py @@ -1,5 +1,29 @@ -from app.blueprints.base_blueprint import Blueprint, BaseBlueprint, request, Security, Auth +from app.blueprints.base_blueprint import ( + Blueprint, + BaseBlueprint, + request, + Security, + Auth, +) +from app.controllers.skill_controller import SkillController -url_prefix = '{}/skills'.format(BaseBlueprint.base_url_prefix) -skill_blueprint = Blueprint('skill', __name__, url_prefix=url_prefix) - \ No newline at end of file +url_prefix = "{}/skills".format(BaseBlueprint.base_url_prefix) +skill_blueprint = Blueprint("skill", __name__, url_prefix=url_prefix) +skill_controller = SkilController(request) + + +def list_skills(): + pass + + +def get_skill(skill_id): + pass + +def create_skill(): + pass + +def update_skill(skill_id): + pass + +def delete_skill(skill_id): + pass diff --git a/app/controllers/skill_controller.py b/app/controllers/skill_controller.py index 2fd89eb..eba31f4 100644 --- a/app/controllers/skill_controller.py +++ b/app/controllers/skill_controller.py @@ -1,6 +1,36 @@ from app.controllers.base_controller import BaseController +from app.repositories.skill_repo import SkillRepo class SkillController(BaseController): def __init__(self, request): BaseController.__init__(self, request) - \ No newline at end of file + self.skill_repo = SkillRepo() + + def list_skills(self): + skills = self.skill_repo.fetch_all() + skill_list = [skill.serialize() for skill in skills.items] + return self.handle_response( + "OK", + payload={ + "skills": skill_list, + "meta": self.pagination_meta(skills), + }, + ) + + + def get_skill(self, skill_id): + skill = self.skill_repo.get(skill_id) + if skill: + return self.handle_response( + "OK", payload={"skill": skill.serialize()} + ) + return self.handle_response("Invalid or Missing skill_id", status_code=400) + + def create_skill(self): + name, skill_category_id = self.request_params("name", "skill_category_id") + skill = self.skill_repo.new_skill(name=name, skill_category_id=skill_category_id) + + return self.handle_response( + "OK", payload={"skill": skill.serialize()}, status_code=201 + + ) diff --git a/app/repositories/skill_repo.py b/app/repositories/skill_repo.py index fa5c9d3..f5217e6 100644 --- a/app/repositories/skill_repo.py +++ b/app/repositories/skill_repo.py @@ -4,4 +4,10 @@ class SkillRepo(BaseRepo): def __init__(self): - BaseRepo.__init__(self, Skill) \ No newline at end of file + BaseRepo.__init__(self, Skill) + + def new_skill(self, name, skill_category_id): + skill = Skill(name=name, skill_category_id=skill_category_id) + + skill.save() + return skill \ No newline at end of file From 9d145b036c60d859111801783dfb61745806d52b Mon Sep 17 00:00:00 2001 From: Deji Kadri Date: Mon, 18 Jan 2021 11:42:35 -0500 Subject: [PATCH 3/9] Reformatted with black refs PA-16 --- app/blueprints/skill_blueprint.py | 3 ++ app/controllers/skill_controller.py | 43 ++++++++++++++--------------- app/repositories/skill_repo.py | 14 +++++----- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/app/blueprints/skill_blueprint.py b/app/blueprints/skill_blueprint.py index 9c9d15a..4c7f055 100644 --- a/app/blueprints/skill_blueprint.py +++ b/app/blueprints/skill_blueprint.py @@ -19,11 +19,14 @@ def list_skills(): def get_skill(skill_id): pass + def create_skill(): pass + def update_skill(skill_id): pass + def delete_skill(skill_id): pass diff --git a/app/controllers/skill_controller.py b/app/controllers/skill_controller.py index eba31f4..922e0f3 100644 --- a/app/controllers/skill_controller.py +++ b/app/controllers/skill_controller.py @@ -1,36 +1,35 @@ from app.controllers.base_controller import BaseController from app.repositories.skill_repo import SkillRepo + class SkillController(BaseController): - def __init__(self, request): - BaseController.__init__(self, request) - self.skill_repo = SkillRepo() + def __init__(self, request): + BaseController.__init__(self, request) + self.skill_repo = SkillRepo() - def list_skills(self): - skills = self.skill_repo.fetch_all() - skill_list = [skill.serialize() for skill in skills.items] - return self.handle_response( + def list_skills(self): + skills = self.skill_repo.fetch_all() + skill_list = [skill.serialize() for skill in skills.items] + return self.handle_response( "OK", payload={ "skills": skill_list, "meta": self.pagination_meta(skills), }, ) - - - def get_skill(self, skill_id): - skill = self.skill_repo.get(skill_id) - if skill: - return self.handle_response( - "OK", payload={"skill": skill.serialize()} - ) - return self.handle_response("Invalid or Missing skill_id", status_code=400) - def create_skill(self): - name, skill_category_id = self.request_params("name", "skill_category_id") - skill = self.skill_repo.new_skill(name=name, skill_category_id=skill_category_id) + def get_skill(self, skill_id): + skill = self.skill_repo.get(skill_id) + if skill: + return self.handle_response("OK", payload={"skill": skill.serialize()}) + return self.handle_response("Invalid or Missing skill_id", status_code=400) - return self.handle_response( - "OK", payload={"skill": skill.serialize()}, status_code=201 + def create_skill(self): + name, skill_category_id = self.request_params("name", "skill_category_id") + skill = self.skill_repo.new_skill( + name=name, skill_category_id=skill_category_id + ) - ) + return self.handle_response( + "OK", payload={"skill": skill.serialize()}, status_code=201 + ) diff --git a/app/repositories/skill_repo.py b/app/repositories/skill_repo.py index f5217e6..076830b 100644 --- a/app/repositories/skill_repo.py +++ b/app/repositories/skill_repo.py @@ -1,13 +1,13 @@ from app.repositories.base_repo import BaseRepo from app.models.skill import Skill + class SkillRepo(BaseRepo): - - def __init__(self): - BaseRepo.__init__(self, Skill) + def __init__(self): + BaseRepo.__init__(self, Skill) - def new_skill(self, name, skill_category_id): - skill = Skill(name=name, skill_category_id=skill_category_id) + def new_skill(self, name, skill_category_id): + skill = Skill(name=name, skill_category_id=skill_category_id) - skill.save() - return skill \ No newline at end of file + skill.save() + return skill From 6249cbffd89e3b419e7c6ea6e04b52f49e8e41e8 Mon Sep 17 00:00:00 2001 From: Deji Kadri Date: Mon, 18 Jan 2021 23:27:37 -0500 Subject: [PATCH 4/9] added list and get single skill functions to skill controller refs PA-16 --- app/blueprints/base_blueprint.py | 2 ++ app/blueprints/skill_blueprint.py | 8 +++++--- app/controllers/skill_controller.py | 18 ++++++++++-------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/blueprints/base_blueprint.py b/app/blueprints/base_blueprint.py index 9999a10..eba4f26 100755 --- a/app/blueprints/base_blueprint.py +++ b/app/blueprints/base_blueprint.py @@ -21,6 +21,7 @@ def register(self): from app.blueprints.skills_category_blueprint import skills_category_blueprint from app.blueprints.user_employment_blueprint import user_employment_blueprint from app.blueprints.user_project_blueprint import user_project_blueprint + from app.blueprints.skill_blueprint import skill_blueprint self.app.register_blueprint(home_blueprint) self.app.register_blueprint(activity_blueprint) @@ -29,3 +30,4 @@ def register(self): self.app.register_blueprint(skills_category_blueprint) self.app.register_blueprint(user_employment_blueprint) self.app.register_blueprint(user_project_blueprint) + self.app.register_blueprint(skill_blueprint) diff --git a/app/blueprints/skill_blueprint.py b/app/blueprints/skill_blueprint.py index 4c7f055..21845fc 100644 --- a/app/blueprints/skill_blueprint.py +++ b/app/blueprints/skill_blueprint.py @@ -9,15 +9,17 @@ url_prefix = "{}/skills".format(BaseBlueprint.base_url_prefix) skill_blueprint = Blueprint("skill", __name__, url_prefix=url_prefix) -skill_controller = SkilController(request) +skill_controller = SkillController(request) +@skill_blueprint.route('/') def list_skills(): - pass + return skill_controller.list_skills() +@skill_blueprint.route('//') def get_skill(skill_id): - pass + return skill_controller.get_skill(skill_id) def create_skill(): diff --git a/app/controllers/skill_controller.py b/app/controllers/skill_controller.py index 922e0f3..f25c09c 100644 --- a/app/controllers/skill_controller.py +++ b/app/controllers/skill_controller.py @@ -9,14 +9,16 @@ def __init__(self, request): def list_skills(self): skills = self.skill_repo.fetch_all() - skill_list = [skill.serialize() for skill in skills.items] - return self.handle_response( - "OK", - payload={ - "skills": skill_list, - "meta": self.pagination_meta(skills), - }, - ) + if skills: + skill_list = [skill.serialize() for skill in skills.items] + return self.handle_response( + "OK", + payload={ + "skills": skill_list, + "meta": self.pagination_meta(skills), + }, + ) + return self.handle_response("Empty dataset", status_code=400) def get_skill(self, skill_id): skill = self.skill_repo.get(skill_id) From 6efb01c1cb611bc9613cbd2be959f0eac91d4ff2 Mon Sep 17 00:00:00 2001 From: Deji Kadri Date: Wed, 20 Jan 2021 01:14:49 -0500 Subject: [PATCH 5/9] Code the update and delete method in skill controller refs PA-16 --- app/controllers/skill_controller.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/controllers/skill_controller.py b/app/controllers/skill_controller.py index f25c09c..ef6137d 100644 --- a/app/controllers/skill_controller.py +++ b/app/controllers/skill_controller.py @@ -35,3 +35,24 @@ def create_skill(self): return self.handle_response( "OK", payload={"skill": skill.serialize()}, status_code=201 ) + + def update_skill(self, skill_id): + name, skill_category_id = self.request_params("name", "skill_category_id") + skill = self.skill_repo.get(skill_id) + if skill: + skill = self.skill_repo.update(skill, **dict(name=name, skill_category_id=skill_category_id)) + return self.handle_response( + "OK", payload={"skill": skill.serialize()}, status_code=201 + ) + return self.handle_response("Location Not Found", status_code=404) + + + def delete_skill(self, skill_id): + skill = self.skill_repo.get(skill_id) + update_dict ={"is_deleted": True} + if skill: + self.skill_repo.update(**update_dict) + return self.handle_response("role deleted", payload={"status": "success"}) + return self.handle_response( + "Invalid or incorrect role_id provided", status_code=404 + ) \ No newline at end of file From 46ef8f98401565ba7d32588c77e5d508dbdadeaa Mon Sep 17 00:00:00 2001 From: Deji Kadri Date: Thu, 21 Jan 2021 18:53:01 -0500 Subject: [PATCH 6/9] update create, delete and update skill methods in skill blueprint file refs PA-16 --- app/blueprints/skill_blueprint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/blueprints/skill_blueprint.py b/app/blueprints/skill_blueprint.py index 21845fc..d096048 100644 --- a/app/blueprints/skill_blueprint.py +++ b/app/blueprints/skill_blueprint.py @@ -23,12 +23,12 @@ def get_skill(skill_id): def create_skill(): - pass + return skill_controller.create_skill() def update_skill(skill_id): - pass + return skill_controller.update_skill(skill_id) def delete_skill(skill_id): - pass + return skill_controller.delete_skill(skill_id) From 072baa034c515d6f4f93f75a5b5ec9e1c13e3caf Mon Sep 17 00:00:00 2001 From: Deji Kadri Date: Thu, 21 Jan 2021 19:11:04 -0500 Subject: [PATCH 7/9] add file skill_factory.py refs PA-16 --- factories/skill_factory.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 factories/skill_factory.py diff --git a/factories/skill_factory.py b/factories/skill_factory.py new file mode 100644 index 0000000..e69de29 From 4bacf27ee92a1153d5420bd2f0a242bf20104ce8 Mon Sep 17 00:00:00 2001 From: Deji Kadri Date: Fri, 22 Jan 2021 00:15:07 -0500 Subject: [PATCH 8/9] add code to test and factory -incomplete refs PA-16 --- factories/skill_factory.py | 16 ++++++++++++++++ .../endpoints/test_skill_endpoints.py | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/factories/skill_factory.py b/factories/skill_factory.py index e69de29..60edefa 100644 --- a/factories/skill_factory.py +++ b/factories/skill_factory.py @@ -0,0 +1,16 @@ +import factory +from app.utils import db +from app.models import Skill +from faker import Faker + + +class SkillFactory(factory.alchemy.SQLAlchemyModelFactory): + + class Meta: + model = Skill + sqlalchemy_session = db.session + + id = factory.Sequence(lambda n: n) + name = factory.Faker("city") + skill_category_id = factory.Sequence(lambda n: n) + diff --git a/tests/integration/endpoints/test_skill_endpoints.py b/tests/integration/endpoints/test_skill_endpoints.py index 662911f..301b119 100644 --- a/tests/integration/endpoints/test_skill_endpoints.py +++ b/tests/integration/endpoints/test_skill_endpoints.py @@ -4,4 +4,8 @@ class TestSkillEndpoints(BaseTestCase): def setUp(self): self.BaseSetUp() - \ No newline at end of file + + + def tearDown(self): + self.BaseTearDown() + From 411e0babc6a4e5e622aea04a600429f1d568f91d Mon Sep 17 00:00:00 2001 From: Eno Bassey Date: Mon, 25 Jan 2021 10:39:16 +0100 Subject: [PATCH 9/9] add test for skills and refactor other tests --- .pre-commit-config.yaml | 1 + app/blueprints/skill_blueprint.py | 25 +- app/controllers/skill_controller.py | 72 +++-- factories/skill_category_factory.py | 2 - .../test_skills_category_endpoints.py | 4 + .../unit/controllers/test_skill_controller.py | 298 ++++++++++++++++++ tests/unit/repositories/test_skill_repo.py | 23 ++ vessel.py | 271 ++++++++-------- 8 files changed, 545 insertions(+), 151 deletions(-) create mode 100644 tests/unit/controllers/test_skill_controller.py create mode 100644 tests/unit/repositories/test_skill_repo.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 975add3..670ae9f 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,6 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks +exclude: '^vessel.py' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.4.0 diff --git a/app/blueprints/skill_blueprint.py b/app/blueprints/skill_blueprint.py index d096048..c163e2a 100644 --- a/app/blueprints/skill_blueprint.py +++ b/app/blueprints/skill_blueprint.py @@ -12,23 +12,44 @@ skill_controller = SkillController(request) -@skill_blueprint.route('/') +@skill_blueprint.route("/", methods=["GET"]) +@Auth.has_permission(["view_skill"]) def list_skills(): return skill_controller.list_skills() -@skill_blueprint.route('//') +@skill_blueprint.route("//", methods=["GET"]) +@Auth.has_permission(["view_skill"]) def get_skill(skill_id): return skill_controller.get_skill(skill_id) +@skill_blueprint.route("/", methods=["POST"]) +@Security.validator( + [ + "name|required:string", + "skill_category_id|required:int", + ] +) +@Auth.has_permission(["create_skill"]) def create_skill(): return skill_controller.create_skill() +@skill_blueprint.route("/", methods=["PUT", "PATCH"]) +@Security.validator( + [ + "skill_id|required:int", + "name|required:string", + "skill_category_id|required:int", + ] +) +@Auth.has_permission(["update_skill"]) def update_skill(skill_id): return skill_controller.update_skill(skill_id) +@skill_blueprint.route("/", methods=["DELETE"]) +@Auth.has_permission(["delete_skill"]) def delete_skill(skill_id): return skill_controller.delete_skill(skill_id) diff --git a/app/controllers/skill_controller.py b/app/controllers/skill_controller.py index ef6137d..4105f52 100644 --- a/app/controllers/skill_controller.py +++ b/app/controllers/skill_controller.py @@ -8,26 +8,33 @@ def __init__(self, request): self.skill_repo = SkillRepo() def list_skills(self): - skills = self.skill_repo.fetch_all() - if skills: - skill_list = [skill.serialize() for skill in skills.items] - return self.handle_response( - "OK", - payload={ - "skills": skill_list, - "meta": self.pagination_meta(skills), - }, - ) - return self.handle_response("Empty dataset", status_code=400) + skills = self.skill_repo.get_unpaginated() + skill_list = [skill.serialize() for skill in skills.items] + return self.handle_response( + "OK", + payload={ + "skills": skill_list, + }, + ) def get_skill(self, skill_id): - skill = self.skill_repo.get(skill_id) + skill = self.skill_repo.find_first(id=skill_id) if skill: return self.handle_response("OK", payload={"skill": skill.serialize()}) return self.handle_response("Invalid or Missing skill_id", status_code=400) def create_skill(self): name, skill_category_id = self.request_params("name", "skill_category_id") + skill = self.skill_repo.find_first( + name=name, skill_category_id=skill_category_id + ) + + if skill: + print("testing ....") + return self.handle_response( + f"Skill name {skill.name} with category name {skill.skill_category.name} already exists", + status_code=400, + ) skill = self.skill_repo.new_skill( name=name, skill_category_id=skill_category_id ) @@ -36,23 +43,42 @@ def create_skill(self): "OK", payload={"skill": skill.serialize()}, status_code=201 ) - def update_skill(self, skill_id): - name, skill_category_id = self.request_params("name", "skill_category_id") - skill = self.skill_repo.get(skill_id) - if skill: - skill = self.skill_repo.update(skill, **dict(name=name, skill_category_id=skill_category_id)) + def update_skill(self, update_id): + skill_id, name, skill_category_id = self.request_params( + "skill_id", "name", "skill_category_id" + ) + skill = self.skill_repo.find_first( + name=name, skill_category_id=skill_category_id + ) + + skill_other = self.skill_repo.get(name=name) + + if skill_id != update_id: return self.handle_response( - "OK", payload={"skill": skill.serialize()}, status_code=201 + "Invalid or incorrect skill_id provided", status_code=400 ) - return self.handle_response("Location Not Found", status_code=404) + if skill: + if skill.id == skill_other.id: + skill = self.skill_repo.update( + skill, **dict(name=name, skill_category_id=skill_category_id) + ) + return self.handle_response( + "OK", payload={"skill": skill.serialize()}, status_code=200 + ) + else: + return self.handle_response( + f"Skill name '{name}' with category name {skill_other.skill_category.name} already exists", + status_code=400, + ) + return self.handle_response("Skill Not Found", status_code=404) def delete_skill(self, skill_id): skill = self.skill_repo.get(skill_id) - update_dict ={"is_deleted": True} + update_dict = {"is_deleted": True} if skill: self.skill_repo.update(**update_dict) - return self.handle_response("role deleted", payload={"status": "success"}) + return self.handle_response("skill deleted", payload={"status": "success"}) return self.handle_response( - "Invalid or incorrect role_id provided", status_code=404 - ) \ No newline at end of file + "Invalid or incorrect skill_id provided", status_code=404 + ) diff --git a/factories/skill_category_factory.py b/factories/skill_category_factory.py index 2e7a2bb..bd6898d 100644 --- a/factories/skill_category_factory.py +++ b/factories/skill_category_factory.py @@ -15,7 +15,6 @@ class Meta: model = SkillCategory sqlalchemy_session = db.session - id = factory.Sequence(lambda n: n) name = factory.Faker("name") help = factory.Faker("paragraph") is_active = fake.boolean(chance_of_getting_true=75) @@ -49,7 +48,6 @@ class SkillCategoryFactoryFake(factory.Factory): class Meta: model = SkillCategory - id = factory.Sequence(lambda n: n) name = factory.Faker("name") help = factory.Faker("paragraph") is_active = fake.boolean(chance_of_getting_true=75) diff --git a/tests/integration/endpoints/test_skills_category_endpoints.py b/tests/integration/endpoints/test_skills_category_endpoints.py index f998a68..d8ced40 100644 --- a/tests/integration/endpoints/test_skills_category_endpoints.py +++ b/tests/integration/endpoints/test_skills_category_endpoints.py @@ -60,6 +60,7 @@ def test_get_specific_skill_category_endpoint(self): role = RoleFactory.create() user_id = BaseTestCase.user_id() skills_category = SkillCategoryFactory.create() + skills_category.save() PermissionFactory.create(keyword="view_skills_categories", role=role) UserRoleFactory.create(user_id=user_id, role=role) @@ -67,6 +68,7 @@ def test_get_specific_skill_category_endpoint(self): self.make_url("/skills_categories/{}".format(skills_category.id)), headers=self.headers(), ) + response_json = self.decode_from_json_string(response.data.decode("utf-8")) payload = response_json["payload"] @@ -124,6 +126,7 @@ def test_invalid_update(self): def test_delete_skill_category_endpoint_with_right_permission(self): role = RoleFactory.create() skills_category = SkillCategoryFactory.create() + skills_category.save() user_id = BaseTestCase.user_id() PermissionFactory.create(keyword="delete_skills_categories", role=role) @@ -132,6 +135,7 @@ def test_delete_skill_category_endpoint_with_right_permission(self): self.make_url(f"/skills_categories/{skills_category.id}"), headers=self.headers(), ) + response_json = self.decode_from_json_string(response.data.decode("utf-8")) payload = response_json["payload"] diff --git a/tests/unit/controllers/test_skill_controller.py b/tests/unit/controllers/test_skill_controller.py new file mode 100644 index 0000000..6a20df0 --- /dev/null +++ b/tests/unit/controllers/test_skill_controller.py @@ -0,0 +1,298 @@ +"""Unit tests for the skill controller. +""" +from datetime import datetime +from unittest.mock import patch, MagicMock + +from app.controllers.skill_controller import SkillController +from app.models import User +from app.models.permission import Permission +from app.models.skill import Skill +from app.repositories import UserRepo +from app.repositories.skill_repo import SkillRepo +from app.repositories.permission_repo import PermissionRepo +from factories import SkillCategoryFactory, SkillFactory + +from tests.base_test_case import BaseTestCase + + +class TestSkillController(BaseTestCase): + def setUp(self): + self.BaseSetUp() + self.mock_skill_category = SkillCategoryFactory.create() + self.mock_skill_category.save() + + self.mock_skill = Skill( + id=1, + created_at=datetime.now(), + updated_at=datetime.now(), + name="Mock skill", + skill_category_id=self.mock_skill_category.id, + skill_category=self.mock_skill_category, + ) + self.mock_skill.save() + self.mock_skill2 = SkillFactory.create( + created_at=datetime.now(), + updated_at=datetime.now(), + name="Mock skill2", + skill_category_id=self.mock_skill_category.id, + skill_category=self.mock_skill_category, + ) + self.mock_skill2.save() + + def tearDown(self): + self.BaseTearDown() + + @patch.object(SkillRepo, "get_unpaginated") + def test_list_skill_ok_response( + self, + mock_skill_repo_get_unpaginated, + ): + """Test list_skill OK response.""" + # Arrange + with self.app.app_context(): + mock_skill_repo_get_unpaginated.return_value.items = [ + self.mock_skill, + ] + skill_controller = SkillController(self.request_context) + + # Act + result = skill_controller.list_skills() + print(result.__dict__) + # Assert + assert result.status_code == 200 + assert result.get_json()["msg"] == "OK" + + @patch.object(SkillRepo, "find_first") + def test_get_skill_when_invalid_or_missing(self, mock_skill_repo_find_first): + """Test get_skill invalid repo response.""" + # Arrange + with self.app.app_context(): + mock_skill_repo_find_first.return_value = None + skill_controller = SkillController(self.request_context) + + # Act + result = skill_controller.get_skill(99) + + # Assert + assert result.status_code == 400 + assert result.get_json()["msg"] == "Invalid or Missing skill_id" + + @patch.object(SkillRepo, "find_first") + def test_get_skill_ok_response(self, mock_skill_repo_find_first): + """Test get_skill OK response.""" + # Arrange + with self.app.app_context(): + mock_skill_repo_find_first.return_value = self.mock_skill + skill_controller = SkillController(self.request_context) + + # Act + result = skill_controller.get_skill(1) + + # Assert + assert result.status_code == 200 + assert result.get_json()["msg"] == "OK" + + @patch.object(SkillController, "request_params") + @patch.object(SkillRepo, "find_first") + def test_create_skill_when_name_and_category_id_already_exists( + self, + mock_skill_repo_find_first, + mock_skill_controller_request_params, + ): + """Test create_skill when name and category id already exists.""" + # Arrange + with self.app.app_context(): + + mock_skill_controller_request_params.return_value = ( + self.mock_skill.name, + self.mock_skill.skill_category_id, + ) + mock_skill_repo_find_first.return_value = self.mock_skill + skill_controller = SkillController(self.request_context) + + # Act + result = skill_controller.create_skill() + + # Assert + assert result.status_code == 400 + assert ( + result.get_json()["msg"] + == f"Skill name {self.mock_skill.name} with category name " + f"{self.mock_skill.skill_category.name} already exists" + ) + + @patch.object(SkillController, "request_params") + @patch.object(SkillRepo, "find_first") + def test_create_skill_ok_response( + self, + mock_skill_repo_find_first, + mock_skill_controller_request_params, + ): + """Test create_skill OK response.""" + # Arrange + with self.app.app_context(): + mock_skill_controller_request_params.return_value = ( + "Skill Name", + self.mock_skill_category.id, + ) + mock_skill_repo_find_first.return_value = None + skill_controller = SkillController(self.request_context) + + # Act + result = skill_controller.create_skill() + + # Assert + assert result.status_code == 201 + assert result.get_json()["msg"] == "OK" + + @patch.object(SkillController, "request_params") + @patch.object(SkillRepo, "find_first") + @patch.object(SkillRepo, "get") + def test_update_skill_when_skill_doesnot_exist( + self, + mock_skill_repo_get, + mock_skill_repo_find_first, + mock_skill_controller_request_params, + ): + """Test update_skill when skill doesn't exist.""" + # Arrange + with self.app.app_context(): + mock_skill_repo_get.return_value = None + mock_skill_repo_find_first.return_value = None + mock_skill_controller_request_params.return_value = ( + 99, + None, + None, + ) + skill_controller = SkillController(self.request_context) + + # Act + result = skill_controller.update_skill(99) + + # Assert + assert result.status_code == 404 + assert result.get_json()["msg"] == "Skill Not Found" + + @patch.object(SkillRepo, "get") + @patch.object(SkillRepo, "find_first") + @patch.object(SkillController, "request_params") + def test_update_skill_when_name_is_already_taken( + self, + mock_skill_controller_request_params, + mock_skill_repo_find_first, + mock_skill_repo_get, + ): + """Test update_skill when name already exists.""" + # Arrange + with self.app.app_context(): + mock_skill_repo_get.return_value = self.mock_skill2 + mock_skill_repo_find_first.return_value = self.mock_skill + mock_skill_controller_request_params.return_value = ( + self.mock_skill.id, + self.mock_skill2.name, + self.mock_skill_category.id, + ) + skill_controller = SkillController(self.request_context) + + # Act + result = skill_controller.update_skill(self.mock_skill.id) + + # Assert + assert result.status_code == 400 + assert ( + result.get_json()["msg"] + == f"Skill name 'Mock skill2' with category name {self.mock_skill_category.name} already exists" + ) + + @patch.object(SkillRepo, "find_first") + @patch.object(SkillController, "request_params") + @patch.object(SkillRepo, "get") + def test_update_skill_when_id_do_not_match( + self, + mock_skill_repo_get, + mock_skill_controller_request_params, + mock_skill_repo_find_first, + ): + """Test update_skill when role doesn't exist.""" + # Arrange + with self.app.app_context(): + mock_skill_repo_get.return_value = self.mock_skill + mock_skill_repo_find_first.return_value = self.mock_skill + mock_skill_controller_request_params.return_value = ( + "Mock name", + "Mock help", + 8, + ) + skill_controller = SkillController(self.request_context) + + # Act + result = skill_controller.update_skill(1) + + # Assert + assert result.status_code == 400 + assert result.get_json()["msg"] == "Invalid or incorrect skill_id provided" + + @patch.object(SkillRepo, "get") + @patch.object(SkillRepo, "find_first") + @patch.object(SkillController, "request_params") + def test_update_skill_ok_response( + self, + mock_skill_controller_request_params, + mock_skill_repo_find_first, + mock_skill_repo_get, + ): + """Test update_skill when role doesn't exist.""" + # Arrange + with self.app.app_context(): + mock_skill_repo_get.return_value = self.mock_skill + mock_skill_repo_find_first.return_value = self.mock_skill + mock_skill_controller_request_params.return_value = ( + self.mock_skill.id, + "New mockwer", + self.mock_skill.skill_category_id, + ) + skill_controller = SkillController(self.request_context) + + # Act + result = skill_controller.update_skill(self.mock_skill.id) + print(result.__dict__) + + # Assert + assert result.status_code == 200 + assert result.get_json()["msg"] == "OK" + + @patch.object(SkillRepo, "get") + def test_delete_skill_when_skill_is_invalid(self, mock_skill_repo_get): + """Test delete_skill when the role is invalid.""" + # Arrange + with self.app.app_context(): + mock_skill_repo_get.return_value = None + skill_controler = SkillController(self.request_context) + + # Act + result = skill_controler.delete_skill(1) + + # Assert + assert result.status_code == 404 + assert ( + result.get_json()["msg"] == "Invalid or incorrect " "skill_id provided" + ) + + @patch.object(SkillRepo, "get") + @patch.object(SkillRepo, "update") + def test_delete_skill_ok_response( + self, mock_skill_repo_update, mock_skill_repo_get + ): + """Test delete_skill when the role is invalid.""" + # Arrange + with self.app.app_context(): + mock_skill_repo_get.return_value = self.mock_skill + mock_skill_repo_update.return_value = self.mock_skill + skill_controler = SkillController(self.request_context) + + # Act + result = skill_controler.delete_skill(1) + + # Assert + assert result.status_code == 200 + assert result.get_json()["msg"] == "skill deleted" diff --git a/tests/unit/repositories/test_skill_repo.py b/tests/unit/repositories/test_skill_repo.py new file mode 100644 index 0000000..834ebce --- /dev/null +++ b/tests/unit/repositories/test_skill_repo.py @@ -0,0 +1,23 @@ +from app.models import Skill +from app.repositories.skill_repo import SkillRepo +from factories.skill_category_factory import SkillFactoryFake +from tests.base_test_case import BaseTestCase + + +class TestSkillRepo(BaseTestCase): + def setUp(self): + self.BaseSetUp() + self.repo = SkillRepo() + + def tearDown(self): + self.BaseTearDown() + + def test_new_skill_method_returns_new__object(self): + skill_ = SkillFactoryFake.build() + + new_skill_ = self.repo.new_skill( + skill_.name, + skill_.skill_category_id, + ) + self.assertIsInstance(new_skill_, Skill) + self.assertEqual(new_skill_.name, skill_.name) diff --git a/vessel.py b/vessel.py index aeefb68..0f854c9 100755 --- a/vessel.py +++ b/vessel.py @@ -1,10 +1,10 @@ #!/usr/bin/env python -''' +""" Vessel CLI Tool * Usage: python vessel.py * * Command Line Arguments -* make:model name eg. python vessel.py make:model user [--with_repo [_controller] ] +* * make:repo name eg. python vessel.py make:repo user * make:blueprint name eg. python vessel.py make:blueprint vendors [--url_prefix=vendors] * make:controller name eg. python vessel.py make:controller user @@ -13,7 +13,7 @@ * * * NOTE: Please use the singular form of the words. Vessel CLI tool will auto generate the plural forms where needed. -''' +""" import sys, os import inflect @@ -22,77 +22,95 @@ inflect_engine = inflect.engine() + def create_model(name): - name_clean = to_pascal_case(name) - model_stub = '''from .base_model import BaseModel, db + name_clean = to_pascal_case(name) + model_stub = """from .base_model import BaseModel, db class {model_name}(BaseModel): __tablename__ = '{table_name}' - '''.format(model_name=name_clean, table_name=inflect_engine.plural(name)) - - model_file_path = 'app/models/{}.py'.format(name) - write_file(model_file_path, model_stub) - return name_clean, model_file_path + """.format( + model_name=name_clean, table_name=inflect_engine.plural(name) + ) + + model_file_path = "app/models/{}.py".format(name) + write_file(model_file_path, model_stub) + return name_clean, model_file_path + def create_repo(name): - name_clean = to_pascal_case(name) #''.join(list(map(lambda x: x.capitalize(), name.split('_')))) - repo_stub = '''from app.repositories.base_repo import BaseRepo + name_clean = to_pascal_case( + name + ) #''.join(list(map(lambda x: x.capitalize(), name.split('_')))) + repo_stub = """from app.repositories.base_repo import BaseRepo from app.models.{name} import {name_clean} class {name_clean}Repo(BaseRepo): - + def __init__(self): - BaseRepo.__init__(self, {name_clean})'''.format(name=name, name_clean=name_clean) - repo_file_path = 'app/repositories/{}_repo.py'.format(name) - write_file(repo_file_path, repo_stub) - return name_clean, repo_file_path + BaseRepo.__init__(self, {name_clean})""".format( + name=name, name_clean=name_clean + ) + repo_file_path = "app/repositories/{}_repo.py".format(name) + write_file(repo_file_path, repo_stub) + return name_clean, repo_file_path + def create_blueprint(name, url_prefix=None): - uprefix = '' if url_prefix is None else inflect_engine.plural(url_prefix) - blueprint_stub = '''from app.blueprints.base_blueprint import Blueprint, BaseBlueprint, request, Security, Auth + uprefix = "" if url_prefix is None else inflect_engine.plural(url_prefix) + blueprint_stub = """from app.blueprints.base_blueprint import Blueprint, BaseBlueprint, request, Security, Auth url_prefix = '{{}}/{url_prefix}'.format(BaseBlueprint.base_url_prefix) {name}_blueprint = Blueprint('{name}', __name__, url_prefix=url_prefix) - '''.format(name=name, url_prefix=uprefix) - blueprint_file_path = 'app/blueprints/{}_blueprint.py'.format(name) - write_file(blueprint_file_path, blueprint_stub) - return name, blueprint_file_path + """.format( + name=name, url_prefix=uprefix + ) + blueprint_file_path = "app/blueprints/{}_blueprint.py".format(name) + write_file(blueprint_file_path, blueprint_stub) + return name, blueprint_file_path + def create_controller(name): - name_clean = to_pascal_case(name) - controller_stub = '''from app.controllers.base_controller import BaseController + name_clean = to_pascal_case(name) + controller_stub = """from app.controllers.base_controller import BaseController class {name}Controller(BaseController): def __init__(self, request): BaseController.__init__(self, request) - '''.format(name=name_clean) - - controller_file_path = 'app/controllers/{}_controller.py'.format(name) - write_file(controller_file_path, controller_stub) - return name_clean, controller_file_path + """.format( + name=name_clean + ) + + controller_file_path = "app/controllers/{}_controller.py".format(name) + write_file(controller_file_path, controller_stub) + return name_clean, controller_file_path + def create_test(name, test_path=None): - name_clean = to_pascal_case(name) - test_file_path = 'tests' - - if test_path: - os.makedirs(os.path.join(test_file_path, test_path), mode=0o777, exist_ok=True) - test_file_path = os.path.join(test_file_path, test_path) - - test_stub = '''from tests.base_test_case import BaseTestCase + name_clean = to_pascal_case(name) + test_file_path = "tests" + + if test_path: + os.makedirs(os.path.join(test_file_path, test_path), mode=0o777, exist_ok=True) + test_file_path = os.path.join(test_file_path, test_path) + + test_stub = """from tests.base_test_case import BaseTestCase class {name}(BaseTestCase): def setUp(self): self.BaseSetUp() - '''.format(name=name_clean) - test_file_path = '{}/{}.py'.format(test_file_path, name) - write_file(test_file_path, test_stub) - return name_clean, test_file_path + """.format( + name=name_clean + ) + test_file_path = "{}/{}.py".format(test_file_path, name) + write_file(test_file_path, test_stub) + return name_clean, test_file_path + def create_factory(name): - name_clean = to_pascal_case(name) - test_stub = '''import factory + name_clean = to_pascal_case(name) + test_stub = """import factory from app.utils import db from app.models.{name} import {name_clean} @@ -106,88 +124,93 @@ class {name_clean}Factory(factory.alchemy.SQLAlchemyModelFactory): class Meta: model = {name_clean} sqlalchemy_session = db.session - + id = factory.Sequence(lambda n: n) # name = factory.Faker('name') # cohort_position = fake_cohort_position - - '''.format(name_clean=name_clean, name=name) - factory_file_path = 'factories/{}_factory.py'.format(name) - write_file(factory_file_path, test_stub) - return name_clean, factory_file_path - - pass + + """.format( + name_clean=name_clean, name=name + ) + factory_file_path = "factories/{}_factory.py".format(name) + write_file(factory_file_path, test_stub) + return name_clean, factory_file_path + + pass + def write_file(file_path, file_content): - with open(file_path, 'w', encoding='utf-8') as file_handle: - file_handle.write(file_content) - -if __name__ == '__main__': - - args = sys.argv - command = args[1] - - if command == 'make:model' or command == 'make:models': - name = args[2] - - extras = list() - if len(args) > 3: - extras = args[3].split('--with_')[1].split('_') - m = create_model(name) - print(colored('Model: {} Location: {}'.format(m[0], m[1]), 'green')) - - if 'controller' in extras: - c = create_controller(name=name) - print(colored('Controller: {}Controller Location: {}'.format(c[0], c[1]), 'green')) - - if 'repo' in extras: - r = create_repo(name=name) - print(colored('Repository: {}Repo Location: {}'.format(r[0], r[1]), 'green')) - - - if command == 'make:repo' or command == 'make:repos': - name = args[2] - r = create_repo(name=name) - print(colored('Repository: {}Repo Location: {}'.format(r[0], r[1]), 'green')) - - - if command == 'make:blueprint' or command == 'make:blueprints': - name = args[2] - url_prefix = None - - if len(args) > 3: - url_prefix = args[3].split('--url_prefix=')[1] - - b = create_blueprint(name, url_prefix) - print(colored('Blueprint: {} Location: {}'.format(b[0], b[1]), 'green')) - - - if command == 'make:controller' or command == 'make:controllers': - name = args[2] - c = create_controller(name=name) - print(colored('Controller: {}Controller Location: {}'.format(c[0], c[1]), 'green')) - - - if command == 'make:test' or command == 'make:tests': - name = args[2] - test_path = None - # check if test contains path separator. - if name.find('/') > -1: - arr = os.path.split(name) - test_path = arr[0] - name = arr[1] - - t = create_test(name=name, test_path=test_path) - print(colored('Test: {} Created Location: {}'.format(t[0], t[1]), 'green')) - - - if command == 'make:factory' or command == 'make:factories': - name = args[2] - - f = create_factory(name) - print(colored('Factory: {}Factory Location: {}'.format(f[0], f[1]), 'green')) - - if command == 'show_routes': - os.system('python run.py show_routes') - - \ No newline at end of file + with open(file_path, "w", encoding="utf-8") as file_handle: + file_handle.write(file_content) + + +if __name__ == "__main__": + + args = sys.argv + command = args[1] + + if command == "make:model" or command == "make:models": + name = args[2] + + extras = list() + if len(args) > 3: + extras = args[3].split("--with_")[1].split("_") + m = create_model(name) + print(colored("Model: {} Location: {}".format(m[0], m[1]), "green")) + + if "controller" in extras: + c = create_controller(name=name) + print( + colored( + "Controller: {}Controller Location: {}".format(c[0], c[1]), "green" + ) + ) + + if "repo" in extras: + r = create_repo(name=name) + print( + colored("Repository: {}Repo Location: {}".format(r[0], r[1]), "green") + ) + + if command == "make:repo" or command == "make:repos": + name = args[2] + r = create_repo(name=name) + print(colored("Repository: {}Repo Location: {}".format(r[0], r[1]), "green")) + + if command == "make:blueprint" or command == "make:blueprints": + name = args[2] + url_prefix = None + + if len(args) > 3: + url_prefix = args[3].split("--url_prefix=")[1] + + b = create_blueprint(name, url_prefix) + print(colored("Blueprint: {} Location: {}".format(b[0], b[1]), "green")) + + if command == "make:controller" or command == "make:controllers": + name = args[2] + c = create_controller(name=name) + print( + colored("Controller: {}Controller Location: {}".format(c[0], c[1]), "green") + ) + + if command == "make:test" or command == "make:tests": + name = args[2] + test_path = None + # check if test contains path separator. + if name.find("/") > -1: + arr = os.path.split(name) + test_path = arr[0] + name = arr[1] + + t = create_test(name=name, test_path=test_path) + print(colored("Test: {} Created Location: {}".format(t[0], t[1]), "green")) + + if command == "make:factory" or command == "make:factories": + name = args[2] + + f = create_factory(name) + print(colored("Factory: {}Factory Location: {}".format(f[0], f[1]), "green")) + + if command == "show_routes": + os.system("python run.py show_routes")