diff --git a/.gitignore b/.gitignore index e4afd65..aa07da5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ .vscode/ .idea/ docs/build/* -*egg-info \ No newline at end of file +*egg-info +.coverage +htmlcov/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index f5c88cb..36a3fd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,22 @@ python: cache: directories: - node_modules +install: + - pip install -r requirements/requirements.txt + - pip install -r tests/requirements.txt + - pip install codecov +env: + global: + - DEV_MNGR_CRYPTO_PASS="kamehameHA" + - DEV_MNGR_CRYPTO_IV=1234567890123456 + - DEV_MNGR_CRYPTO_SALT="shuriken" script: - 'docker build -t dojot/device-manager .' - 'docker build -t dredd/test -f tests/Dockerfile .' - 'docker-compose -p test -f tests/docker-compose.yaml up -d kafka data-broker postgres device-manager postgres-users' - 'docker-compose -p test -f tests/docker-compose.yaml run --rm test-runner' + - python3 -m pytest --cov-report=html --cov=DeviceManager tests/ after_success: + - codecov - travis/publish.sh - travis/deploy-gh-pages.sh diff --git a/DeviceManager/BackendHandler.py b/DeviceManager/BackendHandler.py index 748095f..c58dc05 100644 --- a/DeviceManager/BackendHandler.py +++ b/DeviceManager/BackendHandler.py @@ -6,7 +6,7 @@ import traceback import requests from DeviceManager.utils import HTTPRequestError -from DeviceManager.KafkaNotifier import send_notification, DeviceEvent +from DeviceManager.KafkaNotifier import KafkaNotifier, DeviceEvent import logging from datetime import datetime import time @@ -53,10 +53,11 @@ def update(self, device): """ raise NotImplementedError('Abstract method called') + class KafkaHandler: def __init__(self): - pass + self.kafkaNotifier = KafkaNotifier() def create(self, device, meta): """ @@ -64,7 +65,7 @@ def create(self, device, meta): """ LOGGER.info(f" Publishing create event to Kafka") - send_notification(DeviceEvent.CREATE, device, meta) + self.kafkaNotifier.send_notification(DeviceEvent.CREATE, device, meta) def remove(self, device, meta): """ @@ -72,7 +73,7 @@ def remove(self, device, meta): """ LOGGER.info(f" Publishing remove event to Kafka") - send_notification(DeviceEvent.REMOVE, device, meta) + self.kafkaNotifier.send_notification(DeviceEvent.REMOVE, device, meta) def update(self, device, meta): """ @@ -80,11 +81,33 @@ def update(self, device, meta): """ LOGGER.info(f" Publishing create update to Kafka") - send_notification(DeviceEvent.UPDATE, device, meta) + self.kafkaNotifier.send_notification(DeviceEvent.UPDATE, device, meta) def configure(self, device, meta): """ Publishes event to kafka broker, notifying device configuration """ LOGGER.info(f" Publishing configure event to Kafka") - send_notification(DeviceEvent.CONFIGURE, device, meta) + self.kafkaNotifier.send_notification(DeviceEvent.CONFIGURE, device, meta) + +class KafkaInstanceHandler: + + kafkaNotifier = None + + def __init__(self): + pass + + def getInstance(self, kafka_instance): + """ + Instantiates a connection with Kafka, was created because + previously the connection was being created in KafkaNotifier + once time every import. + + :param kafka_instance: An instance of KafkaHandler. + :return An instance of KafkaHandler used to notify + """ + + if kafka_instance is None: + self.kafkaNotifier = KafkaHandler() + + return self.kafkaNotifier diff --git a/DeviceManager/DeviceHandler.py b/DeviceManager/DeviceHandler.py index ea52693..df79196 100644 --- a/DeviceManager/DeviceHandler.py +++ b/DeviceManager/DeviceHandler.py @@ -16,7 +16,7 @@ from DeviceManager.utils import create_id, get_pagination, format_response from DeviceManager.utils import HTTPRequestError from DeviceManager.conf import CONFIG -from DeviceManager.BackendHandler import KafkaHandler +from DeviceManager.BackendHandler import KafkaHandler, KafkaInstanceHandler from DeviceManager.DatabaseHandler import db from DeviceManager.DatabaseModels import assert_device_exists, assert_template_exists @@ -26,7 +26,7 @@ from DeviceManager.SerializationModels import device_list_schema, device_schema, ValidationError from DeviceManager.SerializationModels import attr_list_schema from DeviceManager.SerializationModels import parse_payload, load_attrs, validate_repeated_attrs -from DeviceManager.TenancyManager import init_tenant_context, init_tenant_context2 +from DeviceManager.TenancyManager import init_tenant_context from DeviceManager.app import app from DeviceManager.Logger import Log @@ -84,7 +84,7 @@ def serialize_full_device(orm_device, tenant, sensitive_data=False): if attr['id'] == psk_data.attr_id: dec = decrypt(psk_data.psk) attr['static_value'] = dec.decode('ascii') - + return data def find_template(template_list, id): @@ -183,6 +183,8 @@ def find_attribute(orm_device, attr_name, attr_type): class DeviceHandler(object): + kafka = KafkaInstanceHandler() + def __init__(self): pass @@ -220,15 +222,17 @@ def generate_device_id(): raise HTTPRequestError(500, "Failed to generate unique device_id") @staticmethod - def list_ids(req): + def list_ids(token): """ Fetches the list of known device ids. + :param token: The authorization token (JWT). + :return A JSON containing a list of devices ids :rtype JSON :raises HTTPRequestError: If no authorization token was provided (no tenant was informed) """ - init_tenant_context(req, db) + init_tenant_context(token, db) data = [] LOGGER.debug(f" Fetching list with known devices") @@ -237,12 +241,14 @@ def list_ids(req): return data @staticmethod - def get_devices(req, sensitive_data=False): + def get_devices(token, params, sensitive_data=False): """ Fetches known devices, potentially limited by a given value. Ordering might be user-configurable too. - :param req: The received HTTP request, as created by Flask. + :param token: The authorization token (JWT). + :param params: Parameters received from request (page_number, per_page, + sortBy, attr, attr_type, label, template, idsOnly) :param sensitive_data: Informs if sensitive data like keys should be returned :return A JSON containing pagination information and the device list @@ -250,20 +256,19 @@ def get_devices(req, sensitive_data=False): :raises HTTPRequestError: If no authorization token was provided (no tenant was informed) """ + tenant = init_tenant_context(token, db) - tenant = init_tenant_context(req, db) - - page_number, per_page = get_pagination(req) - pagination = {'page': page_number, 'per_page': per_page, 'error_out': False} + pagination = {'page': params.get('page_number'), 'per_page': params.get('per_page'), 'error_out': False} SORT_CRITERION = { 'label': Device.label, None: Device.id } - sortBy = SORT_CRITERION.get(req.args.get('sortBy', None)) + sortBy = SORT_CRITERION.get(params.get('sortBy')) attr_filter = [] - query = req.args.getlist('attr') + query = params.get('attr') + for attr_label_item in query: parsed = re.search('^(.+){1}=(.+){1}$', attr_label_item) attr_label = [] @@ -272,17 +277,17 @@ def get_devices(req, sensitive_data=False): attr_label.append(text("coalesce(overrides.static_value, attrs.static_value)=:static_value ").bindparams(static_value=parsed.group(2))) attr_filter.append(and_(*attr_label)) - query = req.args.getlist('attr_type') + query = params.get('attr_type') for attr_type_item in query: attr_filter.append(DeviceAttr.value_type == attr_type_item) label_filter = [] - target_label = req.args.get('label', None) + target_label = params.get('label') if target_label: label_filter.append(Device.label.like("%{}%".format(target_label))) template_filter = [] - target_template = req.args.get('template', None) + target_template = params.get('template') if target_template: template_filter.append(DeviceTemplateMap.template_id == target_template) @@ -328,8 +333,8 @@ def get_devices(req, sensitive_data=False): page = db.session.query(Device).order_by(sortBy).paginate(**pagination) devices = [] - - if req.args.get('idsOnly', 'false').lower() in ['true', '1', '']: + + if params.get('idsOnly').lower() in ['true', '1', '']: return DeviceHandler.get_only_ids(page) for d in page.items: @@ -345,6 +350,7 @@ def get_devices(req, sensitive_data=False): }, 'devices': devices } + return result @staticmethod @@ -360,11 +366,11 @@ def get_only_ids(page): return device_id @staticmethod - def get_device(req, device_id, sensitive_data=False): + def get_device(token, device_id, sensitive_data=False): """ Fetches a single device. - :param req: The received HTTP request, as created by Flask. + :param token: The authorization token (JWT). :param device_id: The requested device. :param sensitive_data: Informs if sensitive data like keys should be returned @@ -376,16 +382,19 @@ def get_device(req, device_id, sensitive_data=False): database. """ - tenant = init_tenant_context(req, db) + tenant = init_tenant_context(token, db) orm_device = assert_device_exists(device_id) return serialize_full_device(orm_device, tenant, sensitive_data) - @staticmethod - def create_device(req): + @classmethod + def create_device(cls, params, token): """ Creates and configures the given device. - :param req: The received HTTP request, as created by Flask. + :param params: Parameters received from request (count, verbose, + content_type, data) + :param token: The authorization token (JWT). + :return The created device or a list of device summary. This depends on which value the verbose (/?verbose=true) has - if true, only one device can be created ("count" argument can't be used or - at least - it must @@ -400,31 +409,28 @@ def create_device(req): and it is not an integer. """ - - tenant = init_tenant_context(req, db) + tenant = init_tenant_context(token, db) try: - count = int(req.args.get('count', '1')) + count = int(params.get('count')) except ValueError as e: LOGGER.error(e) raise HTTPRequestError(400, "If provided, count must be integer") c_length = len(str(count)) - verbose = req.args.get('verbose', 'false') in ['true', '1', 'True'] + verbose = params.get('verbose') in ['true', '1', 'True'] if verbose and count != 1: raise HTTPRequestError( 400, "Verbose can only be used for single device creation") devices = [] - - # Handlers - kafka_handler = KafkaHandler() - full_device = None orm_devices = [] try: for i in range(0, count): - device_data, json_payload = parse_payload(req, device_schema) + content_type = params.get('content_type') + data_request = params.get('data') + device_data, json_payload = parse_payload(content_type, data_request, device_schema) validate_repeated_attrs(json_payload) device_data['id'] = DeviceHandler.generate_device_id() device_data['label'] = DeviceHandler.indexed_label(count, c_length, device_data['label'], i) @@ -452,7 +458,8 @@ def create_device(req): full_device = serialize_full_device(orm_device, tenant) # Updating handlers - kafka_handler.create(full_device, meta={"service": tenant}) + kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier) + kafka_handler_instance.create(full_device, meta={"service": tenant}) if verbose: result = { @@ -466,13 +473,13 @@ def create_device(req): } return result - @staticmethod - def delete_device(req, device_id): + @classmethod + def delete_device(cls, device_id, token): """ Deletes a single device. - :param req: The received HTTP request, as created by Flask. :param device_id: The device to be removed. + :param token: The authorization token (JWT). :return The removed device. :rtype JSON :raises HTTPRequestError: If no authorization token was provided (no @@ -481,12 +488,12 @@ def delete_device(req, device_id): database. """ - tenant = init_tenant_context(req, db) + tenant = init_tenant_context(token, db) orm_device = assert_device_exists(device_id) data = serialize_full_device(orm_device, tenant) - kafka_handler = KafkaHandler() - kafka_handler.remove(data, meta={"service": tenant}) + kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier) + kafka_handler_instance.remove(data, meta={"service": tenant}) db.session.delete(orm_device) db.session.commit() @@ -495,15 +502,15 @@ def delete_device(req, device_id): return results @staticmethod - def delete_all_devices(req): + def delete_all_devices(token): """ Deletes all devices. - :param req: The received HTTP request, as created by Flask. + :param token: The authorization token (JWT). :raises HTTPRequestError: If this device could not be found in database. """ - tenant = init_tenant_context(req, db) + tenant = init_tenant_context(token, db) json_devices = [] devices = db.session.query(Device) @@ -520,13 +527,15 @@ def delete_all_devices(req): return results - @staticmethod - def update_device(req, device_id): + @classmethod + def update_device(cls, params, device_id, token): """ Updated the information about a particular device - :param req: The received HTTP request, as created by Flask. + :param params: Parameters received from request (content_type, data) + as created by Flask :param device_id: The device to be updated. + :param token: The authorization token (JWT). :return The updated device. :rtype JSON :raises HTTPRequestError: If no authorization token was provided (no @@ -535,10 +544,13 @@ def update_device(req, device_id): database. """ try: - device_data, json_payload = parse_payload(req, device_schema) + content_type = params.get('content_type') + data_request = params.get('data') + + device_data, json_payload = parse_payload(content_type, data_request, device_schema) validate_repeated_attrs(json_payload) - tenant = init_tenant_context(req, db) + tenant = init_tenant_context(token, db) old_orm_device = assert_device_exists(device_id) db.session.delete(old_orm_device) db.session.flush() @@ -562,8 +574,8 @@ def update_device(req, device_id): full_device = serialize_full_device(updated_orm_device, tenant) - kafka_handler = KafkaHandler() - kafka_handler.update(full_device, meta={"service": tenant}) + kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier) + kafka_handler_instance.update(full_device, meta={"service": tenant}) result = { 'message': 'device updated', @@ -571,13 +583,15 @@ def update_device(req, device_id): } return result - @staticmethod - def configure_device(req, device_id): + @classmethod + def configure_device(cls, params, device_id, token): """ Send actuation commands to the device - :param req: The received HTTP request, as created by Flask. + :param params: Parameters received from request (data) + as created by Flask :param device_id: The device which should receive the actuation message + :param token: The authorization token (JWT). :return A status on whether the message was sent to the device or not. Note that this is not a guarantee that the device actually performed what was requested. @@ -587,10 +601,9 @@ def configure_device(req, device_id): meta = { 'service': '' } - kafka_handler = KafkaHandler() invalid_attrs = [] - meta['service'] = init_tenant_context(req, db) + meta['service'] = init_tenant_context(token, db) meta['timestamp'] = int(time.time() * 1000) @@ -598,7 +611,8 @@ def configure_device(req, device_id): full_device = serialize_full_device(orm_device, meta['service']) LOGGER.debug(f" Full device: {json.dumps(full_device)}") - payload = json.loads(req.data) + data = params.get('data') + payload = json.loads(data) LOGGER.debug(f' Parsed request payload: {json.dumps(payload)}') payload['id'] = orm_device.id @@ -609,7 +623,8 @@ def configure_device(req, device_id): if not invalid_attrs: LOGGER.debug(f' Sending configuration message through Kafka.') - kafka_handler.configure(payload, meta) + kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier) + kafka_handler_instance.configure(payload, meta) LOGGER.debug(f' Configuration sent.') result = {f' status': 'configuration sent to device'} else: @@ -623,11 +638,11 @@ def configure_device(req, device_id): return result @staticmethod - def add_template_to_device(req, device_id, template_id): + def add_template_to_device(token, device_id, template_id): """ Associates given template with device - :param req: The received HTTP request, as created by Flask. + :param token: The authorization token (JWT). :param device_id: The device which should be updated :param template_id: The template to be added to this device. :raises HTTPRequestError: If no authorization token was provided (no @@ -638,7 +653,7 @@ def add_template_to_device(req, device_id, template_id): structure for that device. :rtype JSON """ - tenant = init_tenant_context(req, db) + tenant = init_tenant_context(token, db) orm_device = assert_device_exists(device_id) orm_template = assert_template_exists(template_id) @@ -657,11 +672,11 @@ def add_template_to_device(req, device_id, template_id): return result @staticmethod - def remove_template_from_device(req, device_id, template_id): + def remove_template_from_device(token, device_id, template_id): """ Disassociates given template with device - :param req: The received HTTP request, as created by Flask. + :param token: The authorization token (JWT). :param device_id: The device which should be updated :param template_id: The template to be removed from this device. :raises HTTPRequestError: If no authorization token was provided (no @@ -672,7 +687,7 @@ def remove_template_from_device(req, device_id, template_id): structure for that device. :rtype JSON """ - tenant = init_tenant_context(req, db) + tenant = init_tenant_context(token, db) updated_device = assert_device_exists(device_id) relation = assert_device_relation_exists(device_id, template_id) @@ -689,12 +704,14 @@ def remove_template_from_device(req, device_id, template_id): return result @staticmethod - def get_by_template(req, template_id): + def get_by_template(token, params, template_id): """ Return a list of devices that have a particular template associated to it - :param req: The received HTTP request, as created by Flask. + :param token: The authorization token (JWT). + :param params: Parameters received from request (page_number, per_page) + as created by Flask :param template_id: The template to be considered :raises HTTPRequestError: If no authorization token was provided (no tenant was informed) @@ -703,13 +720,13 @@ def get_by_template(req, template_id): :return A list of devices that are associated to the selected template. :rtype JSON """ - tenant = init_tenant_context(req, db) - page_number, per_page = get_pagination(req) + tenant = init_tenant_context(token, db) page = ( db.session.query(Device) .join(DeviceTemplateMap) .filter_by(template_id=template_id) - .paginate(page=page_number, per_page=per_page, error_out=False) + .paginate(page=params.get('page_number'), + per_page=params.get('per_page'), error_out=False) ) devices = [] for d in page.items: @@ -726,8 +743,8 @@ def get_by_template(req, template_id): } return result - @staticmethod - def gen_psk(token, device_id, key_length, target_attributes=None): + @classmethod + def gen_psk(cls, token, device_id, key_length, target_attributes=None): """ Generates pre shared keys to a specifics device @@ -744,7 +761,7 @@ def gen_psk(token, device_id, key_length, target_attributes=None): database. """ - tenant = init_tenant_context2(token, db) + tenant = init_tenant_context(token, db) device_orm = assert_device_exists(device_id, db.session) if not device_orm: @@ -813,13 +830,13 @@ def gen_psk(token, device_id, key_length, target_attributes=None): db.session.commit() # send an update message on kafka - kafka_handler = KafkaHandler() - kafka_handler.update(device, meta={"service": tenant}) + kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier) + kafka_handler_instance.update(device, meta={"service": tenant}) return result - @staticmethod - def copy_psk(token, src_device_id, src_attr, dest_device_id, dest_attr): + @classmethod + def copy_psk(cls, token, src_device_id, src_attr, dest_device_id, dest_attr): """ Copies a pre shared key from a device attribute to another @@ -834,7 +851,7 @@ def copy_psk(token, src_device_id, src_attr, dest_device_id, dest_attr): todo list the others exceptions """ - tenant = init_tenant_context2(token, db) + tenant = init_tenant_context(token, db) src_device_orm = assert_device_exists(src_device_id, db.session) if not src_device_orm: @@ -901,10 +918,8 @@ def copy_psk(token, src_device_id, src_attr, dest_device_id, dest_attr): dest_attr_ref['static_value'] = src_attr_ref['static_value'] # send an update message on kafka - kafka_handler = KafkaHandler() - kafka_handler.update(dest_device, meta={"service": tenant}) - - return None + kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier) + kafka_handler_instance.update(dest_device, meta={"service": tenant}) @device.route('/device', methods=['GET']) @@ -917,7 +932,24 @@ def flask_get_devices(): headers. """ try: - result = DeviceHandler.get_devices(request) + # retrieve the authorization token + token = retrieve_auth_token(request) + + # retrieve pagination + page_number, per_page = get_pagination(request) + + params = { + 'page_number': page_number, + 'per_page': per_page, + 'sortBy': request.args.get('sortBy', None), + 'attr': request.args.getlist('attr'), + 'attr_type': request.args.getlist('attr_type'), + 'label': request.args.get('label', None), + 'template': request.args.get('template', None), + 'idsOnly': request.args.get('idsOnly', 'false'), + } + + result = DeviceHandler.get_devices(token, params) LOGGER.info(f' Getting latest added device(s).') return make_response(jsonify(result), 200) @@ -938,7 +970,17 @@ def flask_create_device(): headers. """ try: - result = DeviceHandler.create_device(request) + # retrieve the authorization token + token = retrieve_auth_token(request) + + params = { + 'count': request.args.get('count', '1'), + 'verbose': request.args.get('verbose', 'false'), + 'content_type': request.headers.get('Content-Type'), + 'data': request.data + } + + result = DeviceHandler.create_device(params, token) devices = result.get('devices') deviceId = devices[0].get('id') LOGGER.info(f' Creating a new device with id {deviceId}.') @@ -959,7 +1001,10 @@ def flask_delete_all_device(): headers. """ try: - result = DeviceHandler.delete_all_devices(request) + # retrieve the authorization token + token = retrieve_auth_token(request) + + result = DeviceHandler.delete_all_devices(token) LOGGER.info('Deleting all devices.') return make_response(jsonify(result), 200) @@ -971,7 +1016,10 @@ def flask_delete_all_device(): @device.route('/device/', methods=['GET']) def flask_get_device(device_id): try: - result = DeviceHandler.get_device(request, device_id) + # retrieve the authorization token + token = retrieve_auth_token(request) + + result = DeviceHandler.get_device(token, device_id) LOGGER.info(f' Getting the device with id {device_id}.') return make_response(jsonify(result), 200) except HTTPRequestError as e: @@ -984,8 +1032,11 @@ def flask_get_device(device_id): @device.route('/device/', methods=['DELETE']) def flask_remove_device(device_id): try: + # retrieve the authorization token + token = retrieve_auth_token(request) + LOGGER.info(f' Removing the device with id {device_id}.') - results = DeviceHandler.delete_device(request, device_id) + results = DeviceHandler.delete_device(device_id, token) return make_response(jsonify(results), 200) except HTTPRequestError as e: LOGGER.error(f' {e.message} - {e.error_code}.') @@ -998,8 +1049,16 @@ def flask_remove_device(device_id): @device.route('/device/', methods=['PUT']) def flask_update_device(device_id): try: + # retrieve the authorization token + token = retrieve_auth_token(request) + + params = { + 'content_type': request.headers.get('Content-Type'), + 'data': request.data + } + LOGGER.info(f' Updating the device with id {device_id}.') - results = DeviceHandler.update_device(request, device_id) + results = DeviceHandler.update_device(params, device_id, token) return make_response(jsonify(results), 200) except HTTPRequestError as e: LOGGER.error(f' {e.message} - {e.error_code}.') @@ -1015,8 +1074,13 @@ def flask_configure_device(device_id): Send actuation commands to the device """ try: + # retrieve the authorization token + token = retrieve_auth_token(request) + + params = {'data': request.data} + LOGGER.info(f' Actuating in the device with id {device_id}.') - result = DeviceHandler.configure_device(request, device_id) + result = DeviceHandler.configure_device(params, device_id, token) return make_response(jsonify(result), 200) except HTTPRequestError as error: @@ -1031,9 +1095,12 @@ def flask_configure_device(device_id): @device.route('/device//template/', methods=['POST']) def flask_add_template_to_device(device_id, template_id): try: + # retrieve the authorization token + token = retrieve_auth_token(request) + LOGGER.info(f' Adding template with id {template_id} in the device {device_id}.') result = DeviceHandler.add_template_to_device( - request, device_id, template_id) + token, device_id, template_id) return make_response(jsonify(result), 200) except HTTPRequestError as e: LOGGER.error(f' {e.message} - {e.error_code}.') @@ -1046,9 +1113,12 @@ def flask_add_template_to_device(device_id, template_id): @device.route('/device//template/', methods=['DELETE']) def flask_remove_template_from_device(device_id, template_id): try: + # retrieve the authorization token + token = retrieve_auth_token(request) + LOGGER.info(f' Removing template with id {template_id} in the device {device_id}.') result = DeviceHandler.remove_template_from_device( - request, device_id, template_id) + token, device_id, template_id) return make_response(jsonify(result), 200) except HTTPRequestError as e: LOGGER.error(f' {e.message} - {e.error_code}.') @@ -1061,8 +1131,19 @@ def flask_remove_template_from_device(device_id, template_id): @device.route('/device/template/', methods=['GET']) def flask_get_by_template(template_id): try: + # retrieve the authorization token + token = retrieve_auth_token(request) + + # retrieve pagination + page_number, per_page = get_pagination(request) + + params = { + 'page_number': page_number, + 'per_page': per_page, + } + LOGGER.info(f' Getting devices with template id {template_id}.') - result = DeviceHandler.get_by_template(request, template_id) + result = DeviceHandler.get_by_template(token, params, template_id) return make_response(jsonify(result), 200) except HTTPRequestError as e: LOGGER.error(f' {e.message} - {e.error_code}.') @@ -1143,7 +1224,24 @@ def flask_internal_get_devices(): headers. """ try: - result = DeviceHandler.get_devices(request, True) + # retrieve the authorization token + token = retrieve_auth_token(request) + + # retrieve pagination + page_number, per_page = get_pagination(request) + + params = { + 'page_number': page_number, + 'per_page': per_page, + 'sortBy': request.args.get('sortBy', None), + 'attr': request.args.getlist('attr'), + 'attr_type': request.args.getlist('attr_type'), + 'label': request.args.get('label', None), + 'template': request.args.get('template', None), + 'idsOnly': request.args.get('idsOnly', 'false'), + } + + result = DeviceHandler.get_devices(token, params, True) LOGGER.info(f' Getting known internal devices.') return make_response(jsonify(result), 200) @@ -1158,7 +1256,10 @@ def flask_internal_get_devices(): @device.route('/internal/device/', methods=['GET']) def flask_internal_get_device(device_id): try: - result = DeviceHandler.get_device(request, device_id, True) + # retrieve the authorization token + token = retrieve_auth_token(request) + + result = DeviceHandler.get_device(token, device_id, True) LOGGER.info(f'Get known device with id: {device_id}.') return make_response(jsonify(result), 200) except HTTPRequestError as e: diff --git a/DeviceManager/ImportHandler.py b/DeviceManager/ImportHandler.py index d36310b..96168eb 100644 --- a/DeviceManager/ImportHandler.py +++ b/DeviceManager/ImportHandler.py @@ -8,9 +8,9 @@ from DeviceManager.app import app from DeviceManager.Logger import Log -from DeviceManager.utils import format_response, HTTPRequestError +from DeviceManager.utils import format_response, HTTPRequestError, retrieve_auth_token from DeviceManager.conf import CONFIG -from DeviceManager.BackendHandler import KafkaHandler +from DeviceManager.BackendHandler import KafkaHandler, KafkaInstanceHandler from DeviceManager.DatabaseHandler import db from DeviceManager.DatabaseModels import DeviceTemplate, Device, DeviceAttr, DeviceOverride @@ -26,10 +26,11 @@ class ImportHandler: + kafka = KafkaInstanceHandler() + def __init__(self): pass - def drop_sequences(): db.session.execute("DROP SEQUENCE template_id") db.session.execute("DROP SEQUENCE attr_id") @@ -60,10 +61,10 @@ def restore_sequences(): ImportHandler.restore_attr_sequence() LOGGER.info(f" Restored sequences") - def notifies_deletion_to_kafka(device, tenant): + def notifies_deletion_to_kafka(cls, device, tenant): data = serialize_full_device(device, tenant) - kafka_handler = KafkaHandler() - kafka_handler.remove(data, meta={"service": tenant}) + kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier) + kafka_handler_instance.remove(data, meta={"service": tenant}) def delete_records(tenant): overrides = db.session.query(DeviceOverride) @@ -133,19 +134,21 @@ def save_devices(json_data, json_payload, saved_templates): return saved_devices - def notifies_creation_to_kafka(saved_devices, tenant): - kafka_handler = KafkaHandler() + def notifies_creation_to_kafka(cls, saved_devices, tenant): + kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier) for orm_device in saved_devices: full_device = serialize_full_device(orm_device, tenant) - kafka_handler.create(full_device, meta={"service": tenant}) + kafka_handler_instance.create(full_device, meta={"service": tenant}) @staticmethod - def import_data(req): + def import_data(data, token, content_type): """ Import data. - :param req: The received HTTP request, as created by Flask. + :param data: The received data HTTP request, as created by Flask. + :param token: The authorization token (JWT). + :param content_type: The content_type of request (application/json) :return The status message. :raises HTTPRequestError: If no authorization token was provided (no tenant was informed) @@ -158,17 +161,17 @@ def import_data(req): saved_devices = [] try: - tenant = init_tenant_context(req, db) + tenant = init_tenant_context(token, db) ImportHandler.clear_db_config(tenant) - original_req_data = copy.copy(req.data) + original_req_data = copy.copy(data) original_payload = json.loads(original_req_data) - req.data = ImportHandler.replace_ids_by_import_ids(req.data) + data = ImportHandler.replace_ids_by_import_ids(data) - json_data, json_payload = parse_payload(req, import_schema) + json_data, json_payload = parse_payload(content_type, data, import_schema) saved_templates = ImportHandler.save_templates(json_data, json_payload) @@ -176,7 +179,7 @@ def import_data(req): ImportHandler.restore_db_config() - ImportHandler.notifies_creation_to_kafka(saved_devices, tenant) + ImportHandler().notifies_creation_to_kafka(saved_devices, tenant) db.session.commit() @@ -202,7 +205,14 @@ def flask_import_data(): try: LOGGER.info(f" Starting importing data...") - result = ImportHandler.import_data(request) + # retrieve the authorization token + token = retrieve_auth_token(request) + + # retrieve header and body of request + content_type = request.headers.get('Content-Type') + data = request.data + + result = ImportHandler.import_data(data, token, content_type) LOGGER.info(f" Imported data!") diff --git a/DeviceManager/KafkaNotifier.py b/DeviceManager/KafkaNotifier.py index fe418e1..6a2c288 100644 --- a/DeviceManager/KafkaNotifier.py +++ b/DeviceManager/KafkaNotifier.py @@ -12,9 +12,9 @@ import time - LOGGER = Log().color_log() + class DeviceEvent: CREATE = "create" UPDATE = "update" @@ -37,60 +37,64 @@ def to_json(self): return {"event": self.event, "data": self.data, "meta": self.meta} -kafka_address = CONFIG.kafka_host + ':' + CONFIG.kafka_port -kf_prod = KafkaProducer(value_serializer=lambda v: json.dumps(v).encode('utf-8'), - bootstrap_servers=kafka_address) - -# Maps services to their managed topics -topic_map = {} - -def get_topic(service, subject): - if service in topic_map.keys(): - if subject in topic_map[service].keys(): - return topic_map[service][subject] - - target = "{}/topic/{}".format(CONFIG.data_broker, subject) - userinfo = { - "username": "device-manager", - "service": service - } - - jwt = "{}.{}.{}".format(base64.b64encode("model".encode()).decode(), - base64.b64encode(json.dumps(userinfo).encode()).decode(), - base64.b64encode("signature".encode()).decode()) - - response = requests.get(target, headers={"authorization": jwt}) - if 200 <= response.status_code < 300: - payload = response.json() - if topic_map.get(service, None) is None: - topic_map[service] = {} - topic_map[service][subject] = payload['topic'] - return payload['topic'] - return None - - -def send_notification(event, device, meta): - # TODO What if Kafka is not yet up? - - full_msg = NotificationMessage(event, device, meta) - try: - topic = get_topic(meta['service'], CONFIG.subject) - LOGGER.debug(f" topic for {CONFIG.subject} is {topic}") - if topic is None: - LOGGER.error(f" Failed to retrieve named topic to publish to") - - kf_prod.send(topic, full_msg.to_json()) - kf_prod.flush() - except KafkaTimeoutError: - LOGGER.error(f" Kafka timed out.") - -def send_raw(raw_data, tenant): - try: - topic = get_topic(tenant, CONFIG.subject) - if topic is None: - LOGGER.error(f" Failed to retrieve named topic to publish to") - kf_prod.send(topic, raw_data) - kf_prod.flush() - except KafkaTimeoutError: - LOGGER.error(f" Kafka timed out.") - \ No newline at end of file +class KafkaNotifier: + + def __init__(self): + self.kafka_address = CONFIG.kafka_host + ':' + CONFIG.kafka_port + self.kf_prod = None + + self.kf_prod = KafkaProducer(value_serializer=lambda v: json.dumps(v).encode('utf-8'), + bootstrap_servers=self.kafka_address) + + # Maps services to their managed topics + self.topic_map = {} + + def get_topic(self, service, subject): + if service in self.topic_map.keys(): + if subject in self.topic_map[service].keys(): + return self.topic_map[service][subject] + + target = "{}/topic/{}".format(CONFIG.data_broker, subject) + userinfo = { + "username": "device-manager", + "service": service + } + + jwt = "{}.{}.{}".format(base64.b64encode("model".encode()).decode(), + base64.b64encode(json.dumps( + userinfo).encode()).decode(), + base64.b64encode("signature".encode()).decode()) + + response = requests.get(target, headers={"authorization": jwt}) + if 200 <= response.status_code < 300: + payload = response.json() + if self.topic_map.get(service, None) is None: + self.topic_map[service] = {} + self.topic_map[service][subject] = payload['topic'] + return payload['topic'] + return None + + def send_notification(self, event, device, meta): + # TODO What if Kafka is not yet up? + + full_msg = NotificationMessage(event, device, meta) + try: + topic = self.get_topic(meta['service'], CONFIG.subject) + LOGGER.debug(f" topic for {CONFIG.subject} is {topic}") + if topic is None: + LOGGER.error(f" Failed to retrieve named topic to publish to") + + self.kf_prod.send(topic, full_msg.to_json()) + self.kf_prod.flush() + except KafkaTimeoutError: + LOGGER.error(f" Kafka timed out.") + + def send_raw(self, raw_data, tenant): + try: + topic = self.get_topic(tenant, CONFIG.subject) + if topic is None: + LOGGER.error(f" Failed to retrieve named topic to publish to") + self.kf_prod.send(topic, raw_data) + self.kf_prod.flush() + except KafkaTimeoutError: + LOGGER.error(f" Kafka timed out.") diff --git a/DeviceManager/LoggerHandler.py b/DeviceManager/LoggerHandler.py index 609843d..a3684a1 100644 --- a/DeviceManager/LoggerHandler.py +++ b/DeviceManager/LoggerHandler.py @@ -28,6 +28,8 @@ def update_log_level(level): """ LOG.update_log_level(level.upper()) + + return True @staticmethod def get_log_level(): @@ -48,7 +50,10 @@ def get_log_level(): @logger.route('/log', methods=['PUT']) def flask_update_log_level(): try: - _, json_payload = parse_payload(request, log_schema) + content_type = request.headers.get('Content-Type') + data_request = request.data + + _, json_payload = parse_payload(content_type, data_request, log_schema) LoggerHandler.update_log_level(json_payload['level']) return make_response('', 200) diff --git a/DeviceManager/SerializationModels.py b/DeviceManager/SerializationModels.py index dcc5c92..f571f2b 100644 --- a/DeviceManager/SerializationModels.py +++ b/DeviceManager/SerializationModels.py @@ -134,12 +134,11 @@ class LogSchema(Schema): log_schema = LogSchema() -def parse_payload(request, schema): +def parse_payload(content_type, data_request, schema): try: - content_type = request.headers.get('Content-Type') if (content_type is None) or (content_type != "application/json"): raise HTTPRequestError(400, "Payload must be valid JSON, and Content-Type set accordingly") - json_payload = json.loads(request.data) + json_payload = json.loads(data_request) data = schema.load(json_payload) except ValueError: raise HTTPRequestError(400, "Payload must be valid JSON, and Content-Type set accordingly") diff --git a/DeviceManager/TemplateHandler.py b/DeviceManager/TemplateHandler.py index f7ec407..8279968 100644 --- a/DeviceManager/TemplateHandler.py +++ b/DeviceManager/TemplateHandler.py @@ -13,15 +13,15 @@ from DeviceManager.SerializationModels import parse_payload, load_attrs from DeviceManager.SerializationModels import ValidationError from DeviceManager.TenancyManager import init_tenant_context -from DeviceManager.KafkaNotifier import send_raw, DeviceEvent +from DeviceManager.KafkaNotifier import KafkaNotifier, DeviceEvent from DeviceManager.app import app -from DeviceManager.utils import format_response, HTTPRequestError, get_pagination +from DeviceManager.utils import format_response, HTTPRequestError, get_pagination, retrieve_auth_token from DeviceManager.Logger import Log from datetime import datetime -from DeviceManager.BackendHandler import KafkaHandler +from DeviceManager.BackendHandler import KafkaHandler, KafkaInstanceHandler from DeviceManager.DeviceHandler import serialize_full_device import time @@ -31,9 +31,8 @@ LOGGER = Log().color_log() -def attr_format(req, result): +def attr_format(attrs_format, result): """ formats output attr list acording to user input """ - attrs_format = req.args.get('attr_format', 'both') def remove(d,k): try: @@ -64,18 +63,23 @@ def paginate(query, page, per_page=20, error_out=False): return Pagination(query, page, per_page, total, items) -class TemplateHandler: +class TemplateHandler(): + + kafka = KafkaInstanceHandler() def __init__(self): pass @staticmethod - def get_templates(req): + def get_templates(params, token): """ Fetches known templates, potentially limited by a given value. Ordering might be user-configurable too. - :param req: The received HTTP request, as created by Flask. + :param params: Parameters received from request (page_number, per_page, + sort_by, attr, attr_type, label, attrs_format) + as created by Flask + :param token: The authorization token (JWT). :return A JSON containing pagination information and the template list :rtype JSON :raises HTTPRequestError: If no authorization token was provided (no @@ -83,16 +87,16 @@ def get_templates(req): """ LOGGER.debug(f"Retrieving templates.") LOGGER.debug(f"Initializing tenant context...") - init_tenant_context(req, db) + init_tenant_context(token, db) LOGGER.debug(f"... tenant context initialized.") - page_number, per_page = get_pagination(req) - pagination = {'page': page_number, 'per_page': per_page, 'error_out': False} + pagination = {'page': params.get('page_number'), 'per_page': params.get('per_page'), 'error_out': False} LOGGER.debug(f"Pagination configuration is {pagination}") parsed_query = [] - query = req.args.getlist('attr') + query = params.get('attr') + for attr in query: LOGGER.debug(f"Analyzing query parameter: {attr}...") parsed = re.search('^(.+){1}=(.+){1}$', attr) @@ -100,11 +104,13 @@ def get_templates(req): parsed_query.append(DeviceAttr.static_value == parsed.group(2)) LOGGER.debug("... query parameter was added to filter list.") - query = req.args.getlist('attr_type') + query = params.get('attr_type') + for attr_type_item in query: parsed_query.append(DeviceAttr.value_type == attr_type_item) - target_label = req.args.get('label', None) + target_label = params.get('label') + if target_label: LOGGER.debug(f"Adding label filter to query...") parsed_query.append(DeviceTemplate.label.like("%{}%".format(target_label))) @@ -114,7 +120,8 @@ def get_templates(req): 'label': DeviceTemplate.label, None: None } - sortBy = SORT_CRITERION.get(req.args.get('sortBy', None), None) + sortBy = SORT_CRITERION.get(params.get('sortBy'), None) + LOGGER.debug(f"Sortby filter is {sortBy}") if parsed_query: LOGGER.debug(f" Filtering template by {parsed_query}") @@ -137,7 +144,7 @@ def get_templates(req): templates = [] for template in page.items: - formatted_template = attr_format(req, template_schema.dump(template)) + formatted_template = attr_format(params.get('attrs_format'), template_schema.dump(template)) LOGGER.debug(f"Adding resulting template to response...") LOGGER.debug(f"Template is: {formatted_template['label']}") templates.append(formatted_template) @@ -158,11 +165,13 @@ def get_templates(req): return result @staticmethod - def create_template(req): + def create_template(params, token): """ Creates a new template. - :param req: The received HTTP request, as created by Flask. + :param params: Parameters received from request (content_type, data) + as created by Flask + :param token: The authorization token (JWT). :return The created template. :raises HTTPRequestError: If no authorization token was provided (no tenant was informed) @@ -170,8 +179,12 @@ def create_template(req): violated. This might happen if two attributes have the same name, for instance. """ - init_tenant_context(req, db) - tpl, json_payload = parse_payload(req, template_schema) + init_tenant_context(token, db) + + content_type = params.get('content_type') + data_request = params.get('data') + tpl, json_payload = parse_payload(content_type, data_request, template_schema) + loaded_template = DeviceTemplate(**tpl) load_attrs(json_payload['attrs'], loaded_template, DeviceAttr, db) db.session.add(loaded_template) @@ -190,9 +203,10 @@ def create_template(req): return results @staticmethod - def get_template(req, template_id): + def get_template(params, template_id, token): """ Fetches a single template. + :param req: The received HTTP request, as created by Flask. :param template_id: The requested template ID. :return A Template @@ -202,22 +216,22 @@ def get_template(req, template_id): :raises HTTPRequestError: If this template could not be found in database. """ - init_tenant_context(req, db) + init_tenant_context(token, db) tpl = assert_template_exists(template_id) json_template = template_schema.dump(tpl) - attr_format(req, json_template) + attr_format(params.get('attr_format'), json_template) return json_template @staticmethod - def delete_all_templates(req): + def delete_all_templates(token): """ Deletes all templates. - :param req: The received HTTP request, as created by Flask. + :param token: The authorization token (JWT). :raises HTTPRequestError: If this template could not be found in database. """ - init_tenant_context(req, db) + init_tenant_context(token, db) json_templates = [] try: @@ -238,12 +252,12 @@ def delete_all_templates(req): return results @staticmethod - def remove_template(req, template_id): + def remove_template(template_id, token): """ Deletes a single template. - :param req: The received HTTP request, as created by Flask. :param template_id: The template to be removed. + :param token: The authorization token (JWT). :return The removed template. :rtype JSON :raises HTTPRequestError: If no authorization token was provided (no @@ -253,7 +267,7 @@ def remove_template(req, template_id): :raises HTTPRequestError: If the template is being currently used by a device. """ - init_tenant_context(req, db) + init_tenant_context(token, db) tpl = assert_template_exists(template_id) json_template = template_schema.dump(tpl) @@ -270,13 +284,15 @@ def remove_template(req, template_id): return results - @staticmethod - def update_template(req, template_id): + @classmethod + def update_template(cls, params, template_id, token): """ Updates a single template. - :param req: The received HTTP request, as created by Flask. + :param params: Parameters received from request (content_type, data) + as created by Flask :param template_id: The template to be updated. + :param token: The authorization token (JWT). :return The old version of this template (previous to the update). :rtype JSON :raises HTTPRequestError: If no authorization token was provided (no @@ -284,13 +300,16 @@ def update_template(req, template_id): :raises HTTPRequestError: If this template could not be found in database. """ - service = init_tenant_context(req, db) + service = init_tenant_context(token, db) + + content_type = params.get('content_type') + data_request = params.get('data') # find old version of the template, if any old = assert_template_exists(template_id) # parse updated version from payload - updated, json_payload = parse_payload(req, template_schema) - + updated, json_payload = parse_payload(content_type, data_request, template_schema) + LOGGER.debug(f" Current json payload: {json_payload}") old.label = updated['label'] @@ -358,10 +377,11 @@ def analyze_attrs(attrs_from_db, attrs_from_request, parentAttr=None): .all() affected_devices = [] - kafka_handler = KafkaHandler() + + kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier) for device in affected: orm_device = assert_device_exists(device.device_id) - kafka_handler.update(serialize_full_device(orm_device, service), meta={"service": service}) + kafka_handler_instance.update(serialize_full_device(orm_device, service), meta={"service": service}) affected_devices.append(device.device_id) event = { @@ -372,7 +392,7 @@ def analyze_attrs(attrs_from_db, attrs_from_request, parentAttr=None): }, "meta": {"service": service} } - send_raw(event, service) + kafka_handler_instance.kafkaNotifier.send_raw(event, service) results = { 'updated': template_schema.dump(old), @@ -384,7 +404,23 @@ def analyze_attrs(attrs_from_db, attrs_from_request, parentAttr=None): @template.route('/template', methods=['GET']) def flask_get_templates(): try: - result = TemplateHandler.get_templates(request) + # retrieve the authorization token + token = retrieve_auth_token(request) + + # retrieve pagination + page_number, per_page = get_pagination(request) + + params = { + 'page_number': page_number, + 'per_page': per_page, + 'sortBy': request.args.get('sortBy', None), + 'attr': request.args.getlist('attr'), + 'attr_type': request.args.getlist('attr_type'), + 'label': request.args.get('label', None), + 'attrs_format': request.args.get('attr_format', 'both') + } + + result = TemplateHandler.get_templates(params, token) for templates in result.get('templates'): LOGGER.info(f" Getting template with id {templates.get('id')}") @@ -406,7 +442,15 @@ def flask_get_templates(): @template.route('/template', methods=['POST']) def flask_create_template(): try: - result = TemplateHandler.create_template(request) + # retrieve the authorization token + token = retrieve_auth_token(request) + + params = { + 'content_type': request.headers.get('Content-Type'), + 'data': request.data + } + + result = TemplateHandler.create_template(params, token) LOGGER.info(f"Creating a new template") @@ -427,7 +471,10 @@ def flask_create_template(): def flask_delete_all_templates(): try: - result = TemplateHandler.delete_all_templates(request) + # retrieve the authorization token + token = retrieve_auth_token(request) + + result = TemplateHandler.delete_all_templates(token) LOGGER.info(f"deleting all templates") @@ -443,7 +490,12 @@ def flask_delete_all_templates(): @template.route('/template/', methods=['GET']) def flask_get_template(template_id): try: - result = TemplateHandler.get_template(request, template_id) + # retrieve the authorization token + token = retrieve_auth_token(request) + + params = {'attrs_format': request.args.get('attr_format', 'both')} + + result = TemplateHandler.get_template(params, template_id, token) LOGGER.info(f"Getting template with id: {template_id}") return make_response(jsonify(result), 200) except ValidationError as e: @@ -460,7 +512,10 @@ def flask_get_template(template_id): @template.route('/template/', methods=['DELETE']) def flask_remove_template(template_id): try: - result = TemplateHandler.remove_template(request, template_id) + # retrieve the authorization token + token = retrieve_auth_token(request) + + result = TemplateHandler.remove_template(template_id, token) LOGGER.info(f"Removing template with id: {template_id}") return make_response(jsonify(result), 200) except ValidationError as e: @@ -477,7 +532,15 @@ def flask_remove_template(template_id): @template.route('/template/', methods=['PUT']) def flask_update_template(template_id): try: - result = TemplateHandler.update_template(request, template_id) + # retrieve the authorization token + token = retrieve_auth_token(request) + + params = { + 'content_type': request.headers.get('Content-Type'), + 'data': request.data + } + + result = TemplateHandler.update_template(params, template_id, token) LOGGER.info(f"Updating template with id: {template_id}") return make_response(jsonify(result), 200) except ValidationError as errors: diff --git a/DeviceManager/TenancyManager.py b/DeviceManager/TenancyManager.py index fa15ea7..b11ba86 100644 --- a/DeviceManager/TenancyManager.py +++ b/DeviceManager/TenancyManager.py @@ -78,7 +78,6 @@ def init_tenant(tenant, db): .select_from(text("information_schema.schemata")) .where(text("schema_name = '%s'" % tenant))) tenant_exists = db.session.query(query).scalar() - if not tenant_exists: create_tenant(tenant, db) switch_tenant(tenant, db) @@ -107,18 +106,7 @@ def list_tenants(session): result.append(i.schema_name) return result -def init_tenant_context(request, db): - try: - token = request.headers['authorization'] - except KeyError: - raise HTTPRequestError(401, "No authorization token has been supplied") - - tenant = get_allowed_service(token) - init_tenant(tenant, db) - return tenant - - -def init_tenant_context2(token, db): +def init_tenant_context(token, db): tenant = get_allowed_service(token) init_tenant(tenant, db) diff --git a/DeviceManager/conf.py b/DeviceManager/conf.py index 1e61a86..4de714f 100644 --- a/DeviceManager/conf.py +++ b/DeviceManager/conf.py @@ -29,7 +29,7 @@ def __init__(self, # Kafka configuration self.kafka_host = os.environ.get('KAFKA_HOST', kafka_host) self.kafka_port = os.environ.get('KAFKA_PORT', kafka_port) - + # Log configuration self.log_level = os.environ.get('LOG_LEVEL', log_level) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index eb58523..2cb8e8f 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -21,3 +21,6 @@ pycrypto==2.6.1 Flask-Migrate==2.1.1 Flask-Alembic==2.0.1 colorlog==3.1.4 +pytest==4.0.0 +pytest-cov==2.6.0 +alchemy-mock==0.4.1 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/dredd-hooks/operation_hook.py b/tests/dredd-hooks/operation_hook.py index 781967f..1410d5d 100644 --- a/tests/dredd-hooks/operation_hook.py +++ b/tests/dredd-hooks/operation_hook.py @@ -57,7 +57,12 @@ def create_sample_template(): 'body': json.dumps(template) } - result = TemplateHandler.create_template(Request(req)) + params = { + 'content_type': 'application/json', + 'data': json.dumps(template) + } + + result = TemplateHandler.create_template(params, generate_token()) template_id = result['template']['id'] return template_id @@ -102,7 +107,12 @@ def create_actuator_template(): 'body': json.dumps(template) } - result = TemplateHandler.create_template(Request(req)) + params = { + 'content_type': 'application/json', + 'data': json.dumps(template) + } + + result = TemplateHandler.create_template(params, generate_token()) template_id = result['template']['id'] return template_id @@ -171,7 +181,15 @@ def create_single_device(transaction): }, 'body': json.dumps(device) } - result = DeviceHandler.create_device(Request(req)) + + params = { + 'count': '1', + 'verbose': 'False', + 'content_type': 'application/json', + 'data': json.dumps(device) + } + + result = DeviceHandler.create_device(params, generate_token()) device_id = result['devices'][0]['id'] transaction['proprietary']['device_id'] = device_id return device_id @@ -197,7 +215,16 @@ def create_actuator_device(transaction): }, 'body': json.dumps(device) } - result = DeviceHandler.create_device(Request(req)) + + params = { + 'count': '1', + 'verbose': 'False', + 'content_type': 'application/json', + 'data': json.dumps(device) + } + + + result = DeviceHandler.create_device(params, generate_token()) device_id = result['devices'][0]['id'] transaction['proprietary']['device_id'] = device_id return device_id @@ -339,14 +366,25 @@ def clean_scenario(transaction): 'body': '' } - result = DeviceHandler.get_devices(Request(req)) + params = { + 'page_size': 10, + 'page_num': 1, + 'attr_format': 'both', + 'attr': [], + 'attr_type': [], + 'idsOnly':'false' + } + + token = generate_token() + + result = DeviceHandler.get_devices(token, params) + for device in result['devices']: - DeviceHandler.delete_device(Request(req), device['id']) + DeviceHandler.delete_device(device['id'], token) - result = TemplateHandler.get_templates(Request(req)) + result = TemplateHandler.get_templates(params, token) for template in result['templates']: - # print(template) - TemplateHandler.remove_template(Request(req), template['id']) + TemplateHandler.remove_template(template['id'], token) @hooks.before_validation('Templates > Templates > Get the current list of templates') diff --git a/tests/test_backend_handler.py b/tests/test_backend_handler.py new file mode 100644 index 0000000..e885a9b --- /dev/null +++ b/tests/test_backend_handler.py @@ -0,0 +1,54 @@ +import pytest +import json +import unittest +from unittest.mock import Mock, MagicMock, patch, call +from kafka import KafkaProducer + +from DeviceManager.BackendHandler import KafkaHandler, KafkaInstanceHandler + +class TestBackendHandler(unittest.TestCase): + + @patch("DeviceManager.BackendHandler.KafkaNotifier") + @patch("DeviceManager.KafkaNotifier.KafkaProducer.flush") + def test_create_event(self, kafka_instance_mock, kafka_flush): + + device = {'templates': [369], 'label': 'test_device', + 'id': 1, 'created': '2019-08-29T18:18:07.801602+00:00'} + + KafkaHandler().create(device, meta={"service": 'admin'}) + self.assertTrue(kafka_flush.called) + + @patch("DeviceManager.BackendHandler.KafkaNotifier") + @patch("DeviceManager.KafkaNotifier.KafkaProducer.flush") + def test_remove_event(self, kafka_instance_mock, kafka_flush): + + device = {'templates': [369], 'label': 'test_device', + 'id': 1, 'created': '2019-08-29T18:18:07.801602+00:00'} + + KafkaHandler().remove(device, meta={"service": 'admin'}) + self.assertTrue(kafka_flush.called) + + @patch("DeviceManager.BackendHandler.KafkaNotifier") + @patch("DeviceManager.KafkaNotifier.KafkaProducer.flush") + def test_update_event(self, kafka_instance_mock, kafka_flush): + + device = {'templates': [369], 'label': 'test_device', + 'id': 1, 'created': '2019-08-29T18:18:07.801602+00:00'} + + KafkaHandler().update(device, meta={"service": 'admin'}) + self.assertTrue(kafka_flush.called) + + @patch("DeviceManager.BackendHandler.KafkaNotifier") + @patch("DeviceManager.KafkaNotifier.KafkaProducer.flush") + def test_configure_event(self, kafka_instance_mock, kafka_flush): + + device = {'templates': [369], 'label': 'test_device', + 'id': 1, 'created': '2019-08-29T18:18:07.801602+00:00'} + + KafkaHandler().configure(device, meta={"service": 'admin'}) + self.assertTrue(kafka_flush.called) + + def test_verify_intance_kafka(self): + with patch('DeviceManager.BackendHandler.KafkaHandler') as mock_kafka_instance_wrapper: + mock_kafka_instance_wrapper.return_value = Mock() + self.assertIsNotNone(KafkaInstanceHandler().getInstance(None)) diff --git a/tests/test_database_handler.py b/tests/test_database_handler.py new file mode 100644 index 0000000..ba70844 --- /dev/null +++ b/tests/test_database_handler.py @@ -0,0 +1,43 @@ +import pytest +import json +import unittest +from unittest.mock import Mock, MagicMock, patch + + +from DeviceManager.DatabaseHandler import MultiTenantSQLAlchemy, before_request + +from flask import Flask, g +from flask_sqlalchemy import SQLAlchemy + +class TestDatabaseHandler(unittest.TestCase): + + app = Flask(__name__) + + def test_check_binds_multi_tenancy(self): + self.assertIsNone(MultiTenantSQLAlchemy().check_binds('test_bind_sql_alchemy')) + + def test_choose_tenant_multi_tenancy(self): + with self.app.test_request_context(): + self.assertIsNone(MultiTenantSQLAlchemy().choose_tenant('test_bind_sql_alchemy')) + + def test_get_engine_multi_tenancy(self): + with self.app.test_request_context(): + with self.assertRaises(RuntimeError): + MultiTenantSQLAlchemy().get_engine() + + self.app.config['SQLALCHEMY_BINDS'] = { + 'test_bind_sql_alchemy': 'postgresql+psycopg2://postgres@postgres/dojot_devm', + } + + sql_alchemy = SQLAlchemy(self.app) + sql_alchemy.init_app(self.app) + + self.assertIsNotNone(MultiTenantSQLAlchemy().get_engine(self.app, 'test_bind_sql_alchemy')) + + def test_before_request(self): + with self.app.test_request_context(): + result = before_request() + self.assertEqual(result.status, '401 UNAUTHORIZED') + self.assertEqual(json.loads(result.response[0])[ + 'message'], 'No authorization token has been supplied') + \ No newline at end of file diff --git a/tests/test_device_handler.py b/tests/test_device_handler.py new file mode 100644 index 0000000..a30473f --- /dev/null +++ b/tests/test_device_handler.py @@ -0,0 +1,429 @@ +import pytest +import json +import unittest +from unittest.mock import Mock, MagicMock, patch, call +from flask import Flask + +from DeviceManager.DeviceHandler import DeviceHandler, flask_delete_all_device, flask_get_device, flask_remove_device, flask_add_template_to_device, flask_remove_template_from_device, flask_gen_psk,flask_internal_get_device +from DeviceManager.utils import HTTPRequestError +from DeviceManager.DatabaseModels import Device, DeviceAttrsPsk, DeviceAttr +from DeviceManager.DatabaseModels import assert_device_exists +from DeviceManager.BackendHandler import KafkaInstanceHandler +import DeviceManager.DatabaseModels + +from .token_test_generator import generate_token + +from alchemy_mock.mocking import AlchemyMagicMock, UnifiedAlchemyMagicMock + + +class TestDeviceHandler(unittest.TestCase): + + app = Flask(__name__) + + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_generate_deviceId(self, query_property_getter_mock): + query_property_getter_mock.return_value.filter_by.return_value.first.return_value = None + self.assertIsNotNone(DeviceHandler.generate_device_id()) + + query_property_getter_mock.return_value.filter_by.return_value.first.return_value = 'existed_device_id' + with pytest.raises(HTTPRequestError): + DeviceHandler.generate_device_id() + + @patch('DeviceManager.DeviceHandler.db') + def test_get_devices(self, db_mock): + db_mock.session = UnifiedAlchemyMagicMock() + + db_mock.session.paginate().items = [Device(id=1, label='test_device1')] + + params_query = {'page_number': 5, 'per_page': 1, 'sortBy': None, 'attr': [ + ], 'idsOnly': 'false', 'attr_type': [], 'label': 'test_device1'} + token = generate_token() + + result = DeviceHandler.get_devices(token, params_query) + + self.assertIsNotNone(result) + self.assertTrue(json.dumps(result['devices'])) + + params_query = {'page_number': 1, 'per_page': 1, 'sortBy': None, + 'attr': [], 'idsOnly': 'false', 'attr_type': []} + result = DeviceHandler.get_devices(token, params_query) + self.assertTrue(json.dumps(result['devices'])) + self.assertIsNotNone(result) + + params_query = {'page_number': 1, 'per_page': 1, 'sortBy': None, 'attr': [ + 'foo=bar'], 'idsOnly': 'false', 'attr_type': []} + result = DeviceHandler.get_devices(token, params_query) + self.assertTrue(json.dumps(result['devices'])) + self.assertIsNotNone(result) + + params_query = {'sortBy': None, 'attr': [], + 'idsOnly': 'true', 'attr_type': []} + + with patch.object(DeviceHandler, "get_only_ids", return_value=['4f2b', '1e4a']) as DeviceOnlyIds: + result = DeviceHandler.get_devices(token, params_query) + self.assertTrue(json.dumps(result)) + DeviceOnlyIds.assert_called_once() + + params_query = {'page_number': 5, 'per_page': 1, 'sortBy': None, 'attr': [ + ], 'idsOnly': 'false', 'attr_type': [], 'label': 'test_device1'} + result = DeviceHandler.get_devices(token, params_query, True) + self.assertTrue(json.dumps(result['devices'])) + self.assertIsNotNone(result) + + @patch('DeviceManager.DeviceHandler.db') + def test_list_devicesId(self, db_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + db_mock.session.query(Device.id).all.return_value = ['4f2b', '1e4a'] + + result = DeviceHandler.list_ids(token) + self.assertTrue(json.dumps(result)) + self.assertIsNotNone(result) + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_get_device(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + query_property_getter_mock.filter_by.one.return_value = Device( + id=1, label='teste') + result = DeviceHandler.get_device(token, 'device_id') + self.assertIsNotNone(result) + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_delete_device(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + with patch.object(KafkaInstanceHandler, "getInstance", return_value=MagicMock()) as KafkaInstanceMock: + result = DeviceHandler.delete_device('device_id', token) + KafkaInstanceMock.assert_called_once() + self.assertIsNotNone(result) + self.assertEqual(result['result'], 'ok') + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_delete_all_devices(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + db_mock.session.query(Device).return_value = [ + Device(id=1, label='device_label')] + result = DeviceHandler.delete_all_devices(token) + + self.assertIsNotNone(result) + self.assertEqual(result['result'], 'ok') + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_add_template_to_device(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + query_property_getter_mock.filter_by.one.return_value = Device( + id=1, label='device_label') + result = DeviceHandler.add_template_to_device( + token, 'device_id', 'template_id') + self.assertIsNotNone(result) + self.assertIsNotNone(result['device']) + self.assertEqual(result['message'], 'device updated') + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_remove_template_to_device(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + query_property_getter_mock.filter_by.one.return_value = Device( + id=1, label='device_label') + result = DeviceHandler.remove_template_from_device( + token, 'device_id', 'template_id') + self.assertIsNotNone(result) + self.assertIsNotNone(result['device']) + self.assertEqual(result['message'], 'device updated') + + @patch('DeviceManager.DeviceHandler.db') + def test_get_devices_by_templateId(self, db_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + params_query = {'page_number': 1, 'per_page': 1} + + db_mock.session.paginate().items = [Device(id=1, label='test_device1')] + result = DeviceHandler.get_by_template( + token, params_query, 'template_id') + self.assertIsNotNone(result) + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_generate_shared_key(self, db_mock_session, query_property_getter_mock): + + device = Device(id=1, label='test_device') + token = generate_token() + result = None + + with patch('DeviceManager.DeviceHandler.assert_device_exists') as mock_device_exist_wrapper: + mock_device_exist_wrapper.return_value = None + + with self.assertRaises(HTTPRequestError): + DeviceHandler.gen_psk(token, 'device_id', 1024) + + mock_device_exist_wrapper.return_value = device + + with patch('DeviceManager.DeviceHandler.serialize_full_device') as mock_serialize_device_wrapper: + mock_serialize_device_wrapper.return_value = {'templates': [369], 'label': 'test_device', 'id': 1, + 'created': '2019-08-29T18:18:07.801602+00:00', 'attrs': {369: [ + {'label': 'shared_key', 'template_id': '369', 'id': 1504, 'type': 'static', 'created': '2019-08-29T18:18:07.778178+00:00', + 'value_type': 'psk'}]}} + + with patch.object(KafkaInstanceHandler, "getInstance", return_value=MagicMock()): + query_property_getter_mock.return_value.session.return_value.query.return_value.filter_by.first.return_value = None + result = DeviceHandler.gen_psk(token, 'device_id', 1024) + self.assertIsNotNone(result) + + query_property_getter_mock.return_value.session.return_value.query.return_value.filter_by.first.return_value = MagicMock() + result = DeviceHandler.gen_psk(token, 'device_id', 1024) + self.assertIsNotNone(result) + + result = DeviceHandler.gen_psk( + token, 'device_id', 1024, ['shared_key']) + self.assertIsNotNone(result) + + with self.assertRaises(HTTPRequestError): + DeviceHandler.gen_psk(token, 'device_id', 1024, [ + 'shared_key_not_contains']) + + with self.assertRaises(HTTPRequestError): + DeviceHandler.gen_psk(token, 'device_id', 1025) + + with self.assertRaises(HTTPRequestError): + DeviceHandler.gen_psk(token, 'device_id', 0) + + with self.assertRaises(HTTPRequestError): + DeviceHandler.gen_psk(token, 'device_id', -1) + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_copy_shared_key(self, db_mock_session, query_property_getter_mock): + + deviceSrc = Device(id=1, label='test_device') + token = generate_token() + result = None + + with patch('DeviceManager.DeviceHandler.assert_device_exists') as mock_device_exist_wrapper: + mock_device_exist_wrapper.return_value = None + + with self.assertRaises(HTTPRequestError): + DeviceHandler.copy_psk( + token, 'device_id_src', 'label', 'device_id_dest', 'label') + + mock_device_exist_wrapper.return_value = deviceSrc + + with patch('DeviceManager.DeviceHandler.serialize_full_device') as mock_serialize_device_wrapper: + mock_serialize_device_wrapper.return_value = {'templates': [369], 'label': 'test_device', 'id': 1, + 'created': '2019-08-29T18:18:07.801602+00:00', 'attrs': {369: [ + {'static_value': 'model-001', 'label': 'shared_key', 'value_type': 'psk', + 'type': 'static', 'template_id': '391', 'id': 1591, + 'created': '2019-08-29T18:24:43.490221+00:00', 'is_static_overridden': False}]}} + + with patch.object(KafkaInstanceHandler, "getInstance", return_value=MagicMock()): + query_property_getter_mock.return_value.session.return_value.query.return_value.filter_by.first.return_value = None + + with self.assertRaises(HTTPRequestError): + DeviceHandler.copy_psk( + token, 'device_id_src', 'label_not_exist_dest_src', 'device_id_dest', 'label_not_exist_dest') + + result = DeviceHandler.copy_psk( + token, 'device_id_src', 'shared_key', 'device_id_dest', 'shared_key') + self.assertIsNone(result) + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_create_device(self, db_mock_session, query_property_getter_mock): + db_mock_session.session = AlchemyMagicMock() + token = generate_token() + + data = '{"label":"test_device","templates":[1]}' + + with patch('DeviceManager.DeviceHandler.DeviceHandler.generate_device_id') as mock_device_id: + mock_device_id.return_value = 'test_device_id' + + with patch.object(KafkaInstanceHandler, "getInstance", return_value=MagicMock()): + + params = {'count': '1', 'verbose': 'false', + 'content_type': 'application/json', 'data': data} + result = DeviceHandler.create_device(params, token) + + self.assertIsNotNone(result) + self.assertTrue(result['devices']) + self.assertEqual(result['message'], 'devices created') + self.assertEqual(result['devices'][0]['id'], 'test_device_id') + self.assertEqual(result['devices'][0]['label'], 'test_device') + + params = {'count': '1', 'verbose': 'true', + 'content_type': 'application/json', 'data': data} + result = DeviceHandler.create_device(params, token) + self.assertIsNotNone(result) + self.assertTrue(result['devices']) + self.assertEqual(result['message'], 'device created') + + # Here contains the validation when the count is not a number + params = {'count': 'is_not_a_number', 'verbose': 'false', + 'content_type': 'application/json', 'data': data} + + with self.assertRaises(HTTPRequestError): + result = DeviceHandler.create_device(params, token) + + # Here contains the HttpRequestError validating de count with verbose + params = {'count': '2', 'verbose': 'true', + 'content_type': 'application/json', 'data': data} + + with self.assertRaises(HTTPRequestError): + result = DeviceHandler.create_device(params, token) + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_update_device(self, db_mock_session, query_property_getter_mock): + db_mock_session.session = AlchemyMagicMock() + token = generate_token() + + data = '{"label": "test_updated_device", "templates": [4865]}' + + with patch.object(KafkaInstanceHandler, "getInstance", return_value=MagicMock()): + params = {'content_type': 'application/json', 'data': data} + result = DeviceHandler.update_device( + params, 'test_device_id', token) + self.assertIsNotNone(result) + self.assertEqual(result['message'], 'device updated') + self.assertIsNotNone(result['device']) + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_configure_device(self, db_mock_session, query_property_getter_mock): + db_mock_session.session = AlchemyMagicMock() + token = generate_token() + + device = Device(id=1, label='test_device') + + with patch('DeviceManager.DeviceHandler.assert_device_exists') as mock_device_exist_wrapper: + mock_device_exist_wrapper.return_value = device + + with patch('DeviceManager.DeviceHandler.serialize_full_device') as mock_serialize_device_wrapper: + mock_serialize_device_wrapper.return_value = {'templates': [369], 'label': 'test_device', 'id': 1, + 'created': '2019-08-29T18:18:07.801602+00:00', 'attrs': {369: [ + {'label': 'temperature', 'template_id': '369', 'id': 1504, 'type': 'actuator', 'created': '2019-08-29T18:18:07.778178+00:00', + 'value_type': 'psk'}]}} + + data = '{"topic": "/admin/efac/config", "attrs": {"temperature": 10.6}}' + params = {'data': data} + + with patch.object(KafkaInstanceHandler, "getInstance", return_value=MagicMock()): + result = DeviceHandler.configure_device(params, 'test_device_id', token) + self.assertIsNotNone(result) + self.assertEqual(result[' status'], 'configuration sent to device') + + data = '{"topic": "/admin/efac/config", "attrs": {"test_attr": "xpto"}}' + params = {'data': data} + + with self.assertRaises(HTTPRequestError): + DeviceHandler.configure_device(params, 'test_device_id', token) + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_endpoint_delete_all_devices(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.DeviceHandler.retrieve_auth_token") as auth_mock: + auth_mock.return_value = generate_token() + result = flask_delete_all_device() + self.assertFalse(json.loads(result.response[0])['removed_devices']) + self.assertEqual(json.loads(result.response[0])['result'], 'ok') + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_endpoint_get_device(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.DeviceHandler.retrieve_auth_token") as auth_mock: + auth_mock.return_value = generate_token() + with patch.object(DeviceHandler, "get_device") as mock_device: + mock_device.return_value = {'label': 'test_device', 'id':1, 'created': '2019-08-29T18:18:07.801602+00:00', 'attrs': {}} + result = flask_get_device('test_device_id') + self.assertIsNotNone(result.response) + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_endpoint_remove_device(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.DeviceHandler.retrieve_auth_token") as auth_mock: + with patch.object(KafkaInstanceHandler, "getInstance", return_value=MagicMock()): + auth_mock.return_value = generate_token() + + with patch.object(DeviceHandler, "delete_device") as mock_remove_device: + mock_remove_device.return_value = {'result': 'ok', 'removed_device': {'id': 1, 'label': 'test_device', 'created': '2019-08-29T18:18:07.801602+00:00'}} + result = flask_remove_device('test_device_id') + self.assertIsNotNone(result.response) + self.assertEqual(json.loads(result.response[0])['result'], 'ok') + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_endpoint_flask_add_template_to_device(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.DeviceHandler.retrieve_auth_token") as auth_mock: + auth_mock.return_value = generate_token() + + with patch.object(DeviceHandler, "add_template_to_device") as mock_template_to_device: + mock_template_to_device.return_value = {'message': 'device updated', 'id':1, 'created': '2019-08-29T18:18:07.801602+00:00', 'attrs': {}} + result = flask_add_template_to_device('test_device_id', 'test_template_id') + self.assertIsNotNone(result.response) + self.assertEqual(json.loads(result.response[0])['message'], 'device updated') + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_endpoint_flask_remove_template_from_device(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.DeviceHandler.retrieve_auth_token") as auth_mock: + auth_mock.return_value = generate_token() + with patch.object(DeviceHandler, "remove_template_from_device") as mock_template_to_device: + mock_template_to_device.return_value = {'message': 'device updated', 'id':140088130054016, 'created': '2019-08-29T18:18:07.801602+00:00', 'attrs': {}} + result = flask_remove_template_from_device('test_device_id', 'test_template_id') + self.assertIsNotNone(result.response) + self.assertEqual(json.loads(result.response[0])['message'], 'device updated') + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_endpoint_generate_psk(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.DeviceHandler.retrieve_auth_token") as auth_mock: + result = flask_gen_psk('test_device_id') + self.assertEqual(json.loads(result.response[0])['message'], 'Missing key_length parameter') + self.assertEqual(json.loads(result.response[0])['status'], 400) + + with patch("DeviceManager.DeviceHandler.request") as req: + req.args = { + "key_length": "200" + } + + auth_mock.return_value = generate_token() + result = flask_gen_psk('test_device_id') + self.assertEqual(json.loads(result.response[0])['message'], 'ok') + self.assertEqual(json.loads(result.response[0])['status'], 204) + + @patch('DeviceManager.DeviceHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_endpoint_internal_get_device(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.DeviceHandler.retrieve_auth_token") as auth_mock: + auth_mock.return_value = generate_token() + with patch.object(DeviceHandler, "get_device") as mock_getDevice: + mock_getDevice.return_value = {'id': 140110840862312, 'created': '2019-08-29T18:18:07.801602+00:00', 'attrs': {}} + result = flask_internal_get_device('test_device_id') + self.assertIsNotNone(result.response) diff --git a/tests/test_error_manager.py b/tests/test_error_manager.py new file mode 100644 index 0000000..20f2c50 --- /dev/null +++ b/tests/test_error_manager.py @@ -0,0 +1,25 @@ +import pytest +import json +import unittest +from unittest.mock import Mock, MagicMock, patch, call +from flask import Flask + +from DeviceManager.ErrorManager import not_found, internal_error + +class TestErrorManager(unittest.TestCase): + + app = Flask(__name__) + + def test_not_found_endpoint(self): + with self.app.test_request_context(): + result = not_found(Mock()) + self.assertEqual(result.status, '404 NOT FOUND') + self.assertEqual(json.loads(result.response[0])[ + 'msg'], 'Invalid endpoint requested') + + def test_internal_error(self): + with self.app.test_request_context(): + result = internal_error(Mock()) + self.assertEqual(result.status, '500 INTERNAL SERVER ERROR') + self.assertEqual(json.loads(result.response[0])[ + 'msg'], 'Internal Error') diff --git a/tests/test_import_handler.py b/tests/test_import_handler.py new file mode 100644 index 0000000..7111f72 --- /dev/null +++ b/tests/test_import_handler.py @@ -0,0 +1,151 @@ +import pytest +import json +import unittest +from unittest.mock import Mock, MagicMock, patch, call + +from alchemy_mock.mocking import AlchemyMagicMock + +from DeviceManager.ImportHandler import ImportHandler +from DeviceManager.DatabaseModels import Device, DeviceTemplate +from DeviceManager.utils import HTTPRequestError +from DeviceManager.BackendHandler import KafkaInstanceHandler, KafkaHandler + +from .token_test_generator import generate_token + + +class TestImportHandler(unittest.TestCase): + + @patch('DeviceManager.ImportHandler.db') + def test_drop_sequence(self, db_mock): + db_mock.session = AlchemyMagicMock() + self.assertIsNone(ImportHandler.drop_sequences()) + + def test_replaceIds_to_imoprtIds(self): + json_import = '{"id": "test_value"}' + + result = ImportHandler.replace_ids_by_import_ids(json_import) + self.assertIsNotNone(result) + self.assertIn('import_id', result) + + @patch('DeviceManager.ImportHandler.db') + def test_restore_template_sequence(self, db_mock): + db_mock.session = AlchemyMagicMock() + self.assertIsNone(ImportHandler.restore_template_sequence()) + + @patch('DeviceManager.ImportHandler.db') + def test_restore_attr_sequence(self, db_mock): + db_mock.session = AlchemyMagicMock() + self.assertIsNone(ImportHandler.restore_attr_sequence()) + + @patch('DeviceManager.ImportHandler.db') + def test_restore_sequences(self, db_mock): + db_mock.session = AlchemyMagicMock() + self.assertIsNone(ImportHandler.restore_sequences()) + + @patch("DeviceManager.BackendHandler.KafkaNotifier") + @patch("DeviceManager.KafkaNotifier.KafkaProducer.flush") + def test_notifies_deletion_to_kafka(self, kafka_instance_mock, kafka_flush): + with patch('DeviceManager.ImportHandler.serialize_full_device') as mock_serialize_device_wrapper: + mock_serialize_device_wrapper.return_value = {'templates': [369], 'label': 'test_device', 'id': 1, + 'created': '2019-08-29T18:18:07.801602+00:00'} + + with patch.object(KafkaInstanceHandler, "getInstance", return_value=KafkaHandler()): + ImportHandler().notifies_deletion_to_kafka('test_device', 'admin') + self.assertTrue(kafka_flush.called) + + @patch('DeviceManager.ImportHandler.db') + def test_delete_records(self, db_mock): + db_mock.session = AlchemyMagicMock() + self.assertIsNone(ImportHandler.delete_records('admin')) + + @patch('DeviceManager.ImportHandler.db') + def test_clear_db_config(self, db_mock): + db_mock.session = AlchemyMagicMock() + self.assertIsNone(ImportHandler.clear_db_config('admin')) + + @patch('DeviceManager.ImportHandler.db') + def test_restore_db_config(self, db_mock): + db_mock.session = AlchemyMagicMock() + self.assertIsNone(ImportHandler.restore_db_config()) + + @patch('DeviceManager.ImportHandler.db') + def test_save_templates(self, db_mock): + db_mock.session = AlchemyMagicMock() + + json_payload = {'templates': [{'import_id': 1, 'attrs': [ + {'label': 'temperature', 'type': 'dynamic', 'value_type': 'float'}]}], 'label': 'test_device', 'id': 1, + 'created': '2019-08-29T18:18:07.801602+00:00'} + + json_data = {"label": "test_device", "id": 1, + "templates": [{'id': 1, 'label': 'test_template'}]} + + result = ImportHandler.save_templates(json_data, json_payload) + self.assertIsNotNone(result) + self.assertTrue(result) + + json_data = {"label": "test_device", "id": 1, + "templates": [{'id': 2, 'label': 'test_template'}]} + result = ImportHandler.save_templates(json_data, json_payload) + self.assertIsNotNone(result) + self.assertFalse(result[0].attrs) + + @patch('DeviceManager.ImportHandler.db') + def test_set_templates_on_device(self, db_mock): + db_mock.session = AlchemyMagicMock() + + json_payload = {'templates': [{'import_id': 1, 'attrs': [ + {'label': 'temperature', 'type': 'dynamic', 'value_type': 'float'}]}], 'label': 'test_device', 'id': 1, + 'created': '2019-08-29T18:18:07.801602+00:00'} + + saved_templates = [DeviceTemplate(label='test_template', attrs=[])] + + self.assertIsNone(ImportHandler.set_templates_on_device( + Mock(), json_payload, saved_templates)) + + @patch('DeviceManager.ImportHandler.db') + def test_save_devices(self, db_mock): + db_mock.session = AlchemyMagicMock() + + json_payload = {"devices": [{"id": "68fc", "label": "test_device_0"}, { + "id": "94dc", "label": "test_device_1"}]} + json_data = {"devices": [{"id": "68fc", "label": "test_device_0"}, { + "id": "94dc", "label": "test_device_1"}]} + + saved_templates = [DeviceTemplate(label='test_template', attrs=[])] + + result = ImportHandler.save_devices( + json_data, json_payload, saved_templates) + self.assertIsNotNone(result) + self.assertTrue(result) + + @patch("DeviceManager.BackendHandler.KafkaNotifier") + @patch("DeviceManager.KafkaNotifier.KafkaProducer.flush") + def test_notifies_creation_to_kafka(self, kafka_instance_mock, kafka_flush): + with patch('DeviceManager.ImportHandler.serialize_full_device') as mock_serialize_device_wrapper: + mock_serialize_device_wrapper.return_value = {'templates': [369], 'label': 'test_device', 'id': 1, + 'created': '2019-08-29T18:18:07.801602+00:00'} + + with patch.object(KafkaInstanceHandler, "getInstance", return_value=KafkaHandler()): + ImportHandler().notifies_creation_to_kafka( + [Device(id=1, label='test_device')], 'admin') + self.assertTrue(kafka_flush.called) + + @patch('DeviceManager.ImportHandler.db') + def test_import_data(self, db_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + data = """{"templates": [{"id": 1, "label": "template1", "attrs": [{"label": "temperature", "type": "dynamic", "value_type": "float"}]}], + "devices": [{"id": "68fc", "label": "test_device_0"},{"id": "94dc","label": "test_device_1"}]}""" + + with patch.object(KafkaInstanceHandler, "getInstance", return_value=MagicMock()): + result = ImportHandler.import_data(data, token, 'application/json') + self.assertIsNotNone(result) + self.assertEqual(result['message'], 'data imported!') + + data = """{"templates": {"id": 1, "label": "template1", "attrs": [{"label": "temperature", "type": "dynamic", "value_type": "float"}]}, + "devices": [{"id": "68fc", "label": "test_device_0"},{"id": "94dc","label": "test_device_1"}]}""" + + with self.assertRaises(HTTPRequestError): + ImportHandler.import_data(data, token, 'application/json') + \ No newline at end of file diff --git a/tests/test_kafka_notifier.py b/tests/test_kafka_notifier.py new file mode 100644 index 0000000..d974872 --- /dev/null +++ b/tests/test_kafka_notifier.py @@ -0,0 +1,54 @@ +import pytest +import json +import unittest +from unittest.mock import Mock, MagicMock, patch, call + +import requests +from DeviceManager.Logger import Log +from DeviceManager.KafkaNotifier import DeviceEvent, NotificationMessage, KafkaNotifier + +class TestKafkaNotifier(unittest.TestCase): + + def test_notification_message_to_json(self): + data = {'label': 'test_device', 'id': 'test_device_id', 'templates': [1], 'attrs': {}} + kafkaNotification = NotificationMessage(DeviceEvent.CREATE, data, m={"service": 'admin'}) + + result = kafkaNotification.to_json() + self.assertIsNotNone(result) + self.assertIn('event', result) + self.assertIn('data', result) + self.assertIn('meta', result) + + def test_get_topic(self): + with patch.object(KafkaNotifier, "__init__", lambda x: None): + KafkaNotifier.topic_map = {} + + with patch("requests.get") as request_mock: + request_mock.return_value = Mock(status_code=200, json=Mock(return_value={'topic': '83a257de-c421-4529-b42d-5976def7b526'})) + result = KafkaNotifier().get_topic('admin', 'dojot.device-manager.device') + self.assertIsNotNone(result) + self.assertEqual(result, '83a257de-c421-4529-b42d-5976def7b526') + + def test_send_notification(self): + data = {'label': 'test_device', 'id': 'test_device_id', 'templates': [1], 'attrs': {}} + + with patch.object(KafkaNotifier, "__init__", lambda x: None): + KafkaNotifier.kf_prod = Mock() + self.assertIsNone(KafkaNotifier().send_notification(DeviceEvent.CREATE, data, meta={"service": 'admin'})) + + with patch.object(KafkaNotifier, "get_topic", return_value=None): + self.assertIsNone(KafkaNotifier().send_notification(DeviceEvent.CREATE, data, meta={"service": 'admin'})) + + def test_send_raw(self): + event = { + "event": DeviceEvent.TEMPLATE, + "data": { + "affected": [], + "template": {'label': 'SensorModelUpdated', 'config_attrs': [], 'id': 1, 'data_attrs': [], 'attrs': []} + }, + "meta": {"service": 'admin'} + } + + with patch.object(KafkaNotifier, "__init__", lambda x: None): + self.assertIsNone(KafkaNotifier().send_raw(event, 'admin')) + \ No newline at end of file diff --git a/tests/test_logger_handler.py b/tests/test_logger_handler.py new file mode 100644 index 0000000..81865f7 --- /dev/null +++ b/tests/test_logger_handler.py @@ -0,0 +1,38 @@ +import pytest +import json +import unittest + +from DeviceManager.LoggerHandler import LoggerHandler, flask_update_log_level, flask_get_log_level +from DeviceManager.utils import HTTPRequestError +from DeviceManager.SerializationModels import log_schema + +from flask import Flask +from unittest.mock import Mock, MagicMock, patch + +class TestLoggerHandler(unittest.TestCase): + + app = Flask(__name__) + + def test_update_valid_level_log(self): + self.assertTrue(LoggerHandler.update_log_level("info"), "") + with self.assertRaises(HTTPRequestError): + LoggerHandler.update_log_level("teste") + + def test_get_actual_level_log(self): + level = LoggerHandler.get_log_level() + self.assertIsNotNone(level) + + def test_endpoint_get_log(self): + with self.app.test_request_context(): + result = flask_get_log_level() + self.assertEqual(result.status, '200 OK') + self.assertEqual(json.loads(result.response[0])[ + 'level'], 'INFO') + + def test_endpoint_update_log(self): + with self.app.test_request_context(): + result = flask_update_log_level() + self.assertEqual(result.status, '400 BAD REQUEST') + self.assertEqual(json.loads(result.response[0])[ + 'message'], 'Payload must be valid JSON, and Content-Type set accordingly') + \ No newline at end of file diff --git a/tests/test_template_handler.py b/tests/test_template_handler.py new file mode 100644 index 0000000..513c155 --- /dev/null +++ b/tests/test_template_handler.py @@ -0,0 +1,229 @@ +import pytest +import json +import unittest +from unittest.mock import Mock, MagicMock, patch, call +from flask import Flask + +from DeviceManager.DatabaseModels import DeviceTemplate +from DeviceManager.TemplateHandler import TemplateHandler, flask_get_templates, flask_delete_all_templates, flask_get_template, flask_remove_template, paginate, attr_format +from DeviceManager.utils import HTTPRequestError +from DeviceManager.BackendHandler import KafkaInstanceHandler + + +from .token_test_generator import generate_token + +from alchemy_mock.mocking import AlchemyMagicMock, UnifiedAlchemyMagicMock + + +class TestTemplateHandler(unittest.TestCase): + + app = Flask(__name__) + + @patch('DeviceManager.TemplateHandler.db') + def test_get_templates(self, db_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + params_query = {'page_number': 5, 'per_page': 1, + 'sortBy': None, 'attr': [], 'attr_type': [], 'label': 'dummy'} + result = TemplateHandler.get_templates(params_query, token) + self.assertIsNotNone(result) + + # test using attrs + params_query = {'page_number': 1, 'per_page': 1, + 'sortBy': None, 'attr': ['foo=bar'], 'attr_type': []} + result = TemplateHandler.get_templates(params_query, token) + self.assertIsNotNone(result) + + # test using sortBy + params_query = {'page_number': 1, 'per_page': 1, + 'sortBy': 'label', 'attr': ['foo=bar'], 'attr_type': []} + result = TemplateHandler.get_templates(params_query, token) + self.assertIsNotNone(result) + + # test without querys + params_query = {'page_number': 5, 'per_page': 1, + 'sortBy': None, 'attr': [], 'attr_type': []} + result = TemplateHandler.get_templates(params_query, token) + self.assertIsNotNone(result) + + @patch('DeviceManager.TemplateHandler.db') + def test_create_tempĺate(self, db_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + data = """{ + "label": "SensorModel", + "attrs": [ + { + "label": "temperature", + "type": "dynamic", + "value_type": "float" + }, + { + "label": "model-id", + "type": "static", + "value_type": "string", + "static_value": "model-001" + } + ] + }""" + + params_query = {'content_type': 'application/json', 'data': data} + result = TemplateHandler.create_template(params_query, token) + self.assertIsNotNone(result) + self.assertEqual(result['result'], 'ok') + self.assertIsNotNone(result['template']) + + @patch('DeviceManager.TemplateHandler.db') + def test_get_template(self, db_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + template = DeviceTemplate(id=1, label='template1') + params_query = {'attr_format': 'both'} + + with patch('DeviceManager.TemplateHandler.assert_template_exists') as mock_template_exist_wrapper: + mock_template_exist_wrapper.return_value = template + result = TemplateHandler.get_template( + params_query, 'template_id_test', token) + self.assertIsNotNone(result) + + mock_template_exist_wrapper.return_value = None + result = TemplateHandler.get_template( + params_query, 'template_id_test', token) + self.assertFalse(result) + + @patch('DeviceManager.TemplateHandler.db') + def test_delete_all_templates(self, db_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + result = TemplateHandler.delete_all_templates(token) + self.assertIsNotNone(result) + self.assertTrue(result) + self.assertEqual(result['result'], 'ok') + + @patch('DeviceManager.TemplateHandler.db') + def test_remove_template(self, db_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + template = DeviceTemplate(id=1, label='template1') + + with patch('DeviceManager.TemplateHandler.assert_template_exists') as mock_template_exist_wrapper: + mock_template_exist_wrapper.return_value = template + + result = TemplateHandler.remove_template(1, token) + self.assertIsNotNone(result) + self.assertTrue(result) + self.assertTrue(result['removed']) + self.assertEqual(result['result'], 'ok') + + @patch('DeviceManager.TemplateHandler.db') + def test_update_template(self, db_mock): + db_mock.session = AlchemyMagicMock() + token = generate_token() + + template = DeviceTemplate(id=1, label='SensorModel') + + data = """{ + "label": "SensorModelUpdated", + "attrs": [ + { + "label": "temperature", + "type": "dynamic", + "value_type": "float" + }, + { + "label": "model-id", + "type": "static", + "value_type": "string", + "static_value": "model-001" + } + ] + }""" + + params_query = {'content_type': 'application/json', 'data': data} + + with patch('DeviceManager.TemplateHandler.assert_template_exists') as mock_template_exist_wrapper: + mock_template_exist_wrapper.return_value = template + + with patch.object(KafkaInstanceHandler, "getInstance", return_value=MagicMock()): + result = TemplateHandler.update_template( + params_query, 1, token) + self.assertIsNotNone(result) + self.assertTrue(result) + self.assertTrue(result['updated']) + self.assertEqual(result['result'], 'ok') + + def test_attr_format(self): + params = {'data_attrs': [], 'config_attrs': [], + 'id': 1, 'attrs': [], 'label': 'template1'} + + result = attr_format('split', params) + self.assertNotIn('attrs', result) + + result = attr_format('single', params) + self.assertNotIn('config_attrs', result) + self.assertNotIn('data_attrs', result) + + @patch('DeviceManager.TemplateHandler.db') + def test_paginate(self, db_mock): + db_mock.session = AlchemyMagicMock() + + result = paginate(db_mock.session.query, 0, 10, True) + self.assertIsNone(result) + + @patch('DeviceManager.TemplateHandler.db') + def test_endpoint_get_templates(self, db_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.TemplateHandler.retrieve_auth_token") as auth_mock: + auth_mock.return_value = generate_token() + + with patch.object(TemplateHandler, "get_templates") as mock_templates: + mock_templates.return_value = {'pagination': {}, 'templates': []} + result = flask_get_templates() + self.assertEqual(result.status, '200 OK') + self.assertIsNotNone(result) + + @patch('DeviceManager.TemplateHandler.db') + def test_endpoint_delete_all_templates(self, db_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.TemplateHandler.retrieve_auth_token") as auth_mock: + auth_mock.return_value = generate_token() + result = flask_delete_all_templates() + self.assertIsNotNone(result) + self.assertEqual(result.status, '200 OK') + self.assertEqual(json.loads(result.response[0])['result'], 'ok') + + + @patch('DeviceManager.TemplateHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_endpoint_get_template(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.TemplateHandler.retrieve_auth_token") as auth_mock: + auth_mock.return_value = generate_token() + + with patch.object(TemplateHandler, "get_template") as mock_template: + mock_template.return_value = {'label': 'test_template', 'id':1, 'created': '2019-08-29T18:18:07.801602+00:00', 'attrs': []} + result = flask_get_template('test_template_id') + self.assertEqual(result.status, '200 OK') + self.assertIsNotNone(result.response) + + @patch('DeviceManager.TemplateHandler.db') + @patch('flask_sqlalchemy._QueryProperty.__get__') + def test_endpoint_delete_template(self, db_mock, query_property_getter_mock): + db_mock.session = AlchemyMagicMock() + with self.app.test_request_context(): + with patch("DeviceManager.TemplateHandler.retrieve_auth_token") as auth_mock: + auth_mock.return_value = generate_token() + + with patch.object(TemplateHandler, "remove_template") as mock_remove_template: + mock_remove_template.return_value = {'result': 'ok', 'removed': {'id': 1, 'label': 'test_template', 'created': '2019-08-29T18:18:07.801602+00:00'}} + result = flask_remove_template('test_template_id') + self.assertEqual(result.status, '200 OK') + self.assertEqual(json.loads(result.response[0])['result'], 'ok') diff --git a/tests/test_tenancy_manager.py b/tests/test_tenancy_manager.py new file mode 100644 index 0000000..118d67e --- /dev/null +++ b/tests/test_tenancy_manager.py @@ -0,0 +1,27 @@ +import pytest +import json +import unittest +from unittest.mock import Mock, MagicMock, patch +from sqlalchemy.sql import exists + +from DeviceManager.TenancyManager import install_triggers, create_tenant, init_tenant, list_tenants + +from alchemy_mock.mocking import AlchemyMagicMock, UnifiedAlchemyMagicMock + +class TestTenancyManager(unittest.TestCase): + + def test_install_triggers(self): + db_mock = AlchemyMagicMock() + self.assertIsNone(install_triggers(db_mock, 'admin')) + + def test_create_tenant(self): + db_mock = AlchemyMagicMock() + self.assertIsNone(create_tenant('admin', db_mock)) + + def test_init_tenant(self): + db_mock = Mock(return_value=None) + self.assertIsNone(init_tenant('admin', db_mock)) + + def test_list_tenants(self): + db_mock = AlchemyMagicMock() + self.assertFalse(list_tenants(db_mock)) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..0a56dd2 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,89 @@ +import pytest +import json +import unittest + +from flask import Flask +from DeviceManager.utils import format_response, get_pagination, get_allowed_service, decrypt, retrieve_auth_token +from DeviceManager.utils import HTTPRequestError + +from .token_test_generator import generate_token + + +class Request: + def __init__(self, data): + self.headers = data['headers'] + self.args = data['args'] + self.data = data['body'] + + +class TestUtils(unittest.TestCase): + + app = Flask(__name__) + + def test_format_response(self): + with self.app.test_request_context(): + result = format_response(200, 'Unity test of message formatter') + self.assertEqual(result.status, '200 OK') + self.assertEqual(json.loads(result.response[0])[ + 'message'], 'Unity test of message formatter') + + result = format_response(202) + self.assertEqual(result.status, '202 ACCEPTED') + self.assertEqual(json.loads(result.response[0])['message'], 'ok') + + result = format_response(404) + self.assertEqual(result.status, '404 NOT FOUND') + self.assertEqual(json.loads(result.response[0])[ + 'message'], 'Request failed') + + def test_get_pagination(self): + + args = {'page_size': 10,'page_num': 1} + req = {'headers': {'authorization': generate_token()},'args': args,'body': ''} + + page, per_page = get_pagination(Request(req)) + self.assertEqual(page, 1) + self.assertEqual(per_page, 10) + + with self.assertRaises(HTTPRequestError): + args = {'page_size': 10,'page_num': 0} + req = {'headers': {'authorization': generate_token()},'args': args,'body': ''} + page, per_page = get_pagination(Request(req)) + + with self.assertRaises(HTTPRequestError): + args = {'page_size': 0,'page_num': 2} + req = {'headers': {'authorization': generate_token()},'args': args,'body': ''} + page, per_page = get_pagination(Request(req)) + + def test_get_allowed_service(self): + token = generate_token() + + with self.assertRaises(ValueError): + get_allowed_service(None) + + result = get_allowed_service(token) + self.assertIsNotNone(result) + self.assertEqual(result, 'admin') + + with self.assertRaises(ValueError): + get_allowed_service('Is.Not_A_Valid_Token') + + + def test_decrypt(self): + result = decrypt(b"\xa97\xa4o\xba\xddx\xe0\xe9\x8f\xe2\xc4V\x85\xf7'") + self.assertEqual(result, b'') + + with self.assertRaises(ValueError): + result = decrypt('12345678') + + def test_retrieve_auth_token(self): + token = generate_token() + req = {'headers': {'authorization': token},'args': '','body': ''} + + result = retrieve_auth_token(Request(req)) + self.assertIsNotNone(result) + self.assertEqual(result, token) + + with self.assertRaises(HTTPRequestError): + req = {'headers': {},'args': '','body': ''} + result = retrieve_auth_token(Request(req)) diff --git a/tests/token_test_generator.py b/tests/token_test_generator.py new file mode 100644 index 0000000..93f7f6c --- /dev/null +++ b/tests/token_test_generator.py @@ -0,0 +1,17 @@ +import jwt + + +def generate_token(): + service = 'admin' + encode_data = { + 'userid': 1, 'name': 'Admin (superuser)', 'groups': [1], 'iat': + 1517339633, 'exp': 1517340053, 'email': 'admin@noemail.com', 'profile': + 'admin', 'iss': 'eGfIBvOLxz5aQxA92lFk5OExZmBMZDDh', 'service': service, + 'jti': '7e3086317df2c299cef280932da856e5', 'username': 'admin' + } + + jwt_token = jwt.encode(encode_data, 'secret', algorithm='HS256').decode() + + # Substitute Authorization with actual token + auth = 'Bearer ' + jwt_token + return auth