Skip to content

Commit f03b648

Browse files
jadudmbwang-icfJamesDemeryNava
authored
Tooling for improved local dev experience (#1423)
* First commit of the devtools work. This will configure and launch a stack. It isn't grabbing some things correctly, because of some more env-specific configuration that needs to be done. However, it's a solid start. * Updating; looks like it builds/runs. * Stack talks to BFD This worked for test. There is now more cleanup. There's a notional split between "setting up to run" and "getting the variables into the container." This moves all the work out of the container. It could, perhaps, all be moved into the container if the correct env was passed for secrets gathering? * A cleaner/quieter boot sequence. * Improving local build. * Fix where check happens * adding extra around kion setup * Add more checking, change params. * Updated/all tests passing * Removing script, out of scope * Incremental/bug fixes. * Fixes. * Fixed sandbox FHIRv3 URL. * Removing comments, unneeded prints. --------- Co-authored-by: Brandon Wang <[email protected]> Co-authored-by: James Demery <[email protected]>
1 parent 744a8f5 commit f03b648

29 files changed

+1168
-147
lines changed

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,9 @@ reqs-install:
1515

1616
reqs-install-dev:
1717
pip install -r requirements/requirements.dev.txt --no-index --find-links ./vendor/
18+
19+
build-local:
20+
cd dev-local ; make build-local ; cd ..
21+
22+
run-local:
23+
cd dev-local ; make run-local ; cd ..

apps/authorization/views.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class Meta:
3232
fields = ('id', 'name', 'logo_uri', 'tos_uri', 'policy_uri', 'contacts')
3333

3434
def get_contacts(self, obj):
35-
print(obj)
3635
application = Application.objects.get(id=obj.id)
3736
return application.support_email or ""
3837

apps/core/management/commands/create_test_feature_switches.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ def handle(self, *args, **options):
3434
for switch in WAFFLE_FEATURE_SWITCHES:
3535
try:
3636
Switch.objects.get(name=switch[0])
37-
self._log("Feature switch already exists: %s" % (str(switch)))
3837
except Switch.DoesNotExist:
3938
Switch.objects.create(name=switch[0], active=switch[1], note=switch[2])
4039
self._log("Feature switch created: %s" % (str(switch)))
@@ -46,7 +45,6 @@ def handle(self, *args, **options):
4645

4746
try:
4847
flag_obj = Flag.objects.get(name=flag[0])
49-
self._log("Feature flag already exists: %s" % (str(flag_obj)))
5048
except Flag.DoesNotExist:
5149
flag_obj = Flag.objects.create(name=flag[0])
5250
self._log("Feature flag created: %s" % (str(flag[0])))
@@ -62,7 +60,6 @@ def handle(self, *args, **options):
6260
flag_obj.save()
6361
self._log("User {} added to feature flag: {}".format(u, flag))
6462
except Exception as e:
65-
print(e)
6663
self._log("Exception when adding user {} to feature flag: {}".format(u, flag))
6764
except User.DoesNotExist:
6865
# assuming test users exist before creating flags associated with them

apps/dot_ext/tests/test_views.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
SCOPES_TO_URL_BASE_PATH,
2525
)
2626

27+
import os
28+
2729
from hhs_oauth_server.settings.base import MOCK_FHIR_ENDPOINT_HOSTNAME
2830

2931

@@ -576,16 +578,15 @@ def test_delete_token_success(self):
576578

577579
# This assertion is incorrectly crafted - it actually requires a local server started
578580
# so that the fhir fetch data is called and hence generate cert file not found error.
579-
# TODO: refactor test to not depend on a server up and running.
580-
581-
# Post Django 2.2: An OSError exception is expected when trying to reach the
582-
# backend FHIR server and proves authentication worked.
583-
with self.assertRaisesRegexp(
584-
OSError, 'Could not find the TLS certificate file'
585-
):
586-
response = self.client.get(
587-
'/v1/fhir/Patient', headers={'authorization': 'Bearer ' + anna_token.token}
588-
)
581+
# 20251120 This test is now gated on a variable; if the variable does not exist, or
582+
# is not set, the test will run. This is the desired behavior.
583+
if os.getenv("RUNNING_IN_LOCAL_STACK", None) != "true":
584+
with self.assertRaisesRegexp(
585+
OSError, 'Could not find the TLS certificate file'
586+
):
587+
response = self.client.get(
588+
'/v1/fhir/Patient', headers={'authorization': 'Bearer ' + anna_token.token}
589+
)
589590

590591
bob_tkn = self._create_test_token(bob, bob_application)
591592
self.assertTrue(
@@ -638,24 +639,26 @@ def test_delete_token_success(self):
638639

639640
# Post Django 2.2: An OSError exception is expected when trying to reach the
640641
# backend FHIR server and proves authentication worked.
641-
with self.assertRaisesRegexp(
642-
OSError, 'Could not find the TLS certificate file'
643-
):
644-
response = self.client.get(
645-
'/v1/fhir/Patient', headers={'authorization': 'Bearer ' + bob_tkn.token}
646-
)
642+
if os.getenv("RUNNING_IN_LOCAL_STACK", None) != "true":
643+
with self.assertRaisesRegexp(
644+
OSError, 'Could not find the TLS certificate file'
645+
):
646+
response = self.client.get(
647+
'/v1/fhir/Patient', headers={'authorization': 'Bearer ' + bob_tkn.token}
648+
)
647649

648650
next_tkn = self._create_test_token(anna, anna_application)
649651

650652
# Post Django 2.2: An OSError exception is expected when trying to reach the
651653
# backend FHIR server and proves authentication worked.
652-
with self.assertRaisesRegexp(
653-
OSError, 'Could not find the TLS certificate file'
654-
):
655-
response = self.client.get(
656-
'/v1/fhir/Patient',
657-
headers={'authorization': 'Bearer ' + next_tkn.token},
658-
)
654+
if os.getenv("RUNNING_IN_LOCAL_STACK", None) != "true":
655+
with self.assertRaisesRegexp(
656+
OSError, 'Could not find the TLS certificate file'
657+
):
658+
response = self.client.get(
659+
'/v1/fhir/Patient',
660+
headers={'authorization': 'Bearer ' + next_tkn.token},
661+
)
659662

660663
# self.assertEqual(next_tkn.token, tkn.token)
661664
self.assertTrue(

apps/fhir/bluebutton/tests/test_wellknown_endpoints.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,10 @@ def test_smart_configuration_missing_fields_in_v3(self):
147147
# is commented above for reference.
148148

149149
@skipIf((not settings.RUN_ONLINE_TESTS), "Can't reach external sites.")
150-
# This overrides the switch and sets it to true, always.
151-
# We should only run the test if we have v3 enabled.
152150
@override_switch('v3_endpoints', active=True)
153151
def test_fhir_metadata_extensions_have_v3(self):
154-
response = self.client.get(f'{BASEURL}/v3/fhir/metadata')
152+
the_url = f'{BASEURL}/v3/fhir/metadata'
153+
response = self.client.get(the_url)
155154
self.assertEqual(response.status_code, 200)
156155
json = response.json()
157156
self.assertIn('v3', json['implementation']['url'])

apps/testclient/management/commands/create_test_user_and_application.py

Lines changed: 93 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@
1212
from datetime import timedelta, datetime
1313
from django.conf import settings
1414
from apps.authorization.models import update_grants
15+
from apps.authorization.models import ArchivedDataAccessGrant, DataAccessGrant
1516

17+
# Imports for quieting things during startup.
18+
from waffle.models import Switch
19+
20+
from uuid import uuid4
1621

17-
def create_group(name="BlueButton"):
1822

23+
def create_group(name="BlueButton"):
1924
g, created = Group.objects.get_or_create(name=name)
2025
if created:
2126
print("%s group created" % (name))
@@ -24,42 +29,29 @@ def create_group(name="BlueButton"):
2429
return g
2530

2631

27-
def create_user(group, usr):
28-
u_name = "fred"
29-
first_name = "Fred"
30-
last_name = "Flinstone"
31-
32-
password = "foobarfoobarfoobar"
32+
def create_user(the_group):
33+
username = "rogersf"
34+
first_name = "Fred"
35+
last_name = "Rogers"
36+
37+
password = uuid4()
3338
user_type = "BEN"
34-
35-
if usr is not None:
36-
u_name = usr
37-
first_name = "{}{}".format(usr, "First")
38-
last_name = "{}{}".format(usr, "Last")
39-
email = "{}.{}@example.com".format(first_name, last_name)
40-
user_type = "DEV"
41-
42-
43-
if User.objects.filter(username=u_name).exists():
44-
User.objects.filter(username=u_name).delete()
45-
46-
u = None
47-
48-
if usr is not None:
49-
u = User.objects.create_user(username=u_name,
50-
first_name=first_name,
51-
last_name=last_name,
52-
email=email)
53-
u.set_unusable_password()
54-
else:
55-
# create a sample user 'fred' for dev local that has a usable password
56-
u = User.objects.create_user(username=u_name,
57-
first_name=first_name,
58-
last_name=last_name,
59-
email=email,
60-
password=password,)
61-
62-
UserProfile.objects.create(user=u,
39+
40+
# We will do this over-and-over.
41+
# If we don't already exist, then create the user.
42+
if User.objects.filter(username=username).exists():
43+
print(f"👟 {username} already exists. Skipping test user creation.")
44+
return User.objects.get(username=username)
45+
46+
# If the user didn't exist, it is our first time through.
47+
# Create the user.
48+
user_obj = User.objects.create(username=username,
49+
first_name=first_name,
50+
last_name=last_name,
51+
email=email,
52+
password=password,)
53+
user_obj.set_unusable_password()
54+
UserProfile.objects.create(user=user_obj,
6355
user_type=user_type,
6456
create_applications=True,
6557
password_reset_question_1='1',
@@ -68,33 +60,35 @@ def create_user(group, usr):
6860
password_reset_answer_2='Frank',
6961
password_reset_question_3='3',
7062
password_reset_answer_3='Bentley')
63+
user_obj.groups.add(the_group)
7164

72-
u.groups.add(group)
65+
# CROSSWALK
66+
# Removing any existing crosswalks for this artificial user.
67+
# Why? Just in case.
68+
user_id_hash = "ee78989d1d9ba0b98f3cfbd52479f10c7631679c17563186f70fbef038cc9536"
69+
Crosswalk.objects.filter(_user_id_hash=user_id_hash).delete()
70+
Crosswalk.objects.get_or_create(user=user_obj,
71+
fhir_id_v2=settings.DEFAULT_SAMPLE_FHIR_ID_V2,
72+
_user_id_hash=user_id_hash)
73+
return user_obj
7374

74-
if usr is None:
75-
c, g_o_c = Crosswalk.objects.get_or_create(user=u,
76-
fhir_id_v2=settings.DEFAULT_SAMPLE_FHIR_ID_V2,
77-
_user_id_hash="ee78989d1d9ba0b98f3cfbd52479f10c7631679c17563186f70fbef038cc9536")
78-
return u
7975

76+
def create_application(user):
77+
app_name = "TestApp"
78+
if Application.objects.filter(name=app_name).exists():
79+
return Application.objects.get(name=app_name)
80+
81+
# If the app doesn't exist, create the test app.
8082

81-
def create_application(user, group, app, redirect):
82-
app_name = "TestApp" if app is None else app
8383
Application.objects.filter(name=app_name).delete()
8484
redirect_uri = "{}{}".format(settings.HOSTNAME_URL, settings.TESTCLIENT_REDIRECT_URI)
8585

86-
if redirect:
87-
redirect_uri = redirect
88-
89-
if not(redirect_uri.startswith("http://") or redirect_uri.startswith("https://")):
90-
redirect_uri = "https://" + redirect_uri
91-
92-
a = Application.objects.create(name=app_name,
93-
redirect_uris=redirect_uri,
94-
user=user,
95-
data_access_type="THIRTEEN_MONTH",
96-
client_type="confidential",
97-
authorization_grant_type="authorization-code")
86+
the_app = Application.objects.create(name=app_name,
87+
redirect_uris=redirect_uri,
88+
user=user,
89+
data_access_type="THIRTEEN_MONTH",
90+
client_type="confidential",
91+
authorization_grant_type="authorization-code",)
9892

9993
titles = ["My Medicare and supplemental coverage information.",
10094
"My Medicare claim information.",
@@ -104,49 +98,64 @@ def create_application(user, group, app, redirect):
10498

10599
for t in titles:
106100
c = ProtectedCapability.objects.get(title=t)
107-
a.scope.add(c)
108-
return a
101+
the_app.scope.add(c)
102+
103+
return the_app
109104

110105

111-
def create_test_token(user, application):
106+
def create_test_token(the_user, the_app):
112107

108+
# Set expiration one day from now.
113109
now = timezone.now()
114110
expires = now + timedelta(days=1)
115111

116-
scopes = application.scope.all()
112+
scopes = the_app.scope.all()
117113
scope = []
118114
for s in scopes:
119115
scope.append(s.slug)
120116

121-
t = AccessToken.objects.create(user=user, application=application,
117+
# We have to have a tokent with token="sample-token-string", because we
118+
# rely on it existing for unit tests. Which are actually integration tests.
119+
if AccessToken.objects.filter(token="sample-token-string").exists():
120+
t = AccessToken.objects.get(token="sample-token-string")
121+
t.expires = expires
122+
t.save()
123+
else:
124+
AccessToken.objects.create(user=the_user,
125+
application=the_app,
126+
# This needs to be "sample-token-string", because
127+
# we have tests that rely on it.
122128
token="sample-token-string",
123129
expires=expires,
124-
scope=' '.join(scope))
125-
return t
130+
scope=' '.join(scope),)
131+
132+
133+
def get_switch(name):
134+
try:
135+
sw = Switch.objects.get(name=name)
136+
return sw.active
137+
except Exception as e:
138+
print(f"Could not get switch {name}: {e}")
139+
140+
141+
def set_switch(name, b):
142+
sw, _ = Switch.objects.get_or_create(name=name)
143+
sw.active = b
144+
sw.save()
126145

127146

128147
class Command(BaseCommand):
129148
help = 'Create a test user and application for the test client'
130149

131-
def add_arguments(self, parser):
132-
parser.add_argument("-u", "--user", help="Name of the user to be created (unique).")
133-
parser.add_argument("-a", "--app", help="Name of the application to be created (unique).")
134-
parser.add_argument("-r", "--redirect", help="Redirect url of the application.")
135-
136150
def handle(self, *args, **options):
137-
usr = options["user"]
138-
app = options["app"]
139-
redirect = options["redirect"]
140-
141-
g = create_group()
142-
u = create_user(g, usr)
143-
a = create_application(u, g, app, redirect)
144-
t = None
145-
if usr is None and app is None:
146-
t = create_test_token(u, a)
147-
update_grants()
148-
print("Name:", a.name)
149-
print("client_id:", a.client_id)
150-
print("client_secret:", a.client_secret)
151-
print("access_token:", t.token if t else "None")
152-
print("redirect_uri:", a.redirect_uris)
151+
152+
set_switch('outreach_email', False)
153+
154+
the_group = create_group()
155+
the_user = create_user(the_group)
156+
the_app = create_application(the_user)
157+
create_test_token(the_user, the_app)
158+
update_grants()
159+
160+
# Restore switch to whatever it was.
161+
set_switch('outreach_email', True)

0 commit comments

Comments
 (0)