Module licenseware.test_helpers
Controllers (api declarations)
Here are some test helpers utilities which are useful in creating unit tests.
Ideally each controler should contain only funtionality related to swagger/openapi docs and each endpoint should call a function or class method which returns a response and a status code.
No business logic should be incorporated in the controller.
The failsafe
decorator provided by the sdk should be placed ONLY on endpoints to prevent app crash.
Bellow is a recommendation on how the controller should look:
# /app/controlers/catalogs_controller.py
from flask import request
from flask_restx import Resource, Namespace, fields
from licenseware.decorators.failsafe_decorator import failsafe
from licenseware.decorators.auth_decorators import authorization_check
from licenseware.utils.logger import log
from app.services.catalogs import get_all_catalogs
from app.utils.decorators import access_level
from app.common.constants import rights
ns = Namespace(
name="Catalogs",
description="Admin users can handle product catalogs. Regular users can view catalogs.",
path='/catalogs',
decorators=[authorization_check]
)
catalog_model = ns.model("new_catalog", dict(
catalog_name = fields.String(required=True, description='Catalog name'),
tags = fields.List(fields.String, description='List of tags/categories which this catalog belongs to'),
admins = fields.List(fields.String, required=True, description='List of admins for this catalog (emails)'),
users = fields.List(fields.String, required=False, description='List of users for this catalog (emails)')
))
catalog_model_list = ns.clone("catalogs_list", catalog_model, dict(
updated_at = fields.String(required=False),
catalog_id = fields.String(required=False),
product_requests_allowed = fields.Boolean(required=False),
number_of_products = fields.Integer()
))
@ns.route('')
class CrudApi(Resource):
@failsafe(fail_code=500)
@ns.doc(description="Get list of all saved catalogs")
@ns.param('limit', 'Limit the number of results')
@ns.param('skip', 'Skip the first n results')
@ns.param('search_value', 'Search for a specific value')
@ns.response(200, description="List of all saved catalogs", model=[catalog_model_list])
@ns.response(406, description="Access for this catalog is forbidden", model=None)
@ns.response(500, description="Could not get catalogs. Something went wrong.", model=None)
def get(self):
return get_all_catalogs(request)
# etc
The function get_all_catalogs
contains the logic required
to process the flask request received or other parameters/path parameters provided.
Function get_all_catalogs
will return a response and a status_code.
Test creation
Create all needed test_*.py
files in the tests
folder next to the app
.
Ex:
.
├── __init__.py
├── test_add_catalogs.py
└── test_merge_catalogs.py
Ideally you should test one feature per test case. For example up we have a test suite for adding catalogs and one separate for merging catalogs.
Example of a test:
import pytest
from unittest import TestCase
from flask.testing import Client
from licenseware.mongodata import collection
from licenseware.common.constants import envs
from licenseware.utils.logger import log
from main import app # this is used to get the flask test client and initialize app and workers
from app.common.constants import collections
from licenseware.test_helpers.auth import AuthHelper # this is useful to get authorization headers
# this will be used on all tests (in case of endpoint testing)
@pytest.fixture
def c():
with app.test_client() as client:
yield client
@pytest.fixture
def teardown():
clean_db()
# some utilities functions specific to this test
def increase_quota():
with collection(envs.MONGO_COLLECTION_UTILIZATION_NAME) as col:
res = col.find_one(filter={'uploader_id': 'catalogs_quota'})
if res is None: return
if res['monthly_quota'] <= 1:
col.update_one(
filter={'uploader_id': 'catalogs_quota'},
update={"$inc": {'monthly_quota': 9999}}
)
# asserts can be used also instead of unittest.TestCase assertions
T = TestCase()
# some global variables used
main_admin = "alin+mainadmin@licenseware.io"
catalog1 = "Catalog 1"
catalog2 = "Catalog 2"
catalog3 = "Catalog 3"
catalog4 = "Catalog 4"
cloned_lware_catalog = "Cloned Licenseware Catalog"
user_email1 = "alin+user1@licenseware.io"
user_email2 = "alin+user2@licenseware.io"
user_email3 = "alin+user3@licenseware.io"
user_email4 = "alin+user4@licenseware.io"
admin_email1 = "alin+admin1@licenseware.io"
admin_email2 = "alin+admin2@licenseware.io"
admin_email3 = "alin+admin3@licenseware.io"
admin_email4 = "alin+admin4@licenseware.io"
# tox tests/test_catalogs.py
# tox tests/test_catalogs.py::test_add_new_catalogs
def test_add_new_catalogs(c: Client):
auth_headers = AuthHelper(main_admin).get_auth_headers()
response = c.get(
'/ssc/activate_app',
headers=auth_headers
)
T.assertEqual(response.status_code, 200)
increase_quota()
catalogs = [
{
"catalog_name": catalog1,
"tags": ["IT"],
"admins": [main_admin, admin_email1],
"users": [user_email1]
},
{
"catalog_name": catalog2,
"tags": ["IT"],
"admins": [main_admin, admin_email2],
"users": [user_email2]
},
{
"catalog_name": catalog3,
"tags": ["IT"],
"admins": [main_admin, admin_email3],
"users": [user_email3]
}
]
for payload in catalogs:
response = c.post(
'/ssc/catalogs',
json=payload,
headers=auth_headers
)
log.warning(response.data)
# log.warning(response.get_json())
T.assertEqual(response.status_code, 201)
def test_something_else(c: Client):
result = get_somthing_from_db()
assert admin_email3 in result
# etc
If tests are dependent on each other (data from a test is needed on another test) you can either place all tests in one file (big file hard to manage though) or get add the seed data to db in a fixture function (recomended)
Expand source code
"""
# Controllers (api declarations)
Here are some test helpers utilities which are useful in creating unit tests.
Ideally each controler should contain only funtionality related to swagger/openapi docs and
each endpoint should call a function or class method which returns a response and a status code.
No business logic should be incorporated in the controller.
The `failsafe` decorator provided by the sdk should be placed ONLY on endpoints to prevent app crash.
Bellow is a recommendation on how the controller should look:
```py
# /app/controlers/catalogs_controller.py
from flask import request
from flask_restx import Resource, Namespace, fields
from licenseware.decorators.failsafe_decorator import failsafe
from licenseware.decorators.auth_decorators import authorization_check
from licenseware.utils.logger import log
from app.services.catalogs import get_all_catalogs
from app.utils.decorators import access_level
from app.common.constants import rights
ns = Namespace(
name="Catalogs",
description="Admin users can handle product catalogs. Regular users can view catalogs.",
path='/catalogs',
decorators=[authorization_check]
)
catalog_model = ns.model("new_catalog", dict(
catalog_name = fields.String(required=True, description='Catalog name'),
tags = fields.List(fields.String, description='List of tags/categories which this catalog belongs to'),
admins = fields.List(fields.String, required=True, description='List of admins for this catalog (emails)'),
users = fields.List(fields.String, required=False, description='List of users for this catalog (emails)')
))
catalog_model_list = ns.clone("catalogs_list", catalog_model, dict(
updated_at = fields.String(required=False),
catalog_id = fields.String(required=False),
product_requests_allowed = fields.Boolean(required=False),
number_of_products = fields.Integer()
))
@ns.route('')
class CrudApi(Resource):
@failsafe(fail_code=500)
@ns.doc(description="Get list of all saved catalogs")
@ns.param('limit', 'Limit the number of results')
@ns.param('skip', 'Skip the first n results')
@ns.param('search_value', 'Search for a specific value')
@ns.response(200, description="List of all saved catalogs", model=[catalog_model_list])
@ns.response(406, description="Access for this catalog is forbidden", model=None)
@ns.response(500, description="Could not get catalogs. Something went wrong.", model=None)
def get(self):
return get_all_catalogs(request)
# etc
```
The function `get_all_catalogs` contains the logic required
to process the flask request received or other parameters/path parameters provided.
Function `get_all_catalogs` will return a response and a status_code.
# Test creation
Create all needed `test_*.py` files in the `tests` folder next to the `app`.
Ex:
```bash
.
├── __init__.py
├── test_add_catalogs.py
└── test_merge_catalogs.py
```
Ideally you should test one feature per test case. For example up we have a test suite for adding catalogs
and one separate for merging catalogs.
Example of a test:
```py
import pytest
from unittest import TestCase
from flask.testing import Client
from licenseware.mongodata import collection
from licenseware.common.constants import envs
from licenseware.utils.logger import log
from main import app # this is used to get the flask test client and initialize app and workers
from app.common.constants import collections
from licenseware.test_helpers.auth import AuthHelper # this is useful to get authorization headers
# this will be used on all tests (in case of endpoint testing)
@pytest.fixture
def c():
with app.test_client() as client:
yield client
@pytest.fixture
def teardown():
clean_db()
# some utilities functions specific to this test
def increase_quota():
with collection(envs.MONGO_COLLECTION_UTILIZATION_NAME) as col:
res = col.find_one(filter={'uploader_id': 'catalogs_quota'})
if res is None: return
if res['monthly_quota'] <= 1:
col.update_one(
filter={'uploader_id': 'catalogs_quota'},
update={"$inc": {'monthly_quota': 9999}}
)
# asserts can be used also instead of unittest.TestCase assertions
T = TestCase()
# some global variables used
main_admin = "alin+mainadmin@licenseware.io"
catalog1 = "Catalog 1"
catalog2 = "Catalog 2"
catalog3 = "Catalog 3"
catalog4 = "Catalog 4"
cloned_lware_catalog = "Cloned Licenseware Catalog"
user_email1 = "alin+user1@licenseware.io"
user_email2 = "alin+user2@licenseware.io"
user_email3 = "alin+user3@licenseware.io"
user_email4 = "alin+user4@licenseware.io"
admin_email1 = "alin+admin1@licenseware.io"
admin_email2 = "alin+admin2@licenseware.io"
admin_email3 = "alin+admin3@licenseware.io"
admin_email4 = "alin+admin4@licenseware.io"
# tox tests/test_catalogs.py
# tox tests/test_catalogs.py::test_add_new_catalogs
def test_add_new_catalogs(c: Client):
auth_headers = AuthHelper(main_admin).get_auth_headers()
response = c.get(
'/ssc/activate_app',
headers=auth_headers
)
T.assertEqual(response.status_code, 200)
increase_quota()
catalogs = [
{
"catalog_name": catalog1,
"tags": ["IT"],
"admins": [main_admin, admin_email1],
"users": [user_email1]
},
{
"catalog_name": catalog2,
"tags": ["IT"],
"admins": [main_admin, admin_email2],
"users": [user_email2]
},
{
"catalog_name": catalog3,
"tags": ["IT"],
"admins": [main_admin, admin_email3],
"users": [user_email3]
}
]
for payload in catalogs:
response = c.post(
'/ssc/catalogs',
json=payload,
headers=auth_headers
)
log.warning(response.data)
# log.warning(response.get_json())
T.assertEqual(response.status_code, 201)
def test_something_else(c: Client):
result = get_somthing_from_db()
assert admin_email3 in result
# etc
```
If tests are dependent on each other (data from a test is needed on another test)
you can either place all tests in one file (big file hard to manage though) or
get add the seed data to db in a fixture function (recomended)
"""
from .auth import AuthHelper
from .flask_request import get_flask_request, get_request_files
Sub-modules
licenseware.test_helpers.auth
-
The
AuthHelper
class can be used to obtain authorization headers from auth service + other interactions with auth … licenseware.test_helpers.flask_request
licenseware.test_helpers.order_tests