Skip to content

Commit d208174

Browse files
author
Nicholas
authored
- Completed initial resolvers for domains (canada-ca#99)
- Created test for initial domain resvolers - Added domain resovlers into queries.py - Fixed styling issues in url.py, and added new REGEX to find help reduce incorrect input - Fixed styling issues in email_address.py
1 parent 42b43b5 commit d208174

6 files changed

Lines changed: 331 additions & 9 deletions

File tree

api/queries.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515

1616
from schemas.user import *
1717

18+
from scalars.url import URL
19+
1820
from schemas.sectors import Sectors
1921
from schemas.groups import Groups
2022
from schemas.organizations import Organizations
23+
from schemas.domains import Domains
2124

2225

2326
from resolvers.sectors import (
@@ -33,7 +36,6 @@
3336

3437
)
3538

36-
3739
from resolvers.users import (
3840
resolve_test_user_claims,
3941
resolve_generate_otp_url,
@@ -45,6 +47,11 @@
4547
resolve_get_orgs_by_group
4648
)
4749

50+
from resolvers.domains import (
51+
resolve_get_domain_by_id,
52+
resolve_get_domain_by_domain,
53+
resolve_get_domain_by_organization
54+
)
4855

4956
class Query(graphene.ObjectType):
5057
"""The central gathering point for all of the GraphQL queries."""
@@ -107,6 +114,24 @@ class Query(graphene.ObjectType):
107114
resolver=resolve_get_orgs_by_group,
108115
description="Allows the selection of organizations from a given group"
109116
)
117+
get_domain_by_id = graphene.List(
118+
of_type=Domains,
119+
id=graphene.Argument(graphene.Int, required=True),
120+
resolver=resolve_get_domain_by_id,
121+
description="Allows the selection of a domain from a given ID"
122+
)
123+
get_domain_by_domain = graphene.List(
124+
of_type=Domains,
125+
url=graphene.Argument(URL, required=True),
126+
resolver=resolve_get_domain_by_domain,
127+
description="Allows the selection of a domain from a given domain"
128+
)
129+
get_domain_by_organization = graphene.List(
130+
of_type=Domains,
131+
org=graphene.Argument(OrganizationsEnum, required=True),
132+
resolver=resolve_get_domain_by_organization,
133+
description="Allows the selection of domains under an organization"
134+
)
110135

111136
generate_otp_url = graphene.String(
112137
email=graphene.Argument(EmailAddress, required=True),

api/resolvers/domains.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from graphql import GraphQLError
2+
from sqlalchemy.orm import load_only
3+
4+
from schemas.domains import (
5+
Domains,
6+
DomainModel
7+
)
8+
9+
from schemas.organizations import (
10+
Organizations,
11+
OrganizationsModel
12+
)
13+
14+
15+
# Resolvers
16+
def resolve_get_domain_by_id(self, info, **kwargs):
17+
"""Return a domain by its row ID"""
18+
group_id = kwargs.get('id', 1)
19+
query = Domains.get_query(info).filter(
20+
DomainModel.id == group_id
21+
)
22+
if not len(query.all()):
23+
raise GraphQLError("Error, Invalid ID")
24+
return query.all()
25+
26+
27+
def resolve_get_domain_by_domain(self, info, **kwargs):
28+
"""Return a domain by a url"""
29+
domain = kwargs.get('url')
30+
query = Domains.get_query(info).filter(
31+
DomainModel.domain == domain
32+
)
33+
if not len(query.all()):
34+
raise GraphQLError("Error, domain does not exist")
35+
return query.all()
36+
37+
38+
def resolve_get_domain_by_organization(self, info, **kwargs):
39+
"""Return a list of domains by by their associated organization"""
40+
organization = kwargs.get('org')
41+
42+
organization_id = Organizations.get_query(info).filter(
43+
OrganizationsModel.organization == organization
44+
).options(load_only('id'))
45+
46+
if not len(organization_id.all()):
47+
raise GraphQLError("Error, no organization associated with that enum")
48+
49+
query = Domains.get_query(info).filter(
50+
DomainModel.organization_id == organization_id
51+
)
52+
53+
if not len(query.all()):
54+
raise GraphQLError("Error, no domains associated with that organization")
55+
return query.all()

api/scalars/email_address.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515

1616

1717
class EmailAddress(Scalar):
18-
'''A field whose value conforms to the standard internet email address format as specified in RFC822:
19-
https://www.w3.org/Protocols/rfc822/.'''
18+
"""
19+
A field whose value conforms to the standard internet email address format as specified in RFC822:
20+
https://www.w3.org/Protocols/rfc822/.
21+
"""
2022

2123
@staticmethod
2224
def serialize(value):

api/scalars/url.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,47 @@
1+
from re import compile
12
from graphene.types import Scalar
23
from graphql.language import ast
34
from graphql import GraphQLError
45

56
from functions.error_messages import *
67

8+
URL_REGEX = r'[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)'
9+
10+
URL_REGEX_CHECK = compile(URL_REGEX)
11+
712

813
class URL(Scalar):
9-
'''A field whose value conforms to the standard URL format as specified in RFC3986:
10-
https://www.ietf.org/rfc/rfc3986.txt.'''
14+
"""
15+
A field whose value conforms to the standard URL format as specified in RFC3986:
16+
https://www.ietf.org/rfc/rfc3986.txt.
17+
"""
1118

1219
@staticmethod
1320
def serialize(value):
14-
return str(value)
21+
if not isinstance(value, str):
22+
raise GraphQLError(scalar_error_type("String", value))
23+
24+
if not URL_REGEX_CHECK.search(value):
25+
raise GraphQLError(scalar_error_type("URL", value))
26+
27+
return value
1528

1629
@staticmethod
1730
def parse_value(value):
18-
return str(value)
31+
if not isinstance(value, str):
32+
raise GraphQLError(scalar_error_type("String", value))
33+
34+
if not URL_REGEX_CHECK.search(value):
35+
raise GraphQLError(scalar_error_type("URL", value))
36+
37+
return value
1938

2039
@staticmethod
2140
def parse_literal(node):
2241
if not isinstance(node, ast.StringValue):
2342
raise GraphQLError(scalar_error_only_types("strings", "URLs", str(ast.Type)))
2443

25-
return str(node.value)
44+
if not URL_REGEX_CHECK.search(node.value):
45+
raise GraphQLError(scalar_error_type("URL", node.value))
46+
47+
return node.value

api/tests/test_domains_resolver.py

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import sys
2+
import os
3+
from os.path import dirname, join, expanduser, normpath, realpath
4+
5+
import pytest
6+
from graphene.test import Client
7+
8+
from unittest import TestCase
9+
10+
import model_enums
11+
model_enums._called_from_test = True
12+
13+
from app import app
14+
from db import db
15+
from models import Sectors, Groups, Organizations, Domains
16+
from queries import schema
17+
18+
19+
# This is the only way I could get imports to work for unit testing.
20+
PACKAGE_PARENT = '..'
21+
SCRIPT_DIR = dirname(realpath(join(os.getcwd(), expanduser(__file__))))
22+
sys.path.append(normpath(join(SCRIPT_DIR, PACKAGE_PARENT)))
23+
24+
25+
@pytest.fixture(scope='class')
26+
def domain_test_resolver_db_init():
27+
"""Build database for domain resolver testing"""
28+
db.init_app(app)
29+
30+
sectors_added = False
31+
groups_added = False
32+
org_added = False
33+
domain_added = False
34+
35+
with app.app_context():
36+
if Sectors.query.first() is None:
37+
sector = Sectors(
38+
id=2,
39+
zone="GC",
40+
sector="GC_BF",
41+
description="Banking and Finance"
42+
)
43+
db.session.add(sector)
44+
db.session.commit()
45+
sectors_added = True
46+
47+
if Groups.query.first() is None:
48+
group = Groups(
49+
id=2,
50+
s_group='GC_BF',
51+
description='Banking and Finance',
52+
sector_id=2
53+
)
54+
db.session.add(group)
55+
db.session.commit()
56+
groups_added = True
57+
58+
if Organizations.query.first() is None:
59+
org = Organizations(
60+
id=6,
61+
organization='BOC',
62+
description='BOC - Bank of Canada',
63+
group_id=2
64+
)
65+
db.session.add(org)
66+
db.session.commit()
67+
org_added = True
68+
69+
if Domains.query.first() is None:
70+
domain = Domains(
71+
id=15,
72+
domain='bankofcanada.ca',
73+
organization_id=6
74+
)
75+
db.session.add(domain)
76+
db.session.commit()
77+
domain_added = True
78+
79+
yield
80+
81+
with app.app_context():
82+
if domain_added:
83+
Domains.query.filter(Domains.id == 15).delete()
84+
db.session.commit()
85+
if org_added:
86+
Organizations.query.filter(Organizations.id == 6).delete()
87+
db.session.commit()
88+
if groups_added:
89+
Groups.query.filter(Groups.id == 2).delete()
90+
db.session.commit()
91+
if sectors_added:
92+
Sectors.query.filter(Sectors.id == 2).delete()
93+
db.session.commit()
94+
95+
96+
@pytest.mark.usefixtures('domain_test_resolver_db_init')
97+
class TestOrgResolver(TestCase):
98+
def test_get_domain_resolvers_by_id(self):
99+
"""Test get_domain_by_id resolver"""
100+
with app.app_context():
101+
client = Client(schema)
102+
query = """
103+
{
104+
getDomainById(id: 15){
105+
domain
106+
}
107+
}"""
108+
109+
result_refr = {
110+
"data": {
111+
"getDomainById": [
112+
{
113+
"domain": "bankofcanada.ca"
114+
}
115+
]
116+
}
117+
}
118+
119+
result_eval = client.execute(query)
120+
self.assertDictEqual(result_refr, result_eval)
121+
122+
def test_get_domain_resolvers_by_domain(self):
123+
""""Test get_domain_by_domain resolver"""
124+
with app.app_context():
125+
client = Client(schema)
126+
query = """
127+
{
128+
getDomainByDomain(url: "bankofcanada.ca"){
129+
domain
130+
}
131+
}"""
132+
133+
result_refr = {
134+
"data": {
135+
"getDomainByDomain": [
136+
{
137+
"domain": "bankofcanada.ca"
138+
}
139+
]
140+
}
141+
}
142+
143+
result_eval = client.execute(query)
144+
self.assertDictEqual(result_refr, result_eval)
145+
146+
def test_get_domain_resolvers_by_org(self):
147+
"""Test get_domain_by_org_enum resolver"""
148+
with app.app_context():
149+
client = Client(schema)
150+
query = """
151+
{
152+
getDomainByOrganization(org: BOC){
153+
domain
154+
}
155+
}"""
156+
result_refr = {
157+
"data": {
158+
"getDomainByOrganization": [
159+
{
160+
"domain": "bankofcanada.ca"
161+
}
162+
]
163+
}
164+
}
165+
166+
result_eval = client.execute(query)
167+
self.assertDictEqual(result_refr, result_eval)
168+
169+
def test_domain_resolver_by_id_invalid(self):
170+
"""Test get_domain_by_id invalid ID error handling"""
171+
with app.app_context():
172+
client = Client(schema)
173+
query = """
174+
{
175+
getDomainById(id: 9999){
176+
domain
177+
}
178+
}
179+
"""
180+
executed = client.execute(query)
181+
182+
assert executed['errors']
183+
assert executed['errors'][0]
184+
assert executed['errors'][0]['message'] == "Error, Invalid ID"
185+
186+
def test_domain_resolver_by_org_invalid(self):
187+
"""Test get_domain_by_domain invalid sector error handling"""
188+
with app.app_context():
189+
client = Client(schema)
190+
query = """
191+
{
192+
getDomainByDomain(url: "google.ca"){
193+
domain
194+
}
195+
}
196+
"""
197+
executed = client.execute(query)
198+
199+
assert executed['errors']
200+
assert executed['errors'][0]
201+
assert executed['errors'][0]['message'] == 'Error, domain does not exist'
202+
203+
def test_domain_resolver_by_org_invalid(self):
204+
"""Test get_domain_by_org invalid Zone error handling"""
205+
with app.app_context():
206+
client = Client(schema)
207+
query = """
208+
{
209+
getDomainByOrganization(org: fds){
210+
domain
211+
}
212+
}
213+
"""
214+
executed = client.execute(query)
215+
216+
assert executed['errors']
217+
assert executed['errors'][0]
218+
assert executed['errors'][0]['message'] == f'Argument "org" has invalid value fds.\nExpected type "OrganizationsEnum", found fds.'

0 commit comments

Comments
 (0)