diff --git a/api/functions/generate_enums.py b/api/functions/generate_enums.py index 0c5cee46b..bfd07113e 100644 --- a/api/functions/generate_enums.py +++ b/api/functions/generate_enums.py @@ -1,4 +1,5 @@ import graphene +import enum from sqlalchemy.orm import load_only from model_enums import create_enum_app, create_enum_db from models import ( @@ -40,9 +41,54 @@ def create_enums(Table, enum_name, column): enum_dict.update({select[1]: select[1]}) if not len(enum_dict): - return graphene.Enum(enum_name, 'EMPTY') + ret_enum = enum.Enum(enum_name, 'EMPTY') else: - return graphene.Enum(enum_name, enum_dict) + ret_enum = enum.Enum(enum_name, enum_dict) + + return graphene.Enum.from_enum(ret_enum) + + +def create_enum_with_descriptions(Table, enum_name, enum_column, description_column): + """ + Function to allow the creation of enums with descriptions for various uses. Function takes in SQLAlchemy Model, + and the Column that you would like your enums to be created. The value of the enum is equal to that of the name + """ + app = create_enum_app() + db = create_enum_db(app) + + with app.app_context(): + query = db.session.query(Table).options(load_only(enum_column)) + rows = db.session.execute(query).fetchall() + + enum_dict = dict() + + for select in rows: + enum_dict.update({select[1]: select[1]}) + + if not len(enum_dict): + ret_enum = enum.Enum(enum_name, 'EMPTY') + else: + ret_enum = enum.Enum(enum_name, enum_dict) + + description_dict = dict() + + for e in ret_enum: + with app.app_context(): + query = db.session.query(Table).filter(getattr(Table, enum_column) == e.name).options(load_only(description_column)) + row = db.session.execute(query).first() + + try: + description_dict.update({e.name: row[1]}) + except: + description_dict.update({e.name: "Error loading description"}) + + @property + def description(self): + return description_dict.get(self.name) + + setattr(ret_enum, 'description', description) + + return graphene.Enum.from_enum(ret_enum) # Example of how to create enums for a given table # from functions.generate_enums import create_enums diff --git a/api/model_enums/organiztions.py b/api/model_enums/organiztions.py new file mode 100644 index 000000000..e09e657a2 --- /dev/null +++ b/api/model_enums/organiztions.py @@ -0,0 +1,4 @@ +from models import Organizations +from functions.generate_enums import create_enum_with_descriptions + +OrganizationsEnum = create_enum_with_descriptions(Organizations, 'OrganizationsEnum', 'organization', 'description') diff --git a/api/queries.py b/api/queries.py index 3955431af..4476167aa 100644 --- a/api/queries.py +++ b/api/queries.py @@ -7,11 +7,13 @@ from model_enums.sectors import SectorEnums, ZoneEnums from model_enums.groups import GroupEnums +from model_enums.organiztions import OrganizationsEnum from schemas.user import * from schemas.sectors import Sectors from schemas.groups import Groups +from schemas.organizations import Organizations from resolvers.sectors import ( @@ -26,6 +28,12 @@ resolve_get_group_by_sector ) +from resolvers.organizations import ( + resolve_get_org_by_id, + resolve_get_org_by_org, + resolve_get_orgs_by_group +) + class Query(graphene.ObjectType): """The central gathering point for all of the GraphQL queries.""" @@ -54,7 +62,7 @@ class Query(graphene.ObjectType): ) get_group_by_id = graphene.List( of_type=Groups, - id=graphene.Argument(graphene.Int, required=False), + id=graphene.Argument(graphene.Int, required=True), resolver=resolve_get_group_by_id, description="Allows selection of a group from a given group ID" ) @@ -70,6 +78,24 @@ class Query(graphene.ObjectType): resolver=resolve_get_group_by_sector, description="Allows selection of groups from a given sector enum" ) + get_org_by_id = graphene.List( + of_type=Organizations, + id=graphene.Argument(graphene.Int, required=True), + resolver=resolve_get_org_by_id, + description="Allows the selection of an organization from a given ID" + ) + get_org_by_org = graphene.List( + of_type=Organizations, + org=graphene.Argument(OrganizationsEnum, required=True), + resolver=resolve_get_org_by_org, + description="Allows the selection of an organization from its given organization code" + ) + get_org_by_group = graphene.List( + of_type=Organizations, + group=graphene.Argument(GroupEnums, required=True), + resolver=resolve_get_orgs_by_group, + description="Allows the selection of organizations from a given group" + ) generate_otp_url = String(email=String(required=True)) diff --git a/api/resolvers/organizations.py b/api/resolvers/organizations.py new file mode 100644 index 000000000..20cfe7e03 --- /dev/null +++ b/api/resolvers/organizations.py @@ -0,0 +1,53 @@ +from graphql import GraphQLError +from sqlalchemy.orm import load_only + +from schemas.organizations import ( + Organizations, + OrganizationsModel +) + +from schemas.groups import ( + Groups, + GroupsModel +) + + +# Resolvers +def resolve_get_org_by_id(self, info, **kwargs): + """Return an organization by its id""" + org_id = kwargs.get('id') + query = Organizations.get_query(info).filter( + OrganizationsModel.id == org_id + ) + if not len(query.all()): + raise GraphQLError("Error, Invalid ID") + return query.all() + + +def resolve_get_org_by_org(self, info, **kwargs): + """Return an organization by its organization code""" + org_code = kwargs.get('org') + query = Organizations.get_query(info).filter( + OrganizationsModel.organization == org_code + ) + if not len(query.all()): + raise GraphQLError("Error, Invalid Organization") + return query.all() + + +def resolve_get_orgs_by_group(self, info, **kwargs): + group = kwargs.get('group') + group_id = Groups.get_query(info).filter( + GroupsModel.s_group == group + ).options(load_only('id')) + + if not len(group_id.all()): + raise GraphQLError("Error, no group associated with that enum") + + query = Organizations.get_query(info).filter( + OrganizationsModel.group_id == group_id + ) + + if not len(query.all()): + raise GraphQLError("Error, no organizations associated with that group") + return query.all() diff --git a/api/tests/test_groups_resolver.py b/api/tests/test_groups_resolver.py index 6315e9ad1..724282927 100644 --- a/api/tests/test_groups_resolver.py +++ b/api/tests/test_groups_resolver.py @@ -7,6 +7,9 @@ from unittest import TestCase +import model_enums +model_enums._called_from_test = True + from app import app from db import db from models import Sectors, Groups @@ -23,6 +26,9 @@ def group_test_resolver_db_init(): """Build database for group resolver testing""" db.init_app(app) + sector_added = False + group_added = False + with app.app_context(): if Sectors.query.first() is None: sector = Sectors( @@ -33,6 +39,7 @@ def group_test_resolver_db_init(): ) db.session.add(sector) db.session.commit() + sector_added = True if Groups.query.first() is None: group = Groups( @@ -43,14 +50,17 @@ def group_test_resolver_db_init(): ) db.session.add(group) db.session.commit() + group_added = True - yield + yield - with app.app_context(): - Groups.query.filter(Groups.id == 1).delete() - db.session.commit() - Sectors.query.filter(Sectors.id == 1).delete() - db.session.commit() + with app.app_context(): + if group_added: + Groups.query.filter(Groups.id == 1).delete() + db.session.commit() + if sector_added: + Sectors.query.filter(Sectors.id == 1).delete() + db.session.commit() @pytest.mark.usefixtures('group_test_resolver_db_init') diff --git a/api/tests/test_organizations_resolver.py b/api/tests/test_organizations_resolver.py new file mode 100644 index 000000000..66a9d9b7a --- /dev/null +++ b/api/tests/test_organizations_resolver.py @@ -0,0 +1,213 @@ +import sys +import os +from os.path import dirname, join, expanduser, normpath, realpath + +import pytest +from graphene.test import Client + +from unittest import TestCase + +import model_enums +model_enums._called_from_test = True + +from app import app +from db import db +from models import Sectors, Groups, Organizations +from queries import schema + + +# This is the only way I could get imports to work for unit testing. +PACKAGE_PARENT = '..' +SCRIPT_DIR = dirname(realpath(join(os.getcwd(), expanduser(__file__)))) +sys.path.append(normpath(join(SCRIPT_DIR, PACKAGE_PARENT))) + + +@pytest.fixture(scope='class') +def org_test_resolver_db_init(): + """Build database for group resolver testing""" + db.init_app(app) + + sectors_added = False + groups_added = False + org_added = False + + with app.app_context(): + if Sectors.query.first() is None: + sector = Sectors( + id=1, + zone="GC", + sector="GC_A", + description="Arts" + ) + db.session.add(sector) + db.session.commit() + sectors_added = True + + if Groups.query.first() is None: + group = Groups( + id=1, + s_group='GC_A', + description='Arts', + sector_id=1 + ) + db.session.add(group) + db.session.commit() + groups_added = True + + if Organizations.query.first() is None: + org = Organizations( + id=1, + organization='Arts', + description='Arts', + group_id=1 + ) + db.session.add(org) + db.session.commit() + org_added = True + + yield + + with app.app_context(): + if org_added: + Organizations.query.filter(Organizations.id == 1).delete() + db.session.commit() + if groups_added: + Groups.query.filter(Groups.id == 1).delete() + db.session.commit() + if sectors_added: + Sectors.query.filter(Sectors.id == 1).delete() + db.session.commit() + + +@pytest.mark.usefixtures('org_test_resolver_db_init') +class TestOrgResolver(TestCase): + def test_get_org_resolvers_by_id(self): + """Test get_organization_by_id resolver""" + with app.app_context(): + client = Client(schema) + query = """ + { + getOrgById(id: 1){ + description + organization + } + }""" + + result_refr = { + "data": { + "getOrgById": [ + { + "description": "Arts", + "organization": "Arts" + } + ] + } + } + + result_eval = client.execute(query) + self.assertDictEqual(result_refr, result_eval) + + def test_get_org_resolvers_by_org(self): + """"Test get_org_by_org resolver""" + with app.app_context(): + client = Client(schema) + query = """ + { + getOrgByOrg(org: Arts){ + description + organization + } + }""" + + result_refr = { + "data": { + "getOrgByOrg": [ + { + "description": "Arts", + "organization": "Arts" + } + ] + } + } + + result_eval = client.execute(query) + self.assertDictEqual(result_refr, result_eval) + + def test_get_org_resolvers_by_group(self): + """Test get_org_by_group_id resolver""" + with app.app_context(): + client = Client(schema) + query = """ + { + getOrgByGroup(group: GC_A){ + description + groupId + } + }""" + result_refr = { + "data": { + "getOrgByGroup": [ + { + "description": "Arts", + "groupId": 1 + } + ] + } + } + + result_eval = client.execute(query) + self.assertDictEqual(result_refr, result_eval) + + def test_org_resolver_by_id_invalid(self): + """Test get_org_by_id invalid ID error handling""" + with app.app_context(): + client = Client(schema) + query = """ + { + getOrgById(id: 9999){ + description + groupId + } + } + """ + executed = client.execute(query) + + assert executed['errors'] + assert executed['errors'][0] + assert executed['errors'][0]['message'] == "Error, Invalid ID" + + def test_org_resolver_by_org_invalid(self): + """Test get_org_by_org invalid sector error handling""" + with app.app_context(): + client = Client(schema) + query = """ + { + getOrgByOrg(org: fds){ + id + description + } + } + """ + executed = client.execute(query) + + assert executed['errors'] + assert executed['errors'][0] + assert executed['errors'][0]['message'] == f'Argument "org" has invalid value fds.\nExpected type "OrganizationsEnum", found fds.' + + def test_org_resolver_by_group_invalid(self): + """Test get_org_by_group invalid Zone error handling""" + with app.app_context(): + client = Client(schema) + query = """ + { + getOrgByGroup(group: dsa){ + id + description + } + } + """ + executed = client.execute(query) + + assert executed['errors'] + assert executed['errors'][0] + assert executed['errors'][0]['message'] == f'Argument "group" has invalid value dsa.\nExpected type "GroupEnums", found dsa.' diff --git a/api/tests/test_sector_resolver.py b/api/tests/test_sector_resolver.py index 4d2cc3c69..3ae282422 100644 --- a/api/tests/test_sector_resolver.py +++ b/api/tests/test_sector_resolver.py @@ -7,6 +7,9 @@ from unittest import TestCase +import model_enums +model_enums._called_from_test = True + from app import app from db import db from models import Sectors