Skip to content

Commit d6241cd

Browse files
committed
Experimental TED
1 parent db62c46 commit d6241cd

File tree

3 files changed

+132
-6
lines changed

3 files changed

+132
-6
lines changed

.idea/runConfigurations/tests.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/conftest.py

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,20 @@
2323
import os
2424
import logging
2525
import json
26+
27+
import psycopg2
28+
import psycopg2.sql
29+
import psycopg2.extensions
2630
import pytest
2731

2832
from typing import Optional, List, Dict
2933

3034
import jwt
3135
import oauthlib.oauth2
3236
import requests_oauthlib
37+
from flask_migrate import upgrade
3338

34-
from mrmat_python_api_flask import create_app, db
39+
from mrmat_python_api_flask import create_app, db, migrate
3540

3641
LOGGER = logging.getLogger(__name__)
3742

@@ -59,10 +64,7 @@ def test_config() -> Optional[Dict]:
5964
Returns:
6065
A dictionary of configuration or None if not configuration file is set
6166
"""
62-
if 'FLASK_CONFIG' not in os.environ:
63-
LOGGER.info('Missing test configuration via FLASK_CONFIG environment variable. Tests are limited')
64-
return None
65-
config_file = os.path.expanduser(os.environ['FLASK_CONFIG'])
67+
config_file = os.path.expanduser(os.getenv('FLASK_CONFIG'))
6668
if not os.path.exists(config_file):
6769
LOGGER.info('Configuration set via FLASK_CONFIG environment variable does not exist. Tests are limited')
6870
return None
@@ -148,3 +150,92 @@ def oidc_token_write(test_config) -> Optional[Dict]:
148150
token = oidc_token(test_config, ['mrmat-python-api-flask-resource-write'])
149151
token['jwt'] = jwt.decode(token['access_token'], options={"verify_signature": False})
150152
return token
153+
154+
155+
class TEDException(Exception):
156+
skip: bool = False
157+
msg: str = 'An unexpected exception occurred'
158+
159+
def __init__(self, msg: str, skip: Optional[bool] = False):
160+
self.skip = skip
161+
self.msg = msg
162+
163+
164+
class TED:
165+
available: bool = False
166+
config_file: str = None
167+
config: dict = {}
168+
169+
_admin_conn: psycopg2.extensions.connection = None
170+
171+
def __init__(self):
172+
if 'TED_CONFIG' not in os.environ:
173+
raise TEDException(skip=True,
174+
msg='There is no TED_CONFIG environment variable to configure the environment')
175+
self.config_file = os.path.expanduser(os.getenv('TED_CONFIG'))
176+
if not os.path.exists(self.config_file):
177+
raise TEDException(skip=True, msg='Configuration from TED_CONFIG environment variable is not readable or '
178+
'does not exist')
179+
with open(self.config_file) as C:
180+
self.config = json.load(C)
181+
if 'db' in self.config:
182+
self.admin_dsn = self.config['db']['admin_dsn']
183+
self.user_dsn = self.config['db']['user_dsn']
184+
self.assert_db()
185+
186+
def teardown(self):
187+
if self._admin_conn is not None:
188+
self._admin_conn.close()
189+
190+
def assert_db(self):
191+
user_dsn_info: psycopg2.extensions.ConnectionInfo = psycopg2.extensions.parse_dsn(self.user_dsn)
192+
role = user_dsn_info['user']
193+
password = user_dsn_info['password']
194+
schema = self.config['db']['user_force_schema'] if 'user_force_schema' in self.config['db'] else role
195+
self._admin_conn = psycopg2.connect(self.config['db']['admin_dsn'])
196+
with self._admin_conn.cursor() as cur:
197+
cur.execute("SELECT COUNT(rolname) FROM pg_roles WHERE rolname = %(role_name)s;",
198+
{'role_name': role})
199+
role_count = cur.fetchone()
200+
if role_count[0] == 0:
201+
cur.execute(
202+
psycopg2.sql.SQL('CREATE ROLE {} ENCRYPTED PASSWORD %(password)s LOGIN').format(
203+
psycopg2.sql.Identifier(role)),
204+
{'password': password})
205+
self._admin_conn.commit()
206+
cur.execute(psycopg2.sql.SQL('CREATE SCHEMA IF NOT EXISTS {} AUTHORIZATION {}').format(
207+
psycopg2.sql.Identifier(schema),
208+
psycopg2.sql.Identifier(role)))
209+
self._admin_conn.commit()
210+
cur.execute(psycopg2.sql.SQL('ALTER ROLE {} SET search_path TO {}').format(
211+
psycopg2.sql.Identifier(role),
212+
psycopg2.sql.Identifier(schema)))
213+
self._admin_conn.commit()
214+
215+
216+
@pytest.fixture(scope='session', autouse=False)
217+
def ted() -> TED:
218+
"""Test Environment on Demand
219+
220+
Read a config file to establish a class containing information about the test environment to be injected into
221+
all tests that require one.
222+
"""
223+
try:
224+
ted = TED()
225+
yield ted
226+
LOGGER.info('Teardown')
227+
except TEDException as te:
228+
if te.skip:
229+
pytest.skip(msg=te.msg)
230+
231+
232+
@pytest.fixture(scope='module')
233+
def ted_client(ted):
234+
ted.assert_db()
235+
app = create_app({'TESTING': True,
236+
'SQLALCHEMY_DATABASE_URI': ted.config['db']['user_dsn']})
237+
with app.app_context():
238+
upgrade(directory=os.path.join(os.path.dirname(__file__), '..', 'migrations'))
239+
db.create_all()
240+
with app.test_client() as client:
241+
yield client

tests/test_ted.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# MIT License
2+
#
3+
# Copyright (c) 2021 MrMat
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
import logging
24+
25+
LOGGER = logging.getLogger(__name__)
26+
27+
28+
def test_ted_yield(ted):
29+
LOGGER.info('Running our test')
30+
31+
32+
class TestTED:
33+
34+
def test_one(self, ted_client):
35+
LOGGER.info('Running test_one')

0 commit comments

Comments
 (0)