From 353aa0d339a4ee3ad84e5a893dce7a1f5bf53aa9 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 18 Dec 2017 13:18:02 -0500 Subject: [PATCH 01/29] auto init --- firebase_admin/__init__.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index 10a3b5084..e0056fe7a 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -31,7 +31,7 @@ _clock = datetime.datetime.utcnow _DEFAULT_APP_NAME = '[DEFAULT]' - +_DEFAULT_CONFIG_FILE_ENV = 'FIREBASE_CONFIG' def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): """Initializes and returns a new App instance. @@ -150,6 +150,20 @@ def __init__(self, options): raise ValueError('Illegal Firebase app options type: {0}. Options ' 'must be a dictionary.'.format(type(options))) self._options = options + config_file = os.getenv(_DEFAULT_CONFIG_FILE_ENV) + if config_file is not None: + if self._options.get('databaseURL') is None or + self._options.get('projectId') is None or + self._options.get('storageBucket') is None: + with open(config) as json_file: + json_data = json.load(json_file) + if self._options.get('databaseURL') is None: + self._options['databaseURL'] = json_data.get('databaseURL') + if self._options.get('projectId') is None: + self._options['projectId'] = json_data.get('projectId') + if self._options.get('storageBucket') is None: + self._options['storageBucket'] = json_data.get('storageBucket') + def get(self, key, default=None): """Returns the option identified by the provided key.""" From 2c6f3ad6a0d6848d9bea14a54bbdd120f422756c Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Tue, 19 Dec 2017 15:43:35 -0500 Subject: [PATCH 02/29] pretty the code --- firebase_admin/__init__.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index e0056fe7a..9017284f2 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -46,8 +46,8 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): credential: A credential object used to initialize the SDK (optional). If none is provided, Google Application Default Credentials are used. options: A dictionary of configuration options (optional). Supported options include - ``databaseURL``, ``storageBucket`` and ``httpTimeout``. If ``httpTimeout`` is not set, - HTTP connections initiated by client modules such as ``db`` will not time out. + ``databaseURL``, ``storageBucket`` , ``projectId`` and ``httpTimeout``. If ``httpTimeout`` + is not set, HTTP connections initiated by client modules such as ``db`` will not time out. name: Name of the app (optional). Returns: @@ -156,15 +156,13 @@ def __init__(self, options): self._options.get('projectId') is None or self._options.get('storageBucket') is None: with open(config) as json_file: - json_data = json.load(json_file) - if self._options.get('databaseURL') is None: - self._options['databaseURL'] = json_data.get('databaseURL') - if self._options.get('projectId') is None: - self._options['projectId'] = json_data.get('projectId') - if self._options.get('storageBucket') is None: - self._options['storageBucket'] = json_data.get('storageBucket') - - + try: + json_data = json.load(json_file) + except: + raise ValueError('JSON string in {0} is not valid json.'.format(json_file)) + for conf_field in ['databaseURL', 'projectId','storageBucket']: + if self._options.get(conf_field) is None: + self._options[conf_field] = json_data.get(conf_field def get(self, key, default=None): """Returns the option identified by the provided key.""" return self._options.get(key, default) From f06d5e1b87610bc0c65c4ec515476d906eba8795 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Wed, 20 Dec 2017 13:33:28 -0500 Subject: [PATCH 03/29] initial --- firebase_admin/__init__.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index 9017284f2..d8b26ad44 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -47,9 +47,8 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): Google Application Default Credentials are used. options: A dictionary of configuration options (optional). Supported options include ``databaseURL``, ``storageBucket`` , ``projectId`` and ``httpTimeout``. If ``httpTimeout`` - is not set, HTTP connections initiated by client modules such as ``db`` will not time out. - name: Name of the app (optional). - + is not set, HTTP connections initiated by client modules such as ``db`` will not time out. + name: Name of the app (optional). Returns: App: A newly initialized instance of App. @@ -152,17 +151,17 @@ def __init__(self, options): self._options = options config_file = os.getenv(_DEFAULT_CONFIG_FILE_ENV) if config_file is not None: - if self._options.get('databaseURL') is None or - self._options.get('projectId') is None or - self._options.get('storageBucket') is None: - with open(config) as json_file: + if (self._options.get('databaseURL') is None or + self._options.get('projectId') is None or + self._options.get('storageBucket') is None): + with open(config_file, 'r') as json_file: try: json_data = json.load(json_file) except: raise ValueError('JSON string in {0} is not valid json.'.format(json_file)) for conf_field in ['databaseURL', 'projectId','storageBucket']: if self._options.get(conf_field) is None: - self._options[conf_field] = json_data.get(conf_field + self._options[conf_field] = json_data.get(conf_field) def get(self, key, default=None): """Returns the option identified by the provided key.""" return self._options.get(key, default) From f05ee2751052a5383ac376655111fad24f52f2f8 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 21 Dec 2017 10:45:51 -0500 Subject: [PATCH 04/29] start --- firebase_admin/__init__.py | 34 +++++++++++++++++++--------------- tests/test_app.py | 22 ++++++++++++++++++++-- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index d8b26ad44..00f20a342 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -14,6 +14,7 @@ """Firebase Admin SDK for Python.""" import datetime +import json import os import threading @@ -31,7 +32,8 @@ _clock = datetime.datetime.utcnow _DEFAULT_APP_NAME = '[DEFAULT]' -_DEFAULT_CONFIG_FILE_ENV = 'FIREBASE_CONFIG' +_CONFIG_FILE_ENV = 'FIREBASE_CONFIG' +_FIREBASE_CONFIG_KEYS = ['databaseURL', 'projectId', 'storageBucket'] def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): """Initializes and returns a new App instance. @@ -47,8 +49,9 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): Google Application Default Credentials are used. options: A dictionary of configuration options (optional). Supported options include ``databaseURL``, ``storageBucket`` , ``projectId`` and ``httpTimeout``. If ``httpTimeout`` - is not set, HTTP connections initiated by client modules such as ``db`` will not time out. + is not set, HTTP connections initiated by client modules such as ``db`` will not time out. name: Name of the app (optional). + name: The app name Returns: App: A newly initialized instance of App. @@ -149,19 +152,20 @@ def __init__(self, options): raise ValueError('Illegal Firebase app options type: {0}. Options ' 'must be a dictionary.'.format(type(options))) self._options = options - config_file = os.getenv(_DEFAULT_CONFIG_FILE_ENV) - if config_file is not None: - if (self._options.get('databaseURL') is None or - self._options.get('projectId') is None or - self._options.get('storageBucket') is None): - with open(config_file, 'r') as json_file: - try: - json_data = json.load(json_file) - except: - raise ValueError('JSON string in {0} is not valid json.'.format(json_file)) - for conf_field in ['databaseURL', 'projectId','storageBucket']: - if self._options.get(conf_field) is None: - self._options[conf_field] = json_data.get(conf_field) + config_file = os.getenv(_CONFIG_FILE_ENV) + if config_file is None: + return + if all(self._options.get(field) is not None for field in _FIREBASE_CONFIG_KEYS): + return + with open(config_file, 'r') as json_file: + try: + json_data = json.load(json_file) + except: + raise ValueError('JSON string in {0} is not valid json.'.format(json_file)) + if any(field not in _FIREBASE_CONFIG_KEYS for field in json_data): + raise ValueError('Bad config key in file {}.'.format(config_file)) + json_data.update(self._options) + self._options = json_data def get(self, key, default=None): """Returns the option identified by the provided key.""" return self._options.get(key, default) diff --git a/tests/test_app.py b/tests/test_app.py index 8fd586f9d..f07ab91f0 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -22,10 +22,10 @@ from firebase_admin import _utils from tests import testutils - CREDENTIAL = credentials.Certificate( testutils.resource_filename('service_account.json')) GCLOUD_PROJECT = 'GCLOUD_PROJECT' +CONFIG_FILE = firebase_admin._CONFIG_FILE_ENV class CredentialProvider(object): def init(self): @@ -114,6 +114,22 @@ def test_default_app_init(self, app_credential): assert isinstance(app.credential, credentials.ApplicationDefault) with pytest.raises(ValueError): firebase_admin.initialize_app(app_credential) + + def test_default_app_init_with_config_from_env(self, app_credential): + conf_file = os.getenv(CONFIG_FILE) + os.environ[CONFIG_FILE] = 'tests/data/firebase_config.json' + + app = firebase_admin.initialize_app(app_credential) + # finally: + # if project_id: + # os.environ[GCLOUD_PROJECT] = project_id + # else: + # del os.environ[GCLOUD_PROJECT] + # assert firebase_admin._DEFAULT_APP_NAME == app.name + # if app_credential: + # assert app_credential is app.credential + # else: + # assert isinstance(app.credential, credentials.ApplicationDefault) def test_non_default_app_init(self, app_credential): app = firebase_admin.initialize_app(app_credential, name='myApp') @@ -139,7 +155,9 @@ def test_app_init_with_invalid_options(self, options): def test_app_init_with_invalid_name(self, name): with pytest.raises(ValueError): firebase_admin.initialize_app(CREDENTIAL, name=name) - + def test_app_init_with_default_config(self,name): + #_CONFIG_FILE_ENV + app = firebase_admin.initialize_app() def test_project_id_from_options(self, app_credential): app = firebase_admin.initialize_app( app_credential, options={'projectId': 'test-project'}, name='myApp') From fb63e16ac4394791ce1db0ef2fb77d554a188098 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 21 Dec 2017 12:52:40 -0500 Subject: [PATCH 05/29] test bad json files, and add the fixtures for the good ones --- firebase_admin/__init__.py | 16 ++++---- tests/test_app.py | 79 +++++++++++++++++++++++++++++--------- 2 files changed, 67 insertions(+), 28 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index 00f20a342..fdb9d99f9 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -33,7 +33,7 @@ _DEFAULT_APP_NAME = '[DEFAULT]' _CONFIG_FILE_ENV = 'FIREBASE_CONFIG' -_FIREBASE_CONFIG_KEYS = ['databaseURL', 'projectId', 'storageBucket'] +_CONFIG_VALID_KEYS = ['databaseAuthVariableOverride', 'databaseURL', 'projectId', 'storageBucket'] def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): """Initializes and returns a new App instance. @@ -48,10 +48,10 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): credential: A credential object used to initialize the SDK (optional). If none is provided, Google Application Default Credentials are used. options: A dictionary of configuration options (optional). Supported options include - ``databaseURL``, ``storageBucket`` , ``projectId`` and ``httpTimeout``. If ``httpTimeout`` - is not set, HTTP connections initiated by client modules such as ``db`` will not time out. - name: Name of the app (optional). - name: The app name + ``databaseURL``, ``storageBucket``, ``projectId``, ``databaseAuthVariableOverride`` + and ``httpTimeout``. If ``httpTimeout`` is not set, HTTP connections initiated by client + modules such as ``db`` will not time out. + name: Name of the app (optional). Returns: App: A newly initialized instance of App. @@ -155,15 +155,13 @@ def __init__(self, options): config_file = os.getenv(_CONFIG_FILE_ENV) if config_file is None: return - if all(self._options.get(field) is not None for field in _FIREBASE_CONFIG_KEYS): - return with open(config_file, 'r') as json_file: try: json_data = json.load(json_file) except: raise ValueError('JSON string in {0} is not valid json.'.format(json_file)) - if any(field not in _FIREBASE_CONFIG_KEYS for field in json_data): - raise ValueError('Bad config key in file {}.'.format(config_file)) + if any(field not in _CONFIG_VALID_KEYS for field in json_data): + raise ValueError('Bad config key in JSON file {}.'.format(config_file)) json_data.update(self._options) self._options = json_data def get(self, key, default=None): diff --git a/tests/test_app.py b/tests/test_app.py index f07ab91f0..36143853b 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -70,6 +70,39 @@ def get(self): return None +class OptionsTest(object): + def __init__(self, config_filename, init_options=None, want_options=None): + self.config_filename = config_filename + self.init_options = init_options + self.want_options = want_options + + def init(self): + self.config_file_old = os.environ.get(CONFIG_FILE) + if self.config_filename: + os.environ[CONFIG_FILE] = testutils.resource_filename(self.config_filename) + elif os.environ.get(CONFIG_FILE): + del os.environ[CONFIG_FILE] + + def cleanup(self): + if self.config_file_old: + os.environ[CONFIG_FILE] = self.config_file_old + elif os.environ.get(CONFIG_FILE): + del os.environ[CONFIG_FILE] + +@pytest.fixture(params=[OptionsTest(None, {}, {}), + OptionsTest(None, + {'storageBucket':'bucket1'}, + {'storageBucket':'bucket1'}), + OptionsTest('firebase_config.json', + {'storageBucket':'bucket1'}, + {'storageBucket':'bucket1'})], + ids=['blank', 'blank_with_options', 'full-json']) +def test_option(request): + conf = request.param + conf.init() + yield conf + conf.cleanup() + class AppService(object): def __init__(self, app): self._app = app @@ -90,7 +123,6 @@ def init_app(request): else: return firebase_admin.initialize_app(CREDENTIAL) - class TestFirebaseApp(object): """Test cases for App initialization and life cycle.""" @@ -102,6 +134,11 @@ class TestFirebaseApp(object): firebase_admin.App('uninitialized', CREDENTIAL, {}) ] + bad_config_file = ['firebase_config_empty.json', + 'firebase_config_bad.json', + 'firebase_config_bad_key.json', + 'no_such_file'] + def teardown_method(self): testutils.cleanup_apps() @@ -114,22 +151,6 @@ def test_default_app_init(self, app_credential): assert isinstance(app.credential, credentials.ApplicationDefault) with pytest.raises(ValueError): firebase_admin.initialize_app(app_credential) - - def test_default_app_init_with_config_from_env(self, app_credential): - conf_file = os.getenv(CONFIG_FILE) - os.environ[CONFIG_FILE] = 'tests/data/firebase_config.json' - - app = firebase_admin.initialize_app(app_credential) - # finally: - # if project_id: - # os.environ[GCLOUD_PROJECT] = project_id - # else: - # del os.environ[GCLOUD_PROJECT] - # assert firebase_admin._DEFAULT_APP_NAME == app.name - # if app_credential: - # assert app_credential is app.credential - # else: - # assert isinstance(app.credential, credentials.ApplicationDefault) def test_non_default_app_init(self, app_credential): app = firebase_admin.initialize_app(app_credential, name='myApp') @@ -151,13 +172,33 @@ def test_app_init_with_invalid_options(self, options): with pytest.raises(ValueError): firebase_admin.initialize_app(CREDENTIAL, options=options) + @pytest.mark.parametrize('bad_file_name', bad_config_file) + def test_default_app_init_with_bad_config_from_env(self, bad_file_name): + config_file_old = os.environ.get(CONFIG_FILE) + os.environ[CONFIG_FILE] = testutils.resource_filename(bad_file_name) + try: + with pytest.raises(ValueError): + firebase_admin.initialize_app() + except IOError: + assert bad_file_name == 'no_such_file' + finally: + if config_file_old: + os.environ[CONFIG_FILE] = config_file_old + else: + del os.environ[CONFIG_FILE] + @pytest.mark.parametrize('name', invalid_names) def test_app_init_with_invalid_name(self, name): with pytest.raises(ValueError): firebase_admin.initialize_app(CREDENTIAL, name=name) - def test_app_init_with_default_config(self,name): + + def test_app_init_with_default_config(self, test_option): + app = firebase_admin.initialize_app(options=test_option.init_options) + print app.options + return #_CONFIG_FILE_ENV - app = firebase_admin.initialize_app() + app = firebase_admin.initialize_app() + def test_project_id_from_options(self, app_credential): app = firebase_admin.initialize_app( app_credential, options={'projectId': 'test-project'}, name='myApp') From 4159bbc9c5ca86f15a2a5fc8ba287ee3f59aaccd Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 21 Dec 2017 12:53:25 -0500 Subject: [PATCH 06/29] add json files --- tests/data/firebase_config.json | 6 ++++++ tests/data/firebase_config_bad.json | 1 + tests/data/firebase_config_bad_key.json | 4 ++++ tests/data/firebase_config_empty.json | 0 tests/data/firebase_config_partial.json | 4 ++++ 5 files changed, 15 insertions(+) create mode 100644 tests/data/firebase_config.json create mode 100644 tests/data/firebase_config_bad.json create mode 100644 tests/data/firebase_config_bad_key.json create mode 100644 tests/data/firebase_config_empty.json create mode 100644 tests/data/firebase_config_partial.json diff --git a/tests/data/firebase_config.json b/tests/data/firebase_config.json new file mode 100644 index 000000000..605d07e51 --- /dev/null +++ b/tests/data/firebase_config.json @@ -0,0 +1,6 @@ +{ + "databaseAuthVariableOverride": "this#is#an#auth#string", + "databaseURL": "https://hipster-chat.firebaseio.mock", + "projectId": "hipster-chat-mock", + "storageBucket": "hipster-chat.appspot.mock" +} diff --git a/tests/data/firebase_config_bad.json b/tests/data/firebase_config_bad.json new file mode 100644 index 000000000..74c098fca --- /dev/null +++ b/tests/data/firebase_config_bad.json @@ -0,0 +1 @@ +baaaaad diff --git a/tests/data/firebase_config_bad_key.json b/tests/data/firebase_config_bad_key.json new file mode 100644 index 000000000..079996fb0 --- /dev/null +++ b/tests/data/firebase_config_bad_key.json @@ -0,0 +1,4 @@ +{ + "databaseUrl": "https://hipster-chat.firebaseio.mock", + "projectID": "hipster-chat-mock" +} diff --git a/tests/data/firebase_config_empty.json b/tests/data/firebase_config_empty.json new file mode 100644 index 000000000..e69de29bb diff --git a/tests/data/firebase_config_partial.json b/tests/data/firebase_config_partial.json new file mode 100644 index 000000000..b02f92dde --- /dev/null +++ b/tests/data/firebase_config_partial.json @@ -0,0 +1,4 @@ +{ + "databaseURL": "https://hipster-chat.firebaseio.mock", + "projectId": "hipster-chat-mock" +} From 657e0bdf59c2d30070a444c3c71fda4f7cc4531e Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 21 Dec 2017 15:08:21 -0500 Subject: [PATCH 07/29] test good json files for default config --- firebase_admin/__init__.py | 2 +- tests/test_app.py | 51 ++++++++++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index fdb9d99f9..bdb21ccf9 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -48,7 +48,7 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): credential: A credential object used to initialize the SDK (optional). If none is provided, Google Application Default Credentials are used. options: A dictionary of configuration options (optional). Supported options include - ``databaseURL``, ``storageBucket``, ``projectId``, ``databaseAuthVariableOverride`` + ``databaseURL``, ``storageBucket``, ``projectId``, ``databaseAuthVariableOverride`` and ``httpTimeout``. If ``httpTimeout`` is not set, HTTP connections initiated by client modules such as ``db`` will not time out. name: Name of the app (optional). diff --git a/tests/test_app.py b/tests/test_app.py index 36143853b..08c78ef66 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -90,13 +90,50 @@ def cleanup(self): del os.environ[CONFIG_FILE] @pytest.fixture(params=[OptionsTest(None, {}, {}), - OptionsTest(None, + OptionsTest(None, {'storageBucket':'bucket1'}, {'storageBucket':'bucket1'}), - OptionsTest('firebase_config.json', + OptionsTest('firebase_config.json', {'storageBucket':'bucket1'}, - {'storageBucket':'bucket1'})], - ids=['blank', 'blank_with_options', 'full-json']) + {'databaseAuthVariableOverride': 'this#is#an#auth#string', + 'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock', + 'storageBucket': 'bucket1'}), + OptionsTest('firebase_config.json', + None, + {'databaseAuthVariableOverride': 'this#is#an#auth#string', + 'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock', + 'storageBucket': 'hipster-chat.appspot.mock'}), + OptionsTest('firebase_config.json', + {}, + {'databaseAuthVariableOverride': 'this#is#an#auth#string', + 'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock', + 'storageBucket': 'hipster-chat.appspot.mock'}), + OptionsTest('firebase_config_partial.json', + None, + {'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock'}), + OptionsTest('firebase_config_partial.json', + {'projectId':'pid1-mock', + 'storageBucket':'sb1-mock'}, + {'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-mock'}), + OptionsTest('firebase_config.json', + {'databaseAuthVariableOverride': 'davy1-mock', + 'databaseURL': 'https://db1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}, + {'databaseAuthVariableOverride': 'davy1-mock', + 'databaseURL': 'https://db1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'})], + ids=['blank', 'blank_with_options', + 'full-json', 'full-json-none-options', 'full-json-empty-opts', + 'partial-json-no-options', 'partial-json-non-overlapping', + 'full-json-no-overwrite']) def test_option(request): conf = request.param conf.init() @@ -194,10 +231,8 @@ def test_app_init_with_invalid_name(self, name): def test_app_init_with_default_config(self, test_option): app = firebase_admin.initialize_app(options=test_option.init_options) - print app.options - return - #_CONFIG_FILE_ENV - app = firebase_admin.initialize_app() + for field in firebase_admin._CONFIG_VALID_KEYS: + assert app.options.get(field) == test_option.want_options.get(field) def test_project_id_from_options(self, app_credential): app = firebase_admin.initialize_app( From 3432c34bbecce3ac0796ff605939313a9a598904 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 21 Dec 2017 16:17:59 -0500 Subject: [PATCH 08/29] CREDENTIAL --- tests/test_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_app.py b/tests/test_app.py index 08c78ef66..1a63e5991 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -230,7 +230,7 @@ def test_app_init_with_invalid_name(self, name): firebase_admin.initialize_app(CREDENTIAL, name=name) def test_app_init_with_default_config(self, test_option): - app = firebase_admin.initialize_app(options=test_option.init_options) + app = firebase_admin.initialize_app(CREDENTIAL, options=test_option.init_options) for field in firebase_admin._CONFIG_VALID_KEYS: assert app.options.get(field) == test_option.want_options.get(field) From 1c524ec558429f03bf48442f987ad597c70b2f96 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 21 Dec 2017 16:22:35 -0500 Subject: [PATCH 09/29] CREDENTIAL --- tests/test_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_app.py b/tests/test_app.py index 1a63e5991..c5ecca08b 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -215,7 +215,7 @@ def test_default_app_init_with_bad_config_from_env(self, bad_file_name): os.environ[CONFIG_FILE] = testutils.resource_filename(bad_file_name) try: with pytest.raises(ValueError): - firebase_admin.initialize_app() + firebase_admin.initialize_app(CREDENTIAL) except IOError: assert bad_file_name == 'no_such_file' finally: From fb920fcaa1ab2a1292443f58469c46fef7eacc8a Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sat, 23 Dec 2017 13:30:08 -0500 Subject: [PATCH 10/29] ignore the existing FIREBASE_CONFIG value in tests --- tests/test_app.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/test_app.py b/tests/test_app.py index c5ecca08b..f9df5a854 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -25,7 +25,20 @@ CREDENTIAL = credentials.Certificate( testutils.resource_filename('service_account.json')) GCLOUD_PROJECT = 'GCLOUD_PROJECT' -CONFIG_FILE = firebase_admin._CONFIG_FILE_ENV +CONFIG_FILE = firebase_admin._CONFIG_FILE_ENV + +@pytest.fixture(scope="session", autouse=True) +def ignore_config_file(request): + config_file_old = os.environ.get(CONFIG_FILE) + if config_file_old: + del os.environ[CONFIG_FILE] + def fin(): + if config_file_old: + os.environ[CONFIG_FILE] = config_file_old + else: + if os.environ.get(CONFIG_FILE): + del os.environ[CONFIG_FILE] + request.addfinalizer(fin) class CredentialProvider(object): def init(self): From 9eb492ae1f0a5052f5a83d01b4ec93779e0fa5a2 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sat, 23 Dec 2017 13:46:10 -0500 Subject: [PATCH 11/29] lint --- tests/test_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_app.py b/tests/test_app.py index f9df5a854..2a7500853 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -25,7 +25,7 @@ CREDENTIAL = credentials.Certificate( testutils.resource_filename('service_account.json')) GCLOUD_PROJECT = 'GCLOUD_PROJECT' -CONFIG_FILE = firebase_admin._CONFIG_FILE_ENV +CONFIG_FILE = firebase_admin._CONFIG_FILE_ENV @pytest.fixture(scope="session", autouse=True) def ignore_config_file(request): From 0f44837369bb836ad64639714a408cd1a1ebfb1a Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sun, 31 Dec 2017 00:05:38 -0500 Subject: [PATCH 12/29] Revert the update behavior of the FIREBASE_CONFIG env var. only use if options are None. --- firebase_admin/__init__.py | 20 ++++++------ tests/data/apikey.txt | 1 + tests/data/firebase_config.json | 2 +- tests/data/firebase_config_bad_key.json | 4 +-- tests/data/key.json | 12 +++++++ tests/test_app.py | 42 ++++++++++++------------- 6 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 tests/data/apikey.txt create mode 100644 tests/data/key.json diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index 413b92cf5..315490e56 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -146,24 +146,24 @@ class _AppOptions(object): """A collection of configuration options for an App.""" def __init__(self, options): - if options is None: - options = {} - if not isinstance(options, dict): - raise ValueError('Illegal Firebase app options type: {0}. Options ' - 'must be a dictionary.'.format(type(options))) - self._options = options + if options is not None: + if not isinstance(options, dict): + raise ValueError('Illegal Firebase app options type: {0}. Options ' + 'must be a dictionary.'.format(type(options))) + self._options = options + return config_file = os.getenv(_CONFIG_FILE_ENV) if config_file is None: + self._options = {} return with open(config_file, 'r') as json_file: try: json_data = json.load(json_file) except: raise ValueError('JSON string in {0} is not valid json.'.format(json_file)) - if any(field not in _CONFIG_VALID_KEYS for field in json_data): - raise ValueError('Bad config key in JSON file {}.'.format(config_file)) - json_data.update(self._options) - self._options = json_data + print json_data + self._options = {k: v for k, v in json_data.items() if k in _CONFIG_VALID_KEYS} + def get(self, key, default=None): """Returns the option identified by the provided key.""" return self._options.get(key, default) diff --git a/tests/data/apikey.txt b/tests/data/apikey.txt new file mode 100644 index 000000000..edcc83d58 --- /dev/null +++ b/tests/data/apikey.txt @@ -0,0 +1 @@ +AIzaSyA2HPbUzJqssyep97wUNDmJPHCaCkRE5qo diff --git a/tests/data/firebase_config.json b/tests/data/firebase_config.json index 605d07e51..5a120e3b6 100644 --- a/tests/data/firebase_config.json +++ b/tests/data/firebase_config.json @@ -1,5 +1,5 @@ { - "databaseAuthVariableOverride": "this#is#an#auth#string", + "databaseAuthVariableOverride": {"some_key": "some_val"}, "databaseURL": "https://hipster-chat.firebaseio.mock", "projectId": "hipster-chat-mock", "storageBucket": "hipster-chat.appspot.mock" diff --git a/tests/data/firebase_config_bad_key.json b/tests/data/firebase_config_bad_key.json index 079996fb0..bed63335d 100644 --- a/tests/data/firebase_config_bad_key.json +++ b/tests/data/firebase_config_bad_key.json @@ -1,4 +1,4 @@ { - "databaseUrl": "https://hipster-chat.firebaseio.mock", - "projectID": "hipster-chat-mock" + "databaseUr1": "https://hipster-chat.firebaseio.mock", + "projectId": "hipster-chat-mock" } diff --git a/tests/data/key.json b/tests/data/key.json new file mode 100644 index 000000000..689f1b09b --- /dev/null +++ b/tests/data/key.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "avishalom-testremote", + "private_key_id": "8d5f496b7398d347bad8d2188c3b6166d19709c4", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCw39qNTAZpiJxn\nn4Yjfg2BlU6780At2ntx36j1zzfobcx5WIC8CXHBSQgek8aAT1T/M36rC/1gIt7q\nPziif9bxx23q46OkI2nMVKGDrj7jX9hA+znqmAoKRPWqMYxrIyKlSwO52ESZ5qz3\nzf2ajPs7bUTVLrvBrPR15feRu74sywIgdkXym5pi1n04WbcPLmCYTkcVNKitkG5V\n8NTOknl3oiJfTwau1Nlcik7OqWR6W/Hteaed+PCq7wFjGOf7xksr3h+vaeTvbhZ/\nDvCS/jiK3mYGUtR/RkgGbQVA1FOzb3PfLRfDHQUFU1MblkroEbosHKlpEhc4O/3p\nchIvATJRAgMBAAECggEABqnYTPxxPWaVJd4/qnSkQ56BYdlPMKsLfYXEksYkwA/q\n5SNtbigY7gQ3VxeQIeNbO2YQPpdsC0QlC8Qn+Q9B5MfznybGUne3E6iYkSJuYhiJ\nSCFu1CS4NBzW+Ht3Y83mDz4dPNIl+3pSR9+tzzXCfUGnlzR0hF5ks1BfnFDrYDqR\nqDF6iEBJjZOp+b2ni3aqbL+S4hh8WMCKS7+H+DIK5YW+jgCtAdq4F82BYphZR1l2\nxxJPYsQSTNcFN5xwLLSUbUxZLUZSoSzsphA34EHM6tnffX6u2d4NYJdsoFWKvq0K\nAdB+2UA+ve4hKiR3IfJFVO3xbWToyPVMwgNe8Qk4dQKBgQDW/q/JH9p8cDaNlUlR\nZb+jnGSOP+5K4xxFqSdDgzADHEmNTUycb3U/KhMfKmSBW0kwsha2Qbb0BccSUq36\nvKJCdy5my4kaU35tMLNbNOzjo6SUPGvD32L4wEYRgSAggq4irdfYC+6nGt/2sHiq\nifKqf+OfOvhqRUr4yf23RQLrtwKBgQDSm+ctUwhEA7XcNK1Y//0Br0oVj/l9SWB0\nINRhUPt+LahpjHA3mA2Xp5GAtqYoT5/2KOvdPMsn/rULTgrvndTN7byiONk7eKAn\nE/8aI7noEcI8B0Q+FeRswfk0RDJglQqn457MB1IJFS62FA7f4mHo359XLv+hw8F1\nLLaC5P/iNwKBgHSuZNjS4NEIVZMholj6z7cL+cb6C2m5OG7WDuklGbmWcJSvjxSX\nqRpOy3Hun/1UfbRf8tOXdqOZPyFCngmmtqk75YMEk7Ql/EdmVvD5GFezSG7rG9GC\nO0SllWGKotUCF1/a2Xn3f95RR5uqytJDa9/86JxSdN+4G6hKd9gvLBahAoGAK+T4\nYpbqoxpO3e1Qr7rypVY+T44pRnZzluB8wyeYsuybLBxsCvoy4we2qKfoq4Ak664N\nspeIE/bfDtMeLmCryJui9AB7Lgzu/BYL1gvaJEcHP3GYRIRVjmFDKiSIh16o0OL8\n5rMj4ua6TJKTow/QrjNlR0txVeqxuFUwWMmXlzMCgYBevanyS7qz4t4NSXkv0n4M\nKJbiLgHcbhAyz0r6whEaC4kebDWc1RnPTiIdWyP1y1YLcNGJ7Q/Skx1fK4zOtkCF\nw8zr2I8tpkoywvvzc/vDFmN2/e0h/f2uiIuFcWTHd6Bm1N2xkTyCTe8+HGwQY5Qn\nq8h/LekU0QMNGk6nYO5/MQ==\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-33hi6@avishalom-testremote.iam.gserviceaccount.com", + "client_id": "109983970186096734463", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-33hi6%40avishalom-testremote.iam.gserviceaccount.com" +} diff --git a/tests/test_app.py b/tests/test_app.py index 2a7500853..4cab5e909 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -27,6 +27,8 @@ GCLOUD_PROJECT = 'GCLOUD_PROJECT' CONFIG_FILE = firebase_admin._CONFIG_FILE_ENV +# This fixture will ignore the environment variable pointing to the default +# configuration for the duration of the tests. @pytest.fixture(scope="session", autouse=True) def ignore_config_file(request): config_file_old = os.environ.get(CONFIG_FILE) @@ -104,35 +106,31 @@ def cleanup(self): @pytest.fixture(params=[OptionsTest(None, {}, {}), OptionsTest(None, - {'storageBucket':'bucket1'}, - {'storageBucket':'bucket1'}), + {'storageBucket': 'bucket1'}, + {'storageBucket': 'bucket1'}), OptionsTest('firebase_config.json', - {'storageBucket':'bucket1'}, - {'databaseAuthVariableOverride': 'this#is#an#auth#string', - 'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock', - 'storageBucket': 'bucket1'}), + {'storageBucket': 'bucket1'}, + {'storageBucket': 'bucket1'}), OptionsTest('firebase_config.json', None, - {'databaseAuthVariableOverride': 'this#is#an#auth#string', + {'databaseAuthVariableOverride': {'some_key': 'some_val'}, 'databaseURL': 'https://hipster-chat.firebaseio.mock', 'projectId': 'hipster-chat-mock', 'storageBucket': 'hipster-chat.appspot.mock'}), + OptionsTest('firebase_config_bad_key.json', + None, + {'projectId': 'hipster-chat-mock'}), OptionsTest('firebase_config.json', {}, - {'databaseAuthVariableOverride': 'this#is#an#auth#string', - 'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock', - 'storageBucket': 'hipster-chat.appspot.mock'}), + {}), OptionsTest('firebase_config_partial.json', None, {'databaseURL': 'https://hipster-chat.firebaseio.mock', 'projectId': 'hipster-chat-mock'}), OptionsTest('firebase_config_partial.json', - {'projectId':'pid1-mock', - 'storageBucket':'sb1-mock'}, - {'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'pid1-mock', + {'projectId': 'pid1-mock', + 'storageBucket': 'sb1-mock'}, + {'projectId': 'pid1-mock', 'storageBucket': 'sb1-mock'}), OptionsTest('firebase_config.json', {'databaseAuthVariableOverride': 'davy1-mock', @@ -143,10 +141,9 @@ def cleanup(self): 'databaseURL': 'https://db1-mock', 'projectId': 'pid1-mock', 'storageBucket': 'sb1-.mock'})], - ids=['blank', 'blank_with_options', - 'full-json', 'full-json-none-options', 'full-json-empty-opts', - 'partial-json-no-options', 'partial-json-non-overlapping', - 'full-json-no-overwrite']) + ids=['blank', 'blank_with_options', 'full-json', 'full-json-none-options', + 'bad-key-read-the-rest', 'full-json-empty-opts', 'partial-json-no-options', + 'partial-json-non-overlapping', 'full-json-no-overwrite']) def test_option(request): conf = request.param conf.init() @@ -186,7 +183,6 @@ class TestFirebaseApp(object): bad_config_file = ['firebase_config_empty.json', 'firebase_config_bad.json', - 'firebase_config_bad_key.json', 'no_such_file'] def teardown_method(self): @@ -244,7 +240,11 @@ def test_app_init_with_invalid_name(self, name): def test_app_init_with_default_config(self, test_option): app = firebase_admin.initialize_app(CREDENTIAL, options=test_option.init_options) + print "======" + print test_option.init_options, test_option.want_options , app.options._options, test_option.config_filename + print ";;;;" for field in firebase_admin._CONFIG_VALID_KEYS: + print field assert app.options.get(field) == test_option.want_options.get(field) def test_project_id_from_options(self, app_credential): From 44c1efa8dde73af0cdcb1a76857d0c5e20b5a0a5 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sun, 31 Dec 2017 00:16:03 -0500 Subject: [PATCH 13/29] cleanup --- firebase_admin/__init__.py | 1 - tests/data/apikey.txt | 1 - tests/data/key.json | 12 ------------ tests/test_app.py | 4 ---- 4 files changed, 18 deletions(-) delete mode 100644 tests/data/apikey.txt delete mode 100644 tests/data/key.json diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index 315490e56..b9c6a5d83 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -161,7 +161,6 @@ def __init__(self, options): json_data = json.load(json_file) except: raise ValueError('JSON string in {0} is not valid json.'.format(json_file)) - print json_data self._options = {k: v for k, v in json_data.items() if k in _CONFIG_VALID_KEYS} def get(self, key, default=None): diff --git a/tests/data/apikey.txt b/tests/data/apikey.txt deleted file mode 100644 index edcc83d58..000000000 --- a/tests/data/apikey.txt +++ /dev/null @@ -1 +0,0 @@ -AIzaSyA2HPbUzJqssyep97wUNDmJPHCaCkRE5qo diff --git a/tests/data/key.json b/tests/data/key.json deleted file mode 100644 index 689f1b09b..000000000 --- a/tests/data/key.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "service_account", - "project_id": "avishalom-testremote", - "private_key_id": "8d5f496b7398d347bad8d2188c3b6166d19709c4", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCw39qNTAZpiJxn\nn4Yjfg2BlU6780At2ntx36j1zzfobcx5WIC8CXHBSQgek8aAT1T/M36rC/1gIt7q\nPziif9bxx23q46OkI2nMVKGDrj7jX9hA+znqmAoKRPWqMYxrIyKlSwO52ESZ5qz3\nzf2ajPs7bUTVLrvBrPR15feRu74sywIgdkXym5pi1n04WbcPLmCYTkcVNKitkG5V\n8NTOknl3oiJfTwau1Nlcik7OqWR6W/Hteaed+PCq7wFjGOf7xksr3h+vaeTvbhZ/\nDvCS/jiK3mYGUtR/RkgGbQVA1FOzb3PfLRfDHQUFU1MblkroEbosHKlpEhc4O/3p\nchIvATJRAgMBAAECggEABqnYTPxxPWaVJd4/qnSkQ56BYdlPMKsLfYXEksYkwA/q\n5SNtbigY7gQ3VxeQIeNbO2YQPpdsC0QlC8Qn+Q9B5MfznybGUne3E6iYkSJuYhiJ\nSCFu1CS4NBzW+Ht3Y83mDz4dPNIl+3pSR9+tzzXCfUGnlzR0hF5ks1BfnFDrYDqR\nqDF6iEBJjZOp+b2ni3aqbL+S4hh8WMCKS7+H+DIK5YW+jgCtAdq4F82BYphZR1l2\nxxJPYsQSTNcFN5xwLLSUbUxZLUZSoSzsphA34EHM6tnffX6u2d4NYJdsoFWKvq0K\nAdB+2UA+ve4hKiR3IfJFVO3xbWToyPVMwgNe8Qk4dQKBgQDW/q/JH9p8cDaNlUlR\nZb+jnGSOP+5K4xxFqSdDgzADHEmNTUycb3U/KhMfKmSBW0kwsha2Qbb0BccSUq36\nvKJCdy5my4kaU35tMLNbNOzjo6SUPGvD32L4wEYRgSAggq4irdfYC+6nGt/2sHiq\nifKqf+OfOvhqRUr4yf23RQLrtwKBgQDSm+ctUwhEA7XcNK1Y//0Br0oVj/l9SWB0\nINRhUPt+LahpjHA3mA2Xp5GAtqYoT5/2KOvdPMsn/rULTgrvndTN7byiONk7eKAn\nE/8aI7noEcI8B0Q+FeRswfk0RDJglQqn457MB1IJFS62FA7f4mHo359XLv+hw8F1\nLLaC5P/iNwKBgHSuZNjS4NEIVZMholj6z7cL+cb6C2m5OG7WDuklGbmWcJSvjxSX\nqRpOy3Hun/1UfbRf8tOXdqOZPyFCngmmtqk75YMEk7Ql/EdmVvD5GFezSG7rG9GC\nO0SllWGKotUCF1/a2Xn3f95RR5uqytJDa9/86JxSdN+4G6hKd9gvLBahAoGAK+T4\nYpbqoxpO3e1Qr7rypVY+T44pRnZzluB8wyeYsuybLBxsCvoy4we2qKfoq4Ak664N\nspeIE/bfDtMeLmCryJui9AB7Lgzu/BYL1gvaJEcHP3GYRIRVjmFDKiSIh16o0OL8\n5rMj4ua6TJKTow/QrjNlR0txVeqxuFUwWMmXlzMCgYBevanyS7qz4t4NSXkv0n4M\nKJbiLgHcbhAyz0r6whEaC4kebDWc1RnPTiIdWyP1y1YLcNGJ7Q/Skx1fK4zOtkCF\nw8zr2I8tpkoywvvzc/vDFmN2/e0h/f2uiIuFcWTHd6Bm1N2xkTyCTe8+HGwQY5Qn\nq8h/LekU0QMNGk6nYO5/MQ==\n-----END PRIVATE KEY-----\n", - "client_email": "firebase-adminsdk-33hi6@avishalom-testremote.iam.gserviceaccount.com", - "client_id": "109983970186096734463", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://accounts.google.com/o/oauth2/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-33hi6%40avishalom-testremote.iam.gserviceaccount.com" -} diff --git a/tests/test_app.py b/tests/test_app.py index 4cab5e909..0b4c5ada6 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -240,11 +240,7 @@ def test_app_init_with_invalid_name(self, name): def test_app_init_with_default_config(self, test_option): app = firebase_admin.initialize_app(CREDENTIAL, options=test_option.init_options) - print "======" - print test_option.init_options, test_option.want_options , app.options._options, test_option.config_filename - print ";;;;" for field in firebase_admin._CONFIG_VALID_KEYS: - print field assert app.options.get(field) == test_option.want_options.get(field) def test_project_id_from_options(self, app_credential): From 284f7d7a4ea388f2f0fe5120f78a5187bdc51c46 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sun, 31 Dec 2017 00:22:19 -0500 Subject: [PATCH 14/29] lint --- tests/test_app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_app.py b/tests/test_app.py index 0b4c5ada6..ef7e3ed15 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -141,8 +141,9 @@ def cleanup(self): 'databaseURL': 'https://db1-mock', 'projectId': 'pid1-mock', 'storageBucket': 'sb1-.mock'})], - ids=['blank', 'blank_with_options', 'full-json', 'full-json-none-options', - 'bad-key-read-the-rest', 'full-json-empty-opts', 'partial-json-no-options', + ids=['blank', 'blank_with_options', 'full-json', + 'full-json-none-options', 'bad-key-read-the-rest', + 'full-json-empty-opts', 'partial-json-no-options', 'partial-json-non-overlapping', 'full-json-no-overwrite']) def test_option(request): conf = request.param From 417e0623b85d3e51a449dea99b8759b6843a9664 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Tue, 2 Jan 2018 18:33:22 -0500 Subject: [PATCH 15/29] change fixture format and add json ability to env file --- firebase_admin/__init__.py | 23 +++--- tests/test_app.py | 142 +++++++++++++++++++++---------------- 2 files changed, 95 insertions(+), 70 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index b9c6a5d83..bedff9fc8 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -32,7 +32,7 @@ _clock = datetime.datetime.utcnow _DEFAULT_APP_NAME = '[DEFAULT]' -_CONFIG_FILE_ENV = 'FIREBASE_CONFIG' +_CONFIG_JSON_ENV = 'FIREBASE_CONFIG' _CONFIG_VALID_KEYS = ['databaseAuthVariableOverride', 'databaseURL', 'projectId', 'storageBucket'] def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): @@ -152,15 +152,22 @@ def __init__(self, options): 'must be a dictionary.'.format(type(options))) self._options = options return - config_file = os.getenv(_CONFIG_FILE_ENV) - if config_file is None: + config_file = os.getenv(_CONFIG_JSON_ENV) + if config_file is None or config_file == "": self._options = {} return - with open(config_file, 'r') as json_file: - try: - json_data = json.load(json_file) - except: - raise ValueError('JSON string in {0} is not valid json.'.format(json_file)) + if config_file[0] == '{': + json_str = config_file + else: + with open(config_file, 'r') as json_file: + try: + json_str = json_file.read() + except: + raise ValueError('Unable to read file {}.'.format(json_file)) + try: + json_data = json.loads(json_str) + except Exception as err: + raise ValueError('JSON string "{0}" is not valid json. {1}'.format(json_str, err)) self._options = {k: v for k, v in json_data.items() if k in _CONFIG_VALID_KEYS} def get(self, key, default=None): diff --git a/tests/test_app.py b/tests/test_app.py index ef7e3ed15..7f59478bd 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -25,21 +25,21 @@ CREDENTIAL = credentials.Certificate( testutils.resource_filename('service_account.json')) GCLOUD_PROJECT = 'GCLOUD_PROJECT' -CONFIG_FILE = firebase_admin._CONFIG_FILE_ENV +CONFIG_JSON = firebase_admin._CONFIG_JSON_ENV # This fixture will ignore the environment variable pointing to the default # configuration for the duration of the tests. @pytest.fixture(scope="session", autouse=True) def ignore_config_file(request): - config_file_old = os.environ.get(CONFIG_FILE) + config_file_old = os.environ.get(CONFIG_JSON) if config_file_old: - del os.environ[CONFIG_FILE] + del os.environ[CONFIG_JSON] def fin(): if config_file_old: - os.environ[CONFIG_FILE] = config_file_old + os.environ[CONFIG_JSON] = config_file_old else: - if os.environ.get(CONFIG_FILE): - del os.environ[CONFIG_FILE] + if os.environ.get(CONFIG_JSON): + del os.environ[CONFIG_JSON] request.addfinalizer(fin) class CredentialProvider(object): @@ -86,65 +86,83 @@ def get(self): class OptionsTest(object): - def __init__(self, config_filename, init_options=None, want_options=None): - self.config_filename = config_filename + def __init__(self, config_json, init_options=None, want_options=None): + self.config_json = config_json self.init_options = init_options self.want_options = want_options def init(self): - self.config_file_old = os.environ.get(CONFIG_FILE) - if self.config_filename: - os.environ[CONFIG_FILE] = testutils.resource_filename(self.config_filename) - elif os.environ.get(CONFIG_FILE): - del os.environ[CONFIG_FILE] + self.config_file_old = os.environ.get(CONFIG_JSON) + if self.config_json: + os.environ[CONFIG_JSON] = testutils.resource_filename(self.config_json) + elif os.environ.get(CONFIG_JSON): + del os.environ[CONFIG_JSON] def cleanup(self): if self.config_file_old: - os.environ[CONFIG_FILE] = self.config_file_old - elif os.environ.get(CONFIG_FILE): - del os.environ[CONFIG_FILE] - -@pytest.fixture(params=[OptionsTest(None, {}, {}), - OptionsTest(None, - {'storageBucket': 'bucket1'}, - {'storageBucket': 'bucket1'}), - OptionsTest('firebase_config.json', - {'storageBucket': 'bucket1'}, - {'storageBucket': 'bucket1'}), - OptionsTest('firebase_config.json', - None, - {'databaseAuthVariableOverride': {'some_key': 'some_val'}, - 'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock', - 'storageBucket': 'hipster-chat.appspot.mock'}), - OptionsTest('firebase_config_bad_key.json', - None, - {'projectId': 'hipster-chat-mock'}), - OptionsTest('firebase_config.json', - {}, - {}), - OptionsTest('firebase_config_partial.json', - None, - {'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock'}), - OptionsTest('firebase_config_partial.json', - {'projectId': 'pid1-mock', - 'storageBucket': 'sb1-mock'}, - {'projectId': 'pid1-mock', - 'storageBucket': 'sb1-mock'}), - OptionsTest('firebase_config.json', - {'databaseAuthVariableOverride': 'davy1-mock', - 'databaseURL': 'https://db1-mock', - 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'}, - {'databaseAuthVariableOverride': 'davy1-mock', - 'databaseURL': 'https://db1-mock', - 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'})], - ids=['blank', 'blank_with_options', 'full-json', - 'full-json-none-options', 'bad-key-read-the-rest', - 'full-json-empty-opts', 'partial-json-no-options', - 'partial-json-non-overlapping', 'full-json-no-overwrite']) + os.environ[CONFIG_JSON] = self.config_file_old + elif os.environ.get(CONFIG_JSON): + del os.environ[CONFIG_JSON] + +def named_option_pairs_for_test(named_id_option_pairs): + return dict(zip(("ids", "params"), zip(*named_id_option_pairs))) +@pytest.fixture(**named_option_pairs_for_test([ + ('no env var, empty options', + OptionsTest(None, {}, {})), + ('env var empty string empty options', + OptionsTest('', {}, {})), + ('no env var, no options', + OptionsTest(None, None, {})), + ('empty string with no options', + OptionsTest('', None, {})), + ('no env var with options', + OptionsTest(None, + {'storageBucket': 'bucket1'}, + {'storageBucket': 'bucket1'})), + ('config file ignored with options passed', + OptionsTest('firebase_config.json', + {'storageBucket': 'bucket1'}, + {'storageBucket': 'bucket1'})), + ('asdf', + OptionsTest('firebase_config.json', + {'storageBucket': 'bucket1'}, + {'storageBucket': 'bucket1'})), + ('adf', + OptionsTest('firebase_config.json', + None, + {'databaseAuthVariableOverride': {'some_key': 'some_val'}, + 'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock', + 'storageBucket': 'hipster-chat.appspot.mock'})), + ('gf', + OptionsTest('firebase_config_bad_key.json', + None, + {'projectId': 'hipster-chat-mock'})), + ('adsfads', + OptionsTest('firebase_config.json', + {}, + {})), + ('asdffd', + OptionsTest('firebase_config_partial.json', + None, + {'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock'})), + ('asdfddf', + OptionsTest('firebase_config_partial.json', + {'projectId': 'pid1-mock', + 'storageBucket': 'sb1-mock'}, + {'projectId': 'pid1-mock', + 'storageBucket': 'sb1-mock'})), + ('sdfdf', + OptionsTest('firebase_config.json', + {'databaseAuthVariableOverride': 'davy1-mock', + 'databaseURL': 'https://db1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}, + {'databaseAuthVariableOverride': 'davy1-mock', + 'databaseURL': 'https://db1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}))])) def test_option(request): conf = request.param conf.init() @@ -221,8 +239,8 @@ def test_app_init_with_invalid_options(self, options): @pytest.mark.parametrize('bad_file_name', bad_config_file) def test_default_app_init_with_bad_config_from_env(self, bad_file_name): - config_file_old = os.environ.get(CONFIG_FILE) - os.environ[CONFIG_FILE] = testutils.resource_filename(bad_file_name) + config_file_old = os.environ.get(CONFIG_JSON) + os.environ[CONFIG_JSON] = testutils.resource_filename(bad_file_name) try: with pytest.raises(ValueError): firebase_admin.initialize_app(CREDENTIAL) @@ -230,9 +248,9 @@ def test_default_app_init_with_bad_config_from_env(self, bad_file_name): assert bad_file_name == 'no_such_file' finally: if config_file_old: - os.environ[CONFIG_FILE] = config_file_old + os.environ[CONFIG_JSON] = config_file_old else: - del os.environ[CONFIG_FILE] + del os.environ[CONFIG_JSON] @pytest.mark.parametrize('name', invalid_names) def test_app_init_with_invalid_name(self, name): From c3c54be6091cbe2f4b12f8b59512f433c18e1153 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Tue, 2 Jan 2018 21:19:22 -0500 Subject: [PATCH 16/29] added tests for json env var, and rearanged named tests --- firebase_admin/__init__.py | 2 +- tests/data/firebase_config_bad_key.json | 2 +- tests/test_app.py | 180 ++++++++++++++++-------- 3 files changed, 124 insertions(+), 60 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index bedff9fc8..d9d7da769 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -153,7 +153,7 @@ def __init__(self, options): self._options = options return config_file = os.getenv(_CONFIG_JSON_ENV) - if config_file is None or config_file == "": + if not config_file: self._options = {} return if config_file[0] == '{': diff --git a/tests/data/firebase_config_bad_key.json b/tests/data/firebase_config_bad_key.json index bed63335d..223c60044 100644 --- a/tests/data/firebase_config_bad_key.json +++ b/tests/data/firebase_config_bad_key.json @@ -1,4 +1,4 @@ { - "databaseUr1": "https://hipster-chat.firebaseio.mock", + "databaseUrrrrL": "https://hipster-chat.firebaseio.mock", "projectId": "hipster-chat-mock" } diff --git a/tests/test_app.py b/tests/test_app.py index 7f59478bd..8263859a5 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -93,8 +93,11 @@ def __init__(self, config_json, init_options=None, want_options=None): def init(self): self.config_file_old = os.environ.get(CONFIG_JSON) - if self.config_json: - os.environ[CONFIG_JSON] = testutils.resource_filename(self.config_json) + if self.config_json is not None: + if len(self.config_json) == 0 or self.config_json[0] == '{': + os.environ[CONFIG_JSON] = self.config_json + else: + os.environ[CONFIG_JSON] = testutils.resource_filename(self.config_json) elif os.environ.get(CONFIG_JSON): del os.environ[CONFIG_JSON] @@ -106,63 +109,124 @@ def cleanup(self): def named_option_pairs_for_test(named_id_option_pairs): return dict(zip(("ids", "params"), zip(*named_id_option_pairs))) + + @pytest.fixture(**named_option_pairs_for_test([ - ('no env var, empty options', - OptionsTest(None, {}, {})), - ('env var empty string empty options', - OptionsTest('', {}, {})), - ('no env var, no options', - OptionsTest(None, None, {})), - ('empty string with no options', - OptionsTest('', None, {})), - ('no env var with options', - OptionsTest(None, - {'storageBucket': 'bucket1'}, - {'storageBucket': 'bucket1'})), - ('config file ignored with options passed', - OptionsTest('firebase_config.json', - {'storageBucket': 'bucket1'}, - {'storageBucket': 'bucket1'})), - ('asdf', - OptionsTest('firebase_config.json', - {'storageBucket': 'bucket1'}, - {'storageBucket': 'bucket1'})), - ('adf', - OptionsTest('firebase_config.json', - None, - {'databaseAuthVariableOverride': {'some_key': 'some_val'}, - 'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock', - 'storageBucket': 'hipster-chat.appspot.mock'})), - ('gf', - OptionsTest('firebase_config_bad_key.json', - None, - {'projectId': 'hipster-chat-mock'})), - ('adsfads', - OptionsTest('firebase_config.json', - {}, - {})), - ('asdffd', - OptionsTest('firebase_config_partial.json', - None, - {'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock'})), - ('asdfddf', - OptionsTest('firebase_config_partial.json', - {'projectId': 'pid1-mock', - 'storageBucket': 'sb1-mock'}, - {'projectId': 'pid1-mock', - 'storageBucket': 'sb1-mock'})), - ('sdfdf', - OptionsTest('firebase_config.json', - {'databaseAuthVariableOverride': 'davy1-mock', - 'databaseURL': 'https://db1-mock', - 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'}, - {'databaseAuthVariableOverride': 'davy1-mock', - 'databaseURL': 'https://db1-mock', - 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'}))])) + ( + 'no env var, empty options', + OptionsTest(None, {}, {}) + ), ( + 'env var empty string empty options', + OptionsTest('', {}, {}) + ), ( + 'no env var, no options', + OptionsTest(None, None, {}) + ), ( + 'empty string with no options', + OptionsTest('', None, {}) + ), ( + 'no env var with options', + OptionsTest(None, + {'storageBucket': 'bucket1'}, + {'storageBucket': 'bucket1'}) + ), ( + 'config file ignored with options passed', + OptionsTest('firebase_config.json', + {'storageBucket': 'bucket1'}, + {'storageBucket': 'bucket1'}) + ), ( + 'config json ignored with options passed', + OptionsTest('{"storageBucket": "hipster-chat.appspot.mock"}', + {'storageBucket': 'bucket1'}, + {'storageBucket': 'bucket1'}) + ), ( + 'config file is used when no options are present', + OptionsTest('firebase_config.json', + None, + {'databaseAuthVariableOverride': {'some_key': 'some_val'}, + 'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock', + 'storageBucket': 'hipster-chat.appspot.mock'}) + ), ( + 'config json is used when no options are present', + OptionsTest('''{ + "databaseAuthVariableOverride": {"some_key": "some_val"}, + "databaseURL": "https://hipster-chat.firebaseio.mock", + "projectId": "hipster-chat-mock", + "storageBucket": "hipster-chat.appspot.mock" + }''', + None, + {'databaseAuthVariableOverride': {'some_key': 'some_val'}, + 'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock', + 'storageBucket': 'hipster-chat.appspot.mock'}) + ), ( + 'bad key in file is ignored', + OptionsTest('firebase_config_bad_key.json', + None, + {'projectId': 'hipster-chat-mock'}) + ), ( + 'bad key in json is ignored', + OptionsTest('''{ + "databaseUrrrrL": "https://hipster-chat.firebaseio.mock", + "projectId": "hipster-chat-mock" + }''', + None, + {'projectId': 'hipster-chat-mock'}) + ), ( + 'empty options are options, file is ignored', + OptionsTest('firebase_config.json', + {}, + {}) + ), ( + 'empty options are options, json is ignored', + OptionsTest('{"projectId": "hipster-chat-mock"}', + {}, + {}) + ), ( + 'no options, partial config in file', + OptionsTest('firebase_config_partial.json', + None, + {'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock'}) + ), ( + 'no options, partial config in json', + OptionsTest('''{ + "databaseURL": "https://hipster-chat.firebaseio.mock", + "projectId": "hipster-chat-mock" + }''', + None, + {'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock'}) + ), ( + 'partial config file is ignored', + OptionsTest('firebase_config_partial.json', + {'projectId': 'pid1-mock', + 'storageBucket': 'sb1-mock'}, + {'projectId': 'pid1-mock', + 'storageBucket': 'sb1-mock'}) + ), ( + 'full config file is ignored', + OptionsTest('firebase_config.json', + {'databaseAuthVariableOverride': 'davy1-mock', + 'databaseURL': 'https://db1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}, + {'databaseAuthVariableOverride': 'davy1-mock', + 'databaseURL': 'https://db1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}) + ), ( + 'full config file is ignored with missing values in options', + OptionsTest('firebase_config.json', + {'databaseAuthVariableOverride': 'davy1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}, + {'databaseAuthVariableOverride': 'davy1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}) + ) + ])) def test_option(request): conf = request.param conf.init() From e20592df67951c3758a12a602923830f316f8c5c Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Wed, 3 Jan 2018 03:24:33 -0500 Subject: [PATCH 17/29] unify the open and read in one try block --- firebase_admin/__init__.py | 8 ++++---- tests/test_app.py | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index d9d7da769..1a9ca0090 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -159,11 +159,11 @@ def __init__(self, options): if config_file[0] == '{': json_str = config_file else: - with open(config_file, 'r') as json_file: - try: + try: + with open(config_file, 'r') as json_file: json_str = json_file.read() - except: - raise ValueError('Unable to read file {}.'.format(json_file)) + except Exception as err: + raise ValueError('Unable to read file {}. {}'.format(config_file, err)) try: json_data = json.loads(json_str) except Exception as err: diff --git a/tests/test_app.py b/tests/test_app.py index 8263859a5..8e586ff6b 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -308,8 +308,6 @@ def test_default_app_init_with_bad_config_from_env(self, bad_file_name): try: with pytest.raises(ValueError): firebase_admin.initialize_app(CREDENTIAL) - except IOError: - assert bad_file_name == 'no_such_file' finally: if config_file_old: os.environ[CONFIG_JSON] = config_file_old From 58caa4b0ec21a4449430f23f44c423ff7a47e76c Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sat, 6 Jan 2018 02:15:00 -0500 Subject: [PATCH 18/29] PR comments, changing the parametrized fixture to a parametrized test. refactor env var setting --- firebase_admin/__init__.py | 39 ++- ..._bad.json => firebase_config_invalid.json} | 0 ....json => firebase_config_invalid_key.json} | 0 tests/test_app.py | 323 ++++++++---------- 4 files changed, 166 insertions(+), 196 deletions(-) rename tests/data/{firebase_config_bad.json => firebase_config_invalid.json} (100%) rename tests/data/{firebase_config_bad_key.json => firebase_config_invalid_key.json} (100%) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index 1a9ca0090..441d9863c 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -32,8 +32,9 @@ _clock = datetime.datetime.utcnow _DEFAULT_APP_NAME = '[DEFAULT]' -_CONFIG_JSON_ENV = 'FIREBASE_CONFIG' -_CONFIG_VALID_KEYS = ['databaseAuthVariableOverride', 'databaseURL', 'projectId', 'storageBucket'] +_FIREBASE_CONFIG_ENV_VAR = 'FIREBASE_CONFIG' +_CONFIG_VALID_KEYS = ['databaseAuthVariableOverride', 'databaseURL', 'httpTimeout', 'projectId', + 'storageBucket'] def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): """Initializes and returns a new App instance. @@ -146,17 +147,31 @@ class _AppOptions(object): """A collection of configuration options for an App.""" def __init__(self, options): - if options is not None: - if not isinstance(options, dict): - raise ValueError('Illegal Firebase app options type: {0}. Options ' - 'must be a dictionary.'.format(type(options))) - self._options = options - return - config_file = os.getenv(_CONFIG_JSON_ENV) + self._options = options + if options is None: + self._load_from_environment() + + if not isinstance(self._options, dict): + raise ValueError('Illegal Firebase app options type: {0}. Options ' + 'must be a dictionary.'.format(type(self._options))) + + + def get(self, key, default=None): + """Returns the option identified by the provided key.""" + return self._options.get(key, default) + + def _load_from_environment(self): + """Invoked when no options are passed to __init__, loads options from FIREBASE_CONFIG. + + If the value of the FIREBASE_CONFIG environment variable starts with "{" an attempt is made + to parse it as a JSON object, otherwise it is assumed to be pointing to a JSON file. + """ + + config_file = os.getenv(_FIREBASE_CONFIG_ENV_VAR) if not config_file: self._options = {} return - if config_file[0] == '{': + if config_file.startswith('{'): json_str = config_file else: try: @@ -170,10 +185,6 @@ def __init__(self, options): raise ValueError('JSON string "{0}" is not valid json. {1}'.format(json_str, err)) self._options = {k: v for k, v in json_data.items() if k in _CONFIG_VALID_KEYS} - def get(self, key, default=None): - """Returns the option identified by the provided key.""" - return self._options.get(key, default) - class App(object): """The entry point for Firebase Python SDK. diff --git a/tests/data/firebase_config_bad.json b/tests/data/firebase_config_invalid.json similarity index 100% rename from tests/data/firebase_config_bad.json rename to tests/data/firebase_config_invalid.json diff --git a/tests/data/firebase_config_bad_key.json b/tests/data/firebase_config_invalid_key.json similarity index 100% rename from tests/data/firebase_config_bad_key.json rename to tests/data/firebase_config_invalid_key.json diff --git a/tests/test_app.py b/tests/test_app.py index 8e586ff6b..1029000b4 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for firebase_admin.App.""" +from collections import namedtuple import os import pytest @@ -25,22 +26,10 @@ CREDENTIAL = credentials.Certificate( testutils.resource_filename('service_account.json')) GCLOUD_PROJECT = 'GCLOUD_PROJECT' -CONFIG_JSON = firebase_admin._CONFIG_JSON_ENV +CONFIG_JSON = firebase_admin._FIREBASE_CONFIG_ENV_VAR # This fixture will ignore the environment variable pointing to the default # configuration for the duration of the tests. -@pytest.fixture(scope="session", autouse=True) -def ignore_config_file(request): - config_file_old = os.environ.get(CONFIG_JSON) - if config_file_old: - del os.environ[CONFIG_JSON] - def fin(): - if config_file_old: - os.environ[CONFIG_JSON] = config_file_old - else: - if os.environ.get(CONFIG_JSON): - del os.environ[CONFIG_JSON] - request.addfinalizer(fin) class CredentialProvider(object): def init(self): @@ -85,159 +74,10 @@ def get(self): return None -class OptionsTest(object): - def __init__(self, config_json, init_options=None, want_options=None): - self.config_json = config_json - self.init_options = init_options - self.want_options = want_options - - def init(self): - self.config_file_old = os.environ.get(CONFIG_JSON) - if self.config_json is not None: - if len(self.config_json) == 0 or self.config_json[0] == '{': - os.environ[CONFIG_JSON] = self.config_json - else: - os.environ[CONFIG_JSON] = testutils.resource_filename(self.config_json) - elif os.environ.get(CONFIG_JSON): - del os.environ[CONFIG_JSON] - - def cleanup(self): - if self.config_file_old: - os.environ[CONFIG_JSON] = self.config_file_old - elif os.environ.get(CONFIG_JSON): - del os.environ[CONFIG_JSON] - -def named_option_pairs_for_test(named_id_option_pairs): - return dict(zip(("ids", "params"), zip(*named_id_option_pairs))) - - -@pytest.fixture(**named_option_pairs_for_test([ - ( - 'no env var, empty options', - OptionsTest(None, {}, {}) - ), ( - 'env var empty string empty options', - OptionsTest('', {}, {}) - ), ( - 'no env var, no options', - OptionsTest(None, None, {}) - ), ( - 'empty string with no options', - OptionsTest('', None, {}) - ), ( - 'no env var with options', - OptionsTest(None, - {'storageBucket': 'bucket1'}, - {'storageBucket': 'bucket1'}) - ), ( - 'config file ignored with options passed', - OptionsTest('firebase_config.json', - {'storageBucket': 'bucket1'}, - {'storageBucket': 'bucket1'}) - ), ( - 'config json ignored with options passed', - OptionsTest('{"storageBucket": "hipster-chat.appspot.mock"}', - {'storageBucket': 'bucket1'}, - {'storageBucket': 'bucket1'}) - ), ( - 'config file is used when no options are present', - OptionsTest('firebase_config.json', - None, - {'databaseAuthVariableOverride': {'some_key': 'some_val'}, - 'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock', - 'storageBucket': 'hipster-chat.appspot.mock'}) - ), ( - 'config json is used when no options are present', - OptionsTest('''{ - "databaseAuthVariableOverride": {"some_key": "some_val"}, - "databaseURL": "https://hipster-chat.firebaseio.mock", - "projectId": "hipster-chat-mock", - "storageBucket": "hipster-chat.appspot.mock" - }''', - None, - {'databaseAuthVariableOverride': {'some_key': 'some_val'}, - 'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock', - 'storageBucket': 'hipster-chat.appspot.mock'}) - ), ( - 'bad key in file is ignored', - OptionsTest('firebase_config_bad_key.json', - None, - {'projectId': 'hipster-chat-mock'}) - ), ( - 'bad key in json is ignored', - OptionsTest('''{ - "databaseUrrrrL": "https://hipster-chat.firebaseio.mock", - "projectId": "hipster-chat-mock" - }''', - None, - {'projectId': 'hipster-chat-mock'}) - ), ( - 'empty options are options, file is ignored', - OptionsTest('firebase_config.json', - {}, - {}) - ), ( - 'empty options are options, json is ignored', - OptionsTest('{"projectId": "hipster-chat-mock"}', - {}, - {}) - ), ( - 'no options, partial config in file', - OptionsTest('firebase_config_partial.json', - None, - {'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock'}) - ), ( - 'no options, partial config in json', - OptionsTest('''{ - "databaseURL": "https://hipster-chat.firebaseio.mock", - "projectId": "hipster-chat-mock" - }''', - None, - {'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock'}) - ), ( - 'partial config file is ignored', - OptionsTest('firebase_config_partial.json', - {'projectId': 'pid1-mock', - 'storageBucket': 'sb1-mock'}, - {'projectId': 'pid1-mock', - 'storageBucket': 'sb1-mock'}) - ), ( - 'full config file is ignored', - OptionsTest('firebase_config.json', - {'databaseAuthVariableOverride': 'davy1-mock', - 'databaseURL': 'https://db1-mock', - 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'}, - {'databaseAuthVariableOverride': 'davy1-mock', - 'databaseURL': 'https://db1-mock', - 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'}) - ), ( - 'full config file is ignored with missing values in options', - OptionsTest('firebase_config.json', - {'databaseAuthVariableOverride': 'davy1-mock', - 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'}, - {'databaseAuthVariableOverride': 'davy1-mock', - 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'}) - ) - ])) -def test_option(request): - conf = request.param - conf.init() - yield conf - conf.cleanup() - class AppService(object): def __init__(self, app): self._app = app - @pytest.fixture(params=[Cert(), RefreshToken(), ExplicitAppDefault(), ImplicitAppDefault()], ids=['cert', 'refreshtoken', 'explicit-appdefault', 'implicit-appdefault']) def app_credential(request): @@ -253,6 +93,26 @@ def init_app(request): else: return firebase_admin.initialize_app(CREDENTIAL) + +def set_config_env(config_json): + config_old = os.environ.get(CONFIG_JSON) + if config_json is not None: + if not config_json or config_json.startswith('{'): + os.environ[CONFIG_JSON] = config_json + else: + os.environ[CONFIG_JSON] = testutils.resource_filename( + config_json) + elif os.environ.get(CONFIG_JSON) is not None: + del os.environ[CONFIG_JSON] + return config_old + + +def revert_config_env(config_old): + if config_old is not None: + os.environ[CONFIG_JSON] = config_old + elif os.environ.get(CONFIG_JSON) is not None: + del os.environ[CONFIG_JSON] + class TestFirebaseApp(object): """Test cases for App initialization and life cycle.""" @@ -264,10 +124,6 @@ class TestFirebaseApp(object): firebase_admin.App('uninitialized', CREDENTIAL, {}) ] - bad_config_file = ['firebase_config_empty.json', - 'firebase_config_bad.json', - 'no_such_file'] - def teardown_method(self): testutils.cleanup_apps() @@ -301,28 +157,131 @@ def test_app_init_with_invalid_options(self, options): with pytest.raises(ValueError): firebase_admin.initialize_app(CREDENTIAL, options=options) - @pytest.mark.parametrize('bad_file_name', bad_config_file) - def test_default_app_init_with_bad_config_from_env(self, bad_file_name): - config_file_old = os.environ.get(CONFIG_JSON) - os.environ[CONFIG_JSON] = testutils.resource_filename(bad_file_name) - try: - with pytest.raises(ValueError): - firebase_admin.initialize_app(CREDENTIAL) - finally: - if config_file_old: - os.environ[CONFIG_JSON] = config_file_old - else: - del os.environ[CONFIG_JSON] - @pytest.mark.parametrize('name', invalid_names) def test_app_init_with_invalid_name(self, name): with pytest.raises(ValueError): firebase_admin.initialize_app(CREDENTIAL, name=name) - def test_app_init_with_default_config(self, test_option): - app = firebase_admin.initialize_app(CREDENTIAL, options=test_option.init_options) + + @pytest.mark.parametrize('bad_file_name', ['firebase_config_empty.json', + 'firebase_config_invalid.json', + 'no_such_file']) + def test_app_init_with_invalid_config_file(self, bad_file_name): + config_old = set_config_env(bad_file_name) + with pytest.raises(ValueError): + firebase_admin.initialize_app() + revert_config_env(config_old) + + + OptionsTestCase = namedtuple('OptionsTestCase', + 'name, config_json, init_options, want_options') + options_test_cases = [ + OptionsTestCase(name='no env var, empty options', + config_json=None, + init_options={}, + want_options={}), + OptionsTestCase(name='env var empty string empty options', + config_json='', + init_options={}, + want_options={}), + OptionsTestCase(name='no env var, no options', + config_json=None, + init_options=None, + want_options={}), + OptionsTestCase(name='empty string with no options', + config_json='', + init_options=None, + want_options={}), + OptionsTestCase(name='no env var with options', + config_json=None, + init_options={'storageBucket': 'bucket1'}, + want_options={'storageBucket': 'bucket1'}), + OptionsTestCase(name='config file ignored with options passed', + config_json='firebase_config.json', + init_options={'storageBucket': 'bucket1'}, + want_options={'storageBucket': 'bucket1'}), + OptionsTestCase(name='config json ignored with options passed', + config_json='{"storageBucket": "hipster-chat.appspot.mock"}', + init_options={'storageBucket': 'bucket1'}, + want_options={'storageBucket': 'bucket1'}), + OptionsTestCase(name='config file is used when no options are present', + config_json='firebase_config.json', + init_options=None, + want_options={'databaseAuthVariableOverride': {'some_key': 'some_val'}, + 'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock', + 'storageBucket': 'hipster-chat.appspot.mock'}), + OptionsTestCase(name='config json is used when no options are present', + config_json='{"databaseAuthVariableOverride": {"some_key": "some_val"}, ' + + '"databaseURL": "https://hipster-chat.firebaseio.mock", ' + + '"projectId": "hipster-chat-mock",' + + '"storageBucket": "hipster-chat.appspot.mock"}', + init_options=None, + want_options={'databaseAuthVariableOverride': {'some_key': 'some_val'}, + 'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock', + 'storageBucket': 'hipster-chat.appspot.mock'}), + OptionsTestCase(name='invalid key in file is ignored', + config_json='firebase_config_invalid_key.json', + init_options=None, + want_options={'projectId': 'hipster-chat-mock'}), + OptionsTestCase(name='invalid key in json is ignored', + config_json='{"databaseUrrrrL": "https://hipster-chat.firebaseio.mock",' + + '"projectId": "hipster-chat-mock"}', + init_options=None, + want_options={'projectId': 'hipster-chat-mock'}), + OptionsTestCase(name='empty options are options, file is ignored', + config_json='firebase_config.json', + init_options={}, + want_options={}), + OptionsTestCase(name='empty options are options, json is ignored', + config_json='{"projectId": "hipster-chat-mock"}', + init_options={}, + want_options={}), + OptionsTestCase(name='no options, partial config in file', + config_json='firebase_config_partial.json', + init_options=None, + want_options={'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock'}), + OptionsTestCase(name='no options, partial config in json', + config_json='{"databaseURL": "https://hipster-chat.firebaseio.mock",' + + '"projectId": "hipster-chat-mock"}', + init_options=None, + want_options={'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock'}), + OptionsTestCase(name='partial config file is ignored', + config_json='firebase_config_partial.json', + init_options={'projectId': 'pid1-mock', + 'storageBucket': 'sb1-mock'}, + want_options={'projectId': 'pid1-mock', + 'storageBucket': 'sb1-mock'}), + OptionsTestCase(name='full config file is ignored', + config_json='firebase_config.json', + init_options={'databaseAuthVariableOverride': 'davy1-mock', + 'databaseURL': 'https://db1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}, + want_options={'databaseAuthVariableOverride': 'davy1-mock', + 'databaseURL': 'https://db1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}), + OptionsTestCase(name='full config file is ignored with missing values in options', + config_json='firebase_config.json', + init_options={'databaseAuthVariableOverride': 'davy1 - mock', + 'projectId': 'pid1 - mock', + 'storageBucket': 'sb1 - .mock'}, + want_options={'databaseAuthVariableOverride': 'davy1 - mock', + 'projectId': 'pid1 - mock', + 'storageBucket': 'sb1 - .mock'})] + + @pytest.mark.parametrize('test_case', options_test_cases) + def test_app_init_with_default_config(self, test_case): + """Set the CONFIG env var and test that options are initialized""" + config_old = set_config_env(test_case.config_json) + app = firebase_admin.initialize_app(options=test_case.init_options) for field in firebase_admin._CONFIG_VALID_KEYS: - assert app.options.get(field) == test_option.want_options.get(field) + assert app.options.get(field) == test_case.want_options.get(field), test_case.name + revert_config_env(config_old) def test_project_id_from_options(self, app_credential): app = firebase_admin.initialize_app( From 140461c6314f412d39e167d00a57d45cbe5da03f Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sat, 6 Jan 2018 05:17:10 -0500 Subject: [PATCH 19/29] add CREDENTIAL back (for unit tests without ADC set up) --- tests/test_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_app.py b/tests/test_app.py index 1029000b4..8d9930072 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -169,7 +169,7 @@ def test_app_init_with_invalid_name(self, name): def test_app_init_with_invalid_config_file(self, bad_file_name): config_old = set_config_env(bad_file_name) with pytest.raises(ValueError): - firebase_admin.initialize_app() + firebase_admin.initialize_app(CREDENTIAL) revert_config_env(config_old) From 9b59bef6b2c2c07379e3d75d3a37b2554dc43545 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sat, 6 Jan 2018 05:26:21 -0500 Subject: [PATCH 20/29] add CREDENTIAL back (for unit tests without ADC set up) --- tests/test_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_app.py b/tests/test_app.py index 8d9930072..90ce33a09 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -278,7 +278,7 @@ def test_app_init_with_invalid_config_file(self, bad_file_name): def test_app_init_with_default_config(self, test_case): """Set the CONFIG env var and test that options are initialized""" config_old = set_config_env(test_case.config_json) - app = firebase_admin.initialize_app(options=test_case.init_options) + app = firebase_admin.initialize_app(CREDENTIAL, options=test_case.init_options) for field in firebase_admin._CONFIG_VALID_KEYS: assert app.options.get(field) == test_case.want_options.get(field), test_case.name revert_config_env(config_old) From 2bc327d6e01882f26e724b750d8766fc3ac92318 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 8 Jan 2018 11:01:28 -0500 Subject: [PATCH 21/29] move teardown code to finalizer in indirect fixture, to ensure teardown on exception. --- tests/test_app.py | 223 ++++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 108 deletions(-) diff --git a/tests/test_app.py b/tests/test_app.py index 90ce33a09..68304364e 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -93,6 +93,115 @@ def init_app(request): else: return firebase_admin.initialize_app(CREDENTIAL) +@pytest.fixture(scope="function") +def env_test_case(request): + config_old = set_config_env(request.param.config_json) + def fin(): + revert_config_env(config_old) + request.addfinalizer(fin) + return request.param + + +EnvOptionsTestCase = namedtuple('EnvOptionsTestCase', + 'name, config_json, init_options, want_options') +env_options_test_cases = [ + EnvOptionsTestCase(name='no env var, empty options', + config_json=None, + init_options={}, + want_options={}), + EnvOptionsTestCase(name='env var empty string empty options', + config_json='', + init_options={}, + want_options={}), + EnvOptionsTestCase(name='no env var, no options', + config_json=None, + init_options=None, + want_options={}), + EnvOptionsTestCase(name='empty string with no options', + config_json='', + init_options=None, + want_options={}), + EnvOptionsTestCase(name='no env var with options', + config_json=None, + init_options={'storageBucket': 'bucket1'}, + want_options={'storageBucket': 'bucket1'}), + EnvOptionsTestCase(name='config file ignored with options passed', + config_json='firebase_config.json', + init_options={'storageBucket': 'bucket1'}, + want_options={'storageBucket': 'bucket1'}), + EnvOptionsTestCase(name='config json ignored with options passed', + config_json='{"storageBucket": "hipster-chat.appspot.mock"}', + init_options={'storageBucket': 'bucket1'}, + want_options={'storageBucket': 'bucket1'}), + EnvOptionsTestCase(name='config file is used when no options are present', + config_json='firebase_config.json', + init_options=None, + want_options={'databaseAuthVariableOverride': {'some_key': 'some_val'}, + 'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock', + 'storageBucket': 'hipster-chat.appspot.mock'}), + EnvOptionsTestCase(name='config json is used when no options are present', + config_json='{"databaseAuthVariableOverride": {"some_key": "some_val"}, ' + + '"databaseURL": "https://hipster-chat.firebaseio.mock", ' + + '"projectId": "hipster-chat-mock",' + + '"storageBucket": "hipster-chat.appspot.mock"}', + init_options=None, + want_options={'databaseAuthVariableOverride': {'some_key': 'some_val'}, + 'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock', + 'storageBucket': 'hipster-chat.appspot.mock'}), + EnvOptionsTestCase(name='invalid key in file is ignored', + config_json='firebase_config_invalid_key.json', + init_options=None, + want_options={'projectId': 'hipster-chat-mock'}), + EnvOptionsTestCase(name='invalid key in json is ignored', + config_json='{"databaseUrrrrL": "https://hipster-chat.firebaseio.mock",' + + '"projectId": "hipster-chat-mock"}', + init_options=None, + want_options={'projectId': 'hipster-chat-mock'}), + EnvOptionsTestCase(name='empty options are options, file is ignored', + config_json='firebase_config.json', + init_options={}, + want_options={}), + EnvOptionsTestCase(name='empty options are options, json is ignored', + config_json='{"projectId": "hipster-chat-mock"}', + init_options={}, + want_options={}), + EnvOptionsTestCase(name='no options, partial config in file', + config_json='firebase_config_partial.json', + init_options=None, + want_options={'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock'}), + EnvOptionsTestCase(name='no options, partial config in json', + config_json='{"databaseURL": "https://hipster-chat.firebaseio.mock",' + + '"projectId": "hipster-chat-mock"}', + init_options=None, + want_options={'databaseURL': 'https://hipster-chat.firebaseio.mock', + 'projectId': 'hipster-chat-mock'}), + EnvOptionsTestCase(name='partial config file is ignored', + config_json='firebase_config_partial.json', + init_options={'projectId': 'pid1-mock', + 'storageBucket': 'sb1-mock'}, + want_options={'projectId': 'pid1-mock', + 'storageBucket': 'sb1-mock'}), + EnvOptionsTestCase(name='full config file is ignored', + config_json='firebase_config.json', + init_options={'databaseAuthVariableOverride': 'davy1-mock', + 'databaseURL': 'https://db1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}, + want_options={'databaseAuthVariableOverride': 'davy1-mock', + 'databaseURL': 'https://db1-mock', + 'projectId': 'pid1-mock', + 'storageBucket': 'sb1-.mock'}), + EnvOptionsTestCase(name='full config file is ignored with missing values in options', + config_json='firebase_config.json', + init_options={'databaseAuthVariableOverride': 'davy1 - mock', + 'projectId': 'pid1 - mock', + 'storageBucket': 'sb1 - .mock'}, + want_options={'databaseAuthVariableOverride': 'davy1 - mock', + 'projectId': 'pid1 - mock', + 'storageBucket': 'sb1 - .mock'})] def set_config_env(config_json): config_old = os.environ.get(CONFIG_JSON) @@ -173,115 +282,13 @@ def test_app_init_with_invalid_config_file(self, bad_file_name): revert_config_env(config_old) - OptionsTestCase = namedtuple('OptionsTestCase', - 'name, config_json, init_options, want_options') - options_test_cases = [ - OptionsTestCase(name='no env var, empty options', - config_json=None, - init_options={}, - want_options={}), - OptionsTestCase(name='env var empty string empty options', - config_json='', - init_options={}, - want_options={}), - OptionsTestCase(name='no env var, no options', - config_json=None, - init_options=None, - want_options={}), - OptionsTestCase(name='empty string with no options', - config_json='', - init_options=None, - want_options={}), - OptionsTestCase(name='no env var with options', - config_json=None, - init_options={'storageBucket': 'bucket1'}, - want_options={'storageBucket': 'bucket1'}), - OptionsTestCase(name='config file ignored with options passed', - config_json='firebase_config.json', - init_options={'storageBucket': 'bucket1'}, - want_options={'storageBucket': 'bucket1'}), - OptionsTestCase(name='config json ignored with options passed', - config_json='{"storageBucket": "hipster-chat.appspot.mock"}', - init_options={'storageBucket': 'bucket1'}, - want_options={'storageBucket': 'bucket1'}), - OptionsTestCase(name='config file is used when no options are present', - config_json='firebase_config.json', - init_options=None, - want_options={'databaseAuthVariableOverride': {'some_key': 'some_val'}, - 'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock', - 'storageBucket': 'hipster-chat.appspot.mock'}), - OptionsTestCase(name='config json is used when no options are present', - config_json='{"databaseAuthVariableOverride": {"some_key": "some_val"}, ' + - '"databaseURL": "https://hipster-chat.firebaseio.mock", ' + - '"projectId": "hipster-chat-mock",' + - '"storageBucket": "hipster-chat.appspot.mock"}', - init_options=None, - want_options={'databaseAuthVariableOverride': {'some_key': 'some_val'}, - 'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock', - 'storageBucket': 'hipster-chat.appspot.mock'}), - OptionsTestCase(name='invalid key in file is ignored', - config_json='firebase_config_invalid_key.json', - init_options=None, - want_options={'projectId': 'hipster-chat-mock'}), - OptionsTestCase(name='invalid key in json is ignored', - config_json='{"databaseUrrrrL": "https://hipster-chat.firebaseio.mock",' + - '"projectId": "hipster-chat-mock"}', - init_options=None, - want_options={'projectId': 'hipster-chat-mock'}), - OptionsTestCase(name='empty options are options, file is ignored', - config_json='firebase_config.json', - init_options={}, - want_options={}), - OptionsTestCase(name='empty options are options, json is ignored', - config_json='{"projectId": "hipster-chat-mock"}', - init_options={}, - want_options={}), - OptionsTestCase(name='no options, partial config in file', - config_json='firebase_config_partial.json', - init_options=None, - want_options={'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock'}), - OptionsTestCase(name='no options, partial config in json', - config_json='{"databaseURL": "https://hipster-chat.firebaseio.mock",' + - '"projectId": "hipster-chat-mock"}', - init_options=None, - want_options={'databaseURL': 'https://hipster-chat.firebaseio.mock', - 'projectId': 'hipster-chat-mock'}), - OptionsTestCase(name='partial config file is ignored', - config_json='firebase_config_partial.json', - init_options={'projectId': 'pid1-mock', - 'storageBucket': 'sb1-mock'}, - want_options={'projectId': 'pid1-mock', - 'storageBucket': 'sb1-mock'}), - OptionsTestCase(name='full config file is ignored', - config_json='firebase_config.json', - init_options={'databaseAuthVariableOverride': 'davy1-mock', - 'databaseURL': 'https://db1-mock', - 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'}, - want_options={'databaseAuthVariableOverride': 'davy1-mock', - 'databaseURL': 'https://db1-mock', - 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'}), - OptionsTestCase(name='full config file is ignored with missing values in options', - config_json='firebase_config.json', - init_options={'databaseAuthVariableOverride': 'davy1 - mock', - 'projectId': 'pid1 - mock', - 'storageBucket': 'sb1 - .mock'}, - want_options={'databaseAuthVariableOverride': 'davy1 - mock', - 'projectId': 'pid1 - mock', - 'storageBucket': 'sb1 - .mock'})] - - @pytest.mark.parametrize('test_case', options_test_cases) - def test_app_init_with_default_config(self, test_case): - """Set the CONFIG env var and test that options are initialized""" - config_old = set_config_env(test_case.config_json) - app = firebase_admin.initialize_app(CREDENTIAL, options=test_case.init_options) + @pytest.mark.parametrize('env_test_case', env_options_test_cases, + ids=[x.name for x in env_options_test_cases], + indirect=['env_test_case']) + def test_app_init_with_default_config(self, env_test_case): + app = firebase_admin.initialize_app(CREDENTIAL, options=env_test_case.init_options) for field in firebase_admin._CONFIG_VALID_KEYS: - assert app.options.get(field) == test_case.want_options.get(field), test_case.name - revert_config_env(config_old) + assert app.options.get(field) == env_test_case.want_options.get(field) def test_project_id_from_options(self, app_credential): app = firebase_admin.initialize_app( From ade3983fdf79110dd42ae78a64a29eb0f093998d Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 8 Jan 2018 11:26:01 -0500 Subject: [PATCH 22/29] renaming test cases for config options --- tests/test_app.py | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/tests/test_app.py b/tests/test_app.py index 68304364e..def3e22ad 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -105,42 +105,42 @@ def fin(): EnvOptionsTestCase = namedtuple('EnvOptionsTestCase', 'name, config_json, init_options, want_options') env_options_test_cases = [ - EnvOptionsTestCase(name='no env var, empty options', + EnvOptionsTestCase(name='Environment var not set, initialized with an empty options dict', config_json=None, init_options={}, want_options={}), - EnvOptionsTestCase(name='env var empty string empty options', + EnvOptionsTestCase(name='Environment var empty, initialized with an empty options dict', config_json='', init_options={}, want_options={}), - EnvOptionsTestCase(name='no env var, no options', + EnvOptionsTestCase(name='Environment var not set, initialized with no options dict', config_json=None, init_options=None, want_options={}), - EnvOptionsTestCase(name='empty string with no options', + EnvOptionsTestCase(name='Environment empty, initialized with no options dict', config_json='', init_options=None, want_options={}), - EnvOptionsTestCase(name='no env var with options', + EnvOptionsTestCase(name='Environment var not set, initialized with options dict', config_json=None, init_options={'storageBucket': 'bucket1'}, want_options={'storageBucket': 'bucket1'}), - EnvOptionsTestCase(name='config file ignored with options passed', + EnvOptionsTestCase(name='Environment var set to file but ignored, initialized with options dict', config_json='firebase_config.json', init_options={'storageBucket': 'bucket1'}, want_options={'storageBucket': 'bucket1'}), - EnvOptionsTestCase(name='config json ignored with options passed', + EnvOptionsTestCase(name='Environment var set to string json but ignored, initialized with options dict', config_json='{"storageBucket": "hipster-chat.appspot.mock"}', init_options={'storageBucket': 'bucket1'}, want_options={'storageBucket': 'bucket1'}), - EnvOptionsTestCase(name='config file is used when no options are present', + EnvOptionsTestCase(name='Environment var set to json file and used, initialized with no options dict', config_json='firebase_config.json', init_options=None, want_options={'databaseAuthVariableOverride': {'some_key': 'some_val'}, 'databaseURL': 'https://hipster-chat.firebaseio.mock', 'projectId': 'hipster-chat-mock', 'storageBucket': 'hipster-chat.appspot.mock'}), - EnvOptionsTestCase(name='config json is used when no options are present', + EnvOptionsTestCase(name='Environment var set to json string and used, initialized with no options dict', config_json='{"databaseAuthVariableOverride": {"some_key": "some_val"}, ' + '"databaseURL": "https://hipster-chat.firebaseio.mock", ' + '"projectId": "hipster-chat-mock",' + @@ -150,41 +150,41 @@ def fin(): 'databaseURL': 'https://hipster-chat.firebaseio.mock', 'projectId': 'hipster-chat-mock', 'storageBucket': 'hipster-chat.appspot.mock'}), - EnvOptionsTestCase(name='invalid key in file is ignored', + EnvOptionsTestCase(name='Invalid key in json file is ignored, the rest of the values are used', config_json='firebase_config_invalid_key.json', init_options=None, want_options={'projectId': 'hipster-chat-mock'}), - EnvOptionsTestCase(name='invalid key in json is ignored', + EnvOptionsTestCase(name='Invalid key in json file is ignored, the rest of the values are used', config_json='{"databaseUrrrrL": "https://hipster-chat.firebaseio.mock",' + '"projectId": "hipster-chat-mock"}', init_options=None, want_options={'projectId': 'hipster-chat-mock'}), - EnvOptionsTestCase(name='empty options are options, file is ignored', + EnvOptionsTestCase(name='Environment var set to json file but ignored, initialized with empty options dict', config_json='firebase_config.json', init_options={}, want_options={}), - EnvOptionsTestCase(name='empty options are options, json is ignored', + EnvOptionsTestCase(name='Environment var set to json file but ignored, initialized with empty options dict', config_json='{"projectId": "hipster-chat-mock"}', init_options={}, want_options={}), - EnvOptionsTestCase(name='no options, partial config in file', + EnvOptionsTestCase(name='Environment variable set to json file with some options set', config_json='firebase_config_partial.json', init_options=None, want_options={'databaseURL': 'https://hipster-chat.firebaseio.mock', 'projectId': 'hipster-chat-mock'}), - EnvOptionsTestCase(name='no options, partial config in json', + EnvOptionsTestCase(name='Environment variable set to json string with some options set', config_json='{"databaseURL": "https://hipster-chat.firebaseio.mock",' + '"projectId": "hipster-chat-mock"}', init_options=None, want_options={'databaseURL': 'https://hipster-chat.firebaseio.mock', 'projectId': 'hipster-chat-mock'}), - EnvOptionsTestCase(name='partial config file is ignored', + EnvOptionsTestCase(name='Environment var set to json file but ignored, initialized with options dict', config_json='firebase_config_partial.json', init_options={'projectId': 'pid1-mock', 'storageBucket': 'sb1-mock'}, want_options={'projectId': 'pid1-mock', 'storageBucket': 'sb1-mock'}), - EnvOptionsTestCase(name='full config file is ignored', + EnvOptionsTestCase(name='Environment var set to string json but ignored, initialized with full options dict', config_json='firebase_config.json', init_options={'databaseAuthVariableOverride': 'davy1-mock', 'databaseURL': 'https://db1-mock', @@ -193,15 +193,7 @@ def fin(): want_options={'databaseAuthVariableOverride': 'davy1-mock', 'databaseURL': 'https://db1-mock', 'projectId': 'pid1-mock', - 'storageBucket': 'sb1-.mock'}), - EnvOptionsTestCase(name='full config file is ignored with missing values in options', - config_json='firebase_config.json', - init_options={'databaseAuthVariableOverride': 'davy1 - mock', - 'projectId': 'pid1 - mock', - 'storageBucket': 'sb1 - .mock'}, - want_options={'databaseAuthVariableOverride': 'davy1 - mock', - 'projectId': 'pid1 - mock', - 'storageBucket': 'sb1 - .mock'})] + 'storageBucket': 'sb1-.mock'})] def set_config_env(config_json): config_old = os.environ.get(CONFIG_JSON) From b07314d0b930d4604170883dda8b604b9a3b71a8 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 8 Jan 2018 11:34:56 -0500 Subject: [PATCH 23/29] added test for bad json string --- tests/test_app.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_app.py b/tests/test_app.py index def3e22ad..0d824b38b 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -273,6 +273,13 @@ def test_app_init_with_invalid_config_file(self, bad_file_name): firebase_admin.initialize_app(CREDENTIAL) revert_config_env(config_old) + def test_app_init_with_invalid_config_string(self): + config_old = set_config_env('{,,') + print config_old + with pytest.raises(ValueError): + firebase_admin.initialize_app(CREDENTIAL) + revert_config_env(config_old) + @pytest.mark.parametrize('env_test_case', env_options_test_cases, ids=[x.name for x in env_options_test_cases], From 10747d4b972e129255bf26f423ce8a7377e94d9d Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 8 Jan 2018 11:35:36 -0500 Subject: [PATCH 24/29] whitespace --- firebase_admin/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index 441d9863c..4c6516557 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -155,7 +155,6 @@ def __init__(self, options): raise ValueError('Illegal Firebase app options type: {0}. Options ' 'must be a dictionary.'.format(type(self._options))) - def get(self, key, default=None): """Returns the option identified by the provided key.""" return self._options.get(key, default) From 2837e41805b3f4113bebde27973b9b3c8ea0732d Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 8 Jan 2018 11:37:39 -0500 Subject: [PATCH 25/29] loading options returns a dict an is non mutating --- firebase_admin/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index 4c6516557..dacc4c4d3 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -149,11 +149,12 @@ class _AppOptions(object): def __init__(self, options): self._options = options if options is None: - self._load_from_environment() + options = self._load_from_environment() - if not isinstance(self._options, dict): + if not isinstance(options, dict): raise ValueError('Illegal Firebase app options type: {0}. Options ' 'must be a dictionary.'.format(type(self._options))) + self._options = options def get(self, key, default=None): """Returns the option identified by the provided key.""" @@ -168,8 +169,7 @@ def _load_from_environment(self): config_file = os.getenv(_FIREBASE_CONFIG_ENV_VAR) if not config_file: - self._options = {} - return + return {} if config_file.startswith('{'): json_str = config_file else: @@ -182,7 +182,7 @@ def _load_from_environment(self): json_data = json.loads(json_str) except Exception as err: raise ValueError('JSON string "{0}" is not valid json. {1}'.format(json_str, err)) - self._options = {k: v for k, v in json_data.items() if k in _CONFIG_VALID_KEYS} + return {k: v for k, v in json_data.items() if k in _CONFIG_VALID_KEYS} class App(object): From 12929d0d9435c59890f51e3ea6330197557b903b Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 8 Jan 2018 11:38:13 -0500 Subject: [PATCH 26/29] cleanup --- tests/test_app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_app.py b/tests/test_app.py index 0d824b38b..4a02a3213 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -275,7 +275,6 @@ def test_app_init_with_invalid_config_file(self, bad_file_name): def test_app_init_with_invalid_config_string(self): config_old = set_config_env('{,,') - print config_old with pytest.raises(ValueError): firebase_admin.initialize_app(CREDENTIAL) revert_config_env(config_old) From 48650dd9c8012e0cf9b3e095b01e18e744fe8042 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 8 Jan 2018 11:49:46 -0500 Subject: [PATCH 27/29] line length lint (config option test names) --- tests/test_app.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_app.py b/tests/test_app.py index 4a02a3213..66cfadb2b 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -125,22 +125,22 @@ def fin(): config_json=None, init_options={'storageBucket': 'bucket1'}, want_options={'storageBucket': 'bucket1'}), - EnvOptionsTestCase(name='Environment var set to file but ignored, initialized with options dict', + EnvOptionsTestCase(name='Environment var set to file but ignored, initialized with options', config_json='firebase_config.json', init_options={'storageBucket': 'bucket1'}, want_options={'storageBucket': 'bucket1'}), - EnvOptionsTestCase(name='Environment var set to string json but ignored, initialized with options dict', + EnvOptionsTestCase(name='Environment var set to json but ignored, initialized with options', config_json='{"storageBucket": "hipster-chat.appspot.mock"}', init_options={'storageBucket': 'bucket1'}, want_options={'storageBucket': 'bucket1'}), - EnvOptionsTestCase(name='Environment var set to json file and used, initialized with no options dict', + EnvOptionsTestCase(name='Environment var set to file, initialized with no options dict', config_json='firebase_config.json', init_options=None, want_options={'databaseAuthVariableOverride': {'some_key': 'some_val'}, 'databaseURL': 'https://hipster-chat.firebaseio.mock', 'projectId': 'hipster-chat-mock', 'storageBucket': 'hipster-chat.appspot.mock'}), - EnvOptionsTestCase(name='Environment var set to json string and used, initialized with no options dict', + EnvOptionsTestCase(name='Environment var set to json string, initialized with no options dict', config_json='{"databaseAuthVariableOverride": {"some_key": "some_val"}, ' + '"databaseURL": "https://hipster-chat.firebaseio.mock", ' + '"projectId": "hipster-chat-mock",' + @@ -159,11 +159,11 @@ def fin(): '"projectId": "hipster-chat-mock"}', init_options=None, want_options={'projectId': 'hipster-chat-mock'}), - EnvOptionsTestCase(name='Environment var set to json file but ignored, initialized with empty options dict', + EnvOptionsTestCase(name='Environment var set to file but ignored, init empty options dict', config_json='firebase_config.json', init_options={}, want_options={}), - EnvOptionsTestCase(name='Environment var set to json file but ignored, initialized with empty options dict', + EnvOptionsTestCase(name='Environment var set to string but ignored, init empty options dict', config_json='{"projectId": "hipster-chat-mock"}', init_options={}, want_options={}), @@ -178,13 +178,13 @@ def fin(): init_options=None, want_options={'databaseURL': 'https://hipster-chat.firebaseio.mock', 'projectId': 'hipster-chat-mock'}), - EnvOptionsTestCase(name='Environment var set to json file but ignored, initialized with options dict', + EnvOptionsTestCase(name='Environment var set to json file but ignored, init with options dict', config_json='firebase_config_partial.json', init_options={'projectId': 'pid1-mock', 'storageBucket': 'sb1-mock'}, want_options={'projectId': 'pid1-mock', 'storageBucket': 'sb1-mock'}), - EnvOptionsTestCase(name='Environment var set to string json but ignored, initialized with full options dict', + EnvOptionsTestCase(name='Environment var set to file but ignored, init with full options dict', config_json='firebase_config.json', init_options={'databaseAuthVariableOverride': 'davy1-mock', 'databaseURL': 'https://db1-mock', From d85801b2420fd6feb041a776b17528bbcf4076ac Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 8 Jan 2018 16:34:23 -0500 Subject: [PATCH 28/29] Addressing comments. (cleanup code, and compare dictionaries directly.) --- firebase_admin/__init__.py | 3 +-- tests/test_app.py | 9 +++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index dacc4c4d3..c9e853472 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -147,13 +147,12 @@ class _AppOptions(object): """A collection of configuration options for an App.""" def __init__(self, options): - self._options = options if options is None: options = self._load_from_environment() if not isinstance(options, dict): raise ValueError('Illegal Firebase app options type: {0}. Options ' - 'must be a dictionary.'.format(type(self._options))) + 'must be a dictionary.'.format(type(options))) self._options = options def get(self, key, default=None): diff --git a/tests/test_app.py b/tests/test_app.py index 66cfadb2b..e4450ebf8 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -96,10 +96,8 @@ def init_app(request): @pytest.fixture(scope="function") def env_test_case(request): config_old = set_config_env(request.param.config_json) - def fin(): - revert_config_env(config_old) - request.addfinalizer(fin) - return request.param + yield request.param + revert_config_env(config_old) EnvOptionsTestCase = namedtuple('EnvOptionsTestCase', @@ -285,8 +283,7 @@ def test_app_init_with_invalid_config_string(self): indirect=['env_test_case']) def test_app_init_with_default_config(self, env_test_case): app = firebase_admin.initialize_app(CREDENTIAL, options=env_test_case.init_options) - for field in firebase_admin._CONFIG_VALID_KEYS: - assert app.options.get(field) == env_test_case.want_options.get(field) + assert app.options._options == env_test_case.want_options def test_project_id_from_options(self, app_credential): app = firebase_admin.initialize_app( From 3b69a8b94a638e1ac2a95797812d279a40b980bf Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Wed, 10 Jan 2018 17:18:06 -0500 Subject: [PATCH 29/29] initialize_app comment explaining getting options from env. --- firebase_admin/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index c9e853472..69ef5ac93 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -52,6 +52,10 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): ``databaseURL``, ``storageBucket``, ``projectId``, ``databaseAuthVariableOverride`` and ``httpTimeout``. If ``httpTimeout`` is not set, HTTP connections initiated by client modules such as ``db`` will not time out. + If options are not provided an attempt is made to load the options from the environment. + This is done by looking up the ``FIREBASE_CONFIG`` environment variable. If the value of + the variable starts with ``"{"``, it is parsed as a JSON object. Otherwise it is treated + as a file name and the JSON content is read from the corresponding file. name: Name of the app (optional). Returns: App: A newly initialized instance of App.