Skip to content

Commit 93e14c5

Browse files
committed
Tests for emulator support in auth, user mgmt and token gen
To minimize modification of tests, the app fixture and instrumentation have been modified to use a global dict of URLs, which are then monkey-patched based on fixture parameters. Essentially, all tests using the app fixture are run twice, once with the emulated endpoint and once without.
1 parent 80e4d07 commit 93e14c5

File tree

3 files changed

+91
-43
lines changed

3 files changed

+91
-43
lines changed

tests/test_auth_providers.py

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,18 @@
2121
import firebase_admin
2222
from firebase_admin import auth
2323
from firebase_admin import exceptions
24-
from firebase_admin import _auth_providers
2524
from tests import testutils
2625

27-
USER_MGT_URL_PREFIX = 'https://identitytoolkit.googleapis.com/v2beta1/projects/mock-project-id'
26+
ID_TOOLKIT_URL = 'https://identitytoolkit.googleapis.com/v2beta1'
27+
EMULATOR_HOST_ENV_VAR = 'FIREBASE_AUTH_EMULATOR_HOST'
28+
AUTH_EMULATOR_HOST = 'localhost:9099'
29+
EMULATED_ID_TOOLKIT_URL = 'http://{}/identitytoolkit.googleapis.com/v2beta1'.format(
30+
AUTH_EMULATOR_HOST)
31+
URL_PROJECT_SUFFIX = '/projects/mock-project-id'
32+
USER_MGT_URLS = {
33+
'ID_TOOLKIT': ID_TOOLKIT_URL,
34+
'PREFIX': ID_TOOLKIT_URL + URL_PROJECT_SUFFIX,
35+
}
2836
OIDC_PROVIDER_CONFIG_RESPONSE = testutils.resource('oidc_provider_config.json')
2937
SAML_PROVIDER_CONFIG_RESPONSE = testutils.resource('saml_provider_config.json')
3038
LIST_OIDC_PROVIDER_CONFIGS_RESPONSE = testutils.resource('list_oidc_provider_configs.json')
@@ -39,20 +47,26 @@
3947
INVALID_PROVIDER_IDS = [None, True, False, 1, 0, list(), tuple(), dict(), '']
4048

4149

42-
@pytest.fixture(scope='module')
43-
def user_mgt_app():
50+
@pytest.fixture(scope='module', params=[{'emulated': False}, {'emulated': True}])
51+
def user_mgt_app(request):
52+
monkeypatch = pytest.MonkeyPatch()
53+
if request.param['emulated']:
54+
monkeypatch.setenv(EMULATOR_HOST_ENV_VAR, AUTH_EMULATOR_HOST)
55+
monkeypatch.setitem(USER_MGT_URLS, 'ID_TOOLKIT', EMULATED_ID_TOOLKIT_URL)
56+
monkeypatch.setitem(USER_MGT_URLS, 'PREFIX', EMULATED_ID_TOOLKIT_URL + URL_PROJECT_SUFFIX)
4457
app = firebase_admin.initialize_app(testutils.MockCredential(), name='providerConfig',
4558
options={'projectId': 'mock-project-id'})
4659
yield app
4760
firebase_admin.delete_app(app)
61+
monkeypatch.undo()
4862

4963

5064
def _instrument_provider_mgt(app, status, payload):
5165
client = auth._get_client(app)
5266
provider_manager = client._provider_manager
5367
recorder = []
5468
provider_manager.http_client.session.mount(
55-
_auth_providers.ProviderConfigClient.PROVIDER_CONFIG_URL,
69+
USER_MGT_URLS['ID_TOOLKIT'],
5670
testutils.MockAdapter(payload, status, recorder))
5771
return recorder
5872

@@ -90,7 +104,7 @@ def test_get(self, user_mgt_app):
90104
assert len(recorder) == 1
91105
req = recorder[0]
92106
assert req.method == 'GET'
93-
assert req.url == '{0}{1}'.format(USER_MGT_URL_PREFIX, '/oauthIdpConfigs/oidc.provider')
107+
assert req.url == '{0}{1}'.format(USER_MGT_URLS['PREFIX'], '/oauthIdpConfigs/oidc.provider')
94108

95109
@pytest.mark.parametrize('invalid_opts', [
96110
{'provider_id': None}, {'provider_id': ''}, {'provider_id': 'saml.provider'},
@@ -116,7 +130,7 @@ def test_create(self, user_mgt_app):
116130
req = recorder[0]
117131
assert req.method == 'POST'
118132
assert req.url == '{0}/oauthIdpConfigs?oauthIdpConfigId=oidc.provider'.format(
119-
USER_MGT_URL_PREFIX)
133+
USER_MGT_URLS['PREFIX'])
120134
got = json.loads(req.body.decode())
121135
assert got == self.OIDC_CONFIG_REQUEST
122136

@@ -136,7 +150,7 @@ def test_create_minimal(self, user_mgt_app):
136150
req = recorder[0]
137151
assert req.method == 'POST'
138152
assert req.url == '{0}/oauthIdpConfigs?oauthIdpConfigId=oidc.provider'.format(
139-
USER_MGT_URL_PREFIX)
153+
USER_MGT_URLS['PREFIX'])
140154
got = json.loads(req.body.decode())
141155
assert got == want
142156

@@ -156,7 +170,7 @@ def test_create_empty_values(self, user_mgt_app):
156170
req = recorder[0]
157171
assert req.method == 'POST'
158172
assert req.url == '{0}/oauthIdpConfigs?oauthIdpConfigId=oidc.provider'.format(
159-
USER_MGT_URL_PREFIX)
173+
USER_MGT_URLS['PREFIX'])
160174
got = json.loads(req.body.decode())
161175
assert got == want
162176

@@ -186,7 +200,7 @@ def test_update(self, user_mgt_app):
186200
assert req.method == 'PATCH'
187201
mask = ['clientId', 'displayName', 'enabled', 'issuer']
188202
assert req.url == '{0}/oauthIdpConfigs/oidc.provider?updateMask={1}'.format(
189-
USER_MGT_URL_PREFIX, ','.join(mask))
203+
USER_MGT_URLS['PREFIX'], ','.join(mask))
190204
got = json.loads(req.body.decode())
191205
assert got == self.OIDC_CONFIG_REQUEST
192206

@@ -201,7 +215,7 @@ def test_update_minimal(self, user_mgt_app):
201215
req = recorder[0]
202216
assert req.method == 'PATCH'
203217
assert req.url == '{0}/oauthIdpConfigs/oidc.provider?updateMask=displayName'.format(
204-
USER_MGT_URL_PREFIX)
218+
USER_MGT_URLS['PREFIX'])
205219
got = json.loads(req.body.decode())
206220
assert got == {'displayName': 'oidcProviderName'}
207221

@@ -217,7 +231,7 @@ def test_update_empty_values(self, user_mgt_app):
217231
assert req.method == 'PATCH'
218232
mask = ['displayName', 'enabled']
219233
assert req.url == '{0}/oauthIdpConfigs/oidc.provider?updateMask={1}'.format(
220-
USER_MGT_URL_PREFIX, ','.join(mask))
234+
USER_MGT_URLS['PREFIX'], ','.join(mask))
221235
got = json.loads(req.body.decode())
222236
assert got == {'displayName': None, 'enabled': False}
223237

@@ -236,7 +250,7 @@ def test_delete(self, user_mgt_app):
236250
assert len(recorder) == 1
237251
req = recorder[0]
238252
assert req.method == 'DELETE'
239-
assert req.url == '{0}{1}'.format(USER_MGT_URL_PREFIX, '/oauthIdpConfigs/oidc.provider')
253+
assert req.url == '{0}{1}'.format(USER_MGT_URLS['PREFIX'], '/oauthIdpConfigs/oidc.provider')
240254

241255
@pytest.mark.parametrize('arg', [None, 'foo', list(), dict(), 0, -1, 101, False])
242256
def test_invalid_max_results(self, user_mgt_app, arg):
@@ -259,7 +273,7 @@ def test_list_single_page(self, user_mgt_app):
259273
assert len(recorder) == 1
260274
req = recorder[0]
261275
assert req.method == 'GET'
262-
assert req.url == '{0}{1}'.format(USER_MGT_URL_PREFIX, '/oauthIdpConfigs?pageSize=100')
276+
assert req.url == '{0}{1}'.format(USER_MGT_URLS['PREFIX'], '/oauthIdpConfigs?pageSize=100')
263277

264278
def test_list_multiple_pages(self, user_mgt_app):
265279
sample_response = json.loads(OIDC_PROVIDER_CONFIG_RESPONSE)
@@ -277,7 +291,7 @@ def test_list_multiple_pages(self, user_mgt_app):
277291
assert len(recorder) == 1
278292
req = recorder[0]
279293
assert req.method == 'GET'
280-
assert req.url == '{0}/oauthIdpConfigs?pageSize=10'.format(USER_MGT_URL_PREFIX)
294+
assert req.url == '{0}/oauthIdpConfigs?pageSize=10'.format(USER_MGT_URLS['PREFIX'])
281295

282296
# Page 2 (also the last page)
283297
response = {'oauthIdpConfigs': configs[2:]}
@@ -289,7 +303,7 @@ def test_list_multiple_pages(self, user_mgt_app):
289303
req = recorder[0]
290304
assert req.method == 'GET'
291305
assert req.url == '{0}/oauthIdpConfigs?pageSize=10&pageToken=token'.format(
292-
USER_MGT_URL_PREFIX)
306+
USER_MGT_URLS['PREFIX'])
293307

294308
def test_paged_iteration(self, user_mgt_app):
295309
sample_response = json.loads(OIDC_PROVIDER_CONFIG_RESPONSE)
@@ -310,7 +324,7 @@ def test_paged_iteration(self, user_mgt_app):
310324
assert len(recorder) == 1
311325
req = recorder[0]
312326
assert req.method == 'GET'
313-
assert req.url == '{0}/oauthIdpConfigs?pageSize=100'.format(USER_MGT_URL_PREFIX)
327+
assert req.url == '{0}/oauthIdpConfigs?pageSize=100'.format(USER_MGT_URLS['PREFIX'])
314328

315329
# Page 2 (also the last page)
316330
response = {'oauthIdpConfigs': configs[2:]}
@@ -322,7 +336,7 @@ def test_paged_iteration(self, user_mgt_app):
322336
req = recorder[0]
323337
assert req.method == 'GET'
324338
assert req.url == '{0}/oauthIdpConfigs?pageSize=100&pageToken=token'.format(
325-
USER_MGT_URL_PREFIX)
339+
USER_MGT_URLS['PREFIX'])
326340

327341
with pytest.raises(StopIteration):
328342
next(iterator)
@@ -421,7 +435,8 @@ def test_get(self, user_mgt_app):
421435
assert len(recorder) == 1
422436
req = recorder[0]
423437
assert req.method == 'GET'
424-
assert req.url == '{0}{1}'.format(USER_MGT_URL_PREFIX, '/inboundSamlConfigs/saml.provider')
438+
assert req.url == '{0}{1}'.format(USER_MGT_URLS['PREFIX'],
439+
'/inboundSamlConfigs/saml.provider')
425440

426441
@pytest.mark.parametrize('invalid_opts', [
427442
{'provider_id': None}, {'provider_id': ''}, {'provider_id': 'oidc.provider'},
@@ -451,7 +466,7 @@ def test_create(self, user_mgt_app):
451466
req = recorder[0]
452467
assert req.method == 'POST'
453468
assert req.url == '{0}/inboundSamlConfigs?inboundSamlConfigId=saml.provider'.format(
454-
USER_MGT_URL_PREFIX)
469+
USER_MGT_URLS['PREFIX'])
455470
got = json.loads(req.body.decode())
456471
assert got == self.SAML_CONFIG_REQUEST
457472

@@ -471,7 +486,7 @@ def test_create_minimal(self, user_mgt_app):
471486
req = recorder[0]
472487
assert req.method == 'POST'
473488
assert req.url == '{0}/inboundSamlConfigs?inboundSamlConfigId=saml.provider'.format(
474-
USER_MGT_URL_PREFIX)
489+
USER_MGT_URLS['PREFIX'])
475490
got = json.loads(req.body.decode())
476491
assert got == want
477492

@@ -491,7 +506,7 @@ def test_create_empty_values(self, user_mgt_app):
491506
req = recorder[0]
492507
assert req.method == 'POST'
493508
assert req.url == '{0}/inboundSamlConfigs?inboundSamlConfigId=saml.provider'.format(
494-
USER_MGT_URL_PREFIX)
509+
USER_MGT_URLS['PREFIX'])
495510
got = json.loads(req.body.decode())
496511
assert got == want
497512

@@ -528,7 +543,7 @@ def test_update(self, user_mgt_app):
528543
'idpConfig.ssoUrl', 'spConfig.callbackUri', 'spConfig.spEntityId',
529544
]
530545
assert req.url == '{0}/inboundSamlConfigs/saml.provider?updateMask={1}'.format(
531-
USER_MGT_URL_PREFIX, ','.join(mask))
546+
USER_MGT_URLS['PREFIX'], ','.join(mask))
532547
got = json.loads(req.body.decode())
533548
assert got == self.SAML_CONFIG_REQUEST
534549

@@ -543,7 +558,7 @@ def test_update_minimal(self, user_mgt_app):
543558
req = recorder[0]
544559
assert req.method == 'PATCH'
545560
assert req.url == '{0}/inboundSamlConfigs/saml.provider?updateMask=displayName'.format(
546-
USER_MGT_URL_PREFIX)
561+
USER_MGT_URLS['PREFIX'])
547562
got = json.loads(req.body.decode())
548563
assert got == {'displayName': 'samlProviderName'}
549564

@@ -559,7 +574,7 @@ def test_update_empty_values(self, user_mgt_app):
559574
assert req.method == 'PATCH'
560575
mask = ['displayName', 'enabled']
561576
assert req.url == '{0}/inboundSamlConfigs/saml.provider?updateMask={1}'.format(
562-
USER_MGT_URL_PREFIX, ','.join(mask))
577+
USER_MGT_URLS['PREFIX'], ','.join(mask))
563578
got = json.loads(req.body.decode())
564579
assert got == {'displayName': None, 'enabled': False}
565580

@@ -578,7 +593,8 @@ def test_delete(self, user_mgt_app):
578593
assert len(recorder) == 1
579594
req = recorder[0]
580595
assert req.method == 'DELETE'
581-
assert req.url == '{0}{1}'.format(USER_MGT_URL_PREFIX, '/inboundSamlConfigs/saml.provider')
596+
assert req.url == '{0}{1}'.format(USER_MGT_URLS['PREFIX'],
597+
'/inboundSamlConfigs/saml.provider')
582598

583599
def test_config_not_found(self, user_mgt_app):
584600
_instrument_provider_mgt(user_mgt_app, 500, CONFIG_NOT_FOUND_RESPONSE)
@@ -613,7 +629,8 @@ def test_list_single_page(self, user_mgt_app):
613629
assert len(recorder) == 1
614630
req = recorder[0]
615631
assert req.method == 'GET'
616-
assert req.url == '{0}{1}'.format(USER_MGT_URL_PREFIX, '/inboundSamlConfigs?pageSize=100')
632+
assert req.url == '{0}{1}'.format(USER_MGT_URLS['PREFIX'],
633+
'/inboundSamlConfigs?pageSize=100')
617634

618635
def test_list_multiple_pages(self, user_mgt_app):
619636
sample_response = json.loads(SAML_PROVIDER_CONFIG_RESPONSE)
@@ -631,7 +648,7 @@ def test_list_multiple_pages(self, user_mgt_app):
631648
assert len(recorder) == 1
632649
req = recorder[0]
633650
assert req.method == 'GET'
634-
assert req.url == '{0}/inboundSamlConfigs?pageSize=10'.format(USER_MGT_URL_PREFIX)
651+
assert req.url == '{0}/inboundSamlConfigs?pageSize=10'.format(USER_MGT_URLS['PREFIX'])
635652

636653
# Page 2 (also the last page)
637654
response = {'inboundSamlConfigs': configs[2:]}
@@ -643,7 +660,7 @@ def test_list_multiple_pages(self, user_mgt_app):
643660
req = recorder[0]
644661
assert req.method == 'GET'
645662
assert req.url == '{0}/inboundSamlConfigs?pageSize=10&pageToken=token'.format(
646-
USER_MGT_URL_PREFIX)
663+
USER_MGT_URLS['PREFIX'])
647664

648665
def test_paged_iteration(self, user_mgt_app):
649666
sample_response = json.loads(SAML_PROVIDER_CONFIG_RESPONSE)
@@ -664,7 +681,7 @@ def test_paged_iteration(self, user_mgt_app):
664681
assert len(recorder) == 1
665682
req = recorder[0]
666683
assert req.method == 'GET'
667-
assert req.url == '{0}/inboundSamlConfigs?pageSize=100'.format(USER_MGT_URL_PREFIX)
684+
assert req.url == '{0}/inboundSamlConfigs?pageSize=100'.format(USER_MGT_URLS['PREFIX'])
668685

669686
# Page 2 (also the last page)
670687
response = {'inboundSamlConfigs': configs[2:]}
@@ -676,7 +693,7 @@ def test_paged_iteration(self, user_mgt_app):
676693
req = recorder[0]
677694
assert req.method == 'GET'
678695
assert req.url == '{0}/inboundSamlConfigs?pageSize=100&pageToken=token'.format(
679-
USER_MGT_URL_PREFIX)
696+
USER_MGT_URLS['PREFIX'])
680697

681698
with pytest.raises(StopIteration):
682699
next(iterator)

tests/test_token_gen.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@
5555
'NonEmptyDictToken': {'a': 1},
5656
}
5757

58+
ID_TOOLKIT_URL = 'https://identitytoolkit.googleapis.com/v1'
59+
EMULATOR_HOST_ENV_VAR = 'FIREBASE_AUTH_EMULATOR_HOST'
60+
AUTH_EMULATOR_HOST = 'localhost:9099'
61+
EMULATED_ID_TOOLKIT_URL = 'http://{}/identitytoolkit.googleapis.com/v1'.format(AUTH_EMULATOR_HOST)
62+
TOKEN_MGT_URLS = {
63+
'ID_TOOLKIT': ID_TOOLKIT_URL,
64+
}
65+
5866
# Fixture for mocking a HTTP server
5967
httpserver = plugin.httpserver
6068

@@ -121,7 +129,7 @@ def _instrument_user_manager(app, status, payload):
121129
user_manager = client._user_manager
122130
recorder = []
123131
user_manager.http_client.session.mount(
124-
_token_gen.TokenGenerator.ID_TOOLKIT_URL,
132+
TOKEN_MGT_URLS['ID_TOOLKIT'],
125133
testutils.MockAdapter(payload, status, recorder))
126134
return user_manager, recorder
127135

@@ -133,23 +141,33 @@ def _overwrite_iam_request(app, request):
133141
client = auth._get_client(app)
134142
client._token_generator.request = request
135143

136-
@pytest.fixture(scope='module')
137-
def auth_app():
144+
@pytest.fixture(scope='module', params=[{'emulated': False}, {'emulated': True}])
145+
def auth_app(request):
138146
"""Returns an App initialized with a mock service account credential.
139147
140148
This can be used in any scenario where the private key is required. Use user_mgt_app
141149
for everything else.
142150
"""
151+
monkeypatch = pytest.MonkeyPatch()
152+
if request.param['emulated']:
153+
monkeypatch.setenv(EMULATOR_HOST_ENV_VAR, AUTH_EMULATOR_HOST)
154+
monkeypatch.setitem(TOKEN_MGT_URLS, 'ID_TOOLKIT', EMULATED_ID_TOOLKIT_URL)
143155
app = firebase_admin.initialize_app(MOCK_CREDENTIAL, name='tokenGen')
144156
yield app
145157
firebase_admin.delete_app(app)
146-
147-
@pytest.fixture(scope='module')
148-
def user_mgt_app():
158+
monkeypatch.undo()
159+
160+
@pytest.fixture(scope='module', params=[{'emulated': False}, {'emulated': True}])
161+
def user_mgt_app(request):
162+
monkeypatch = pytest.MonkeyPatch()
163+
if request.param['emulated']:
164+
monkeypatch.setenv(EMULATOR_HOST_ENV_VAR, AUTH_EMULATOR_HOST)
165+
monkeypatch.setitem(TOKEN_MGT_URLS, 'ID_TOOLKIT', EMULATED_ID_TOOLKIT_URL)
149166
app = firebase_admin.initialize_app(testutils.MockCredential(), name='userMgt',
150167
options={'projectId': 'mock-project-id'})
151168
yield app
152169
firebase_admin.delete_app(app)
170+
monkeypatch.undo()
153171

154172
@pytest.fixture
155173
def env_var_app(request):

0 commit comments

Comments
 (0)