Commit 8ba7af09 authored by Karel Hanák's avatar Karel Hanák
Browse files

tls config error handling, tests, coverage


Signed-off-by: Karel Hanák's avatarKarel Hanák <karel.hanak@iqrf.org>
parent 5417df3b
Pipeline #4861 passed with stages
in 40 seconds
......@@ -16,5 +16,7 @@ build/*
dist/*
# other
.coverage
cover/*
test.json
testscript.py
\ No newline at end of file
......@@ -92,7 +92,7 @@ test:
- pip3 install -r requirements.txt
- pip3 install responses nose coverage pylint pycodestyle
script:
- nosetests ./test -v --with-coverage --cover-package=translator
- nosetests ./test -v --with-coverage --cover-package=translator --cover-inclusive --cover-erase --cover-html
- pycodestyle ./src/* --max-line-length=120
- pylint ./src/* --max-line-length=120
......
......@@ -16,7 +16,7 @@ run:
python3 src/translator/__main__.py -c config/config.json -l iqrf-gateway-translator.log
test:
nosetests ./test -v --with-coverage --cover-package=translator
nosetests ./test -v --with-coverage --cover-package=translator --cover-inclusive --cover-erase --cover-html
pycodestyle ./src/* --max-line-length=120
pylint ./src/* --max-line-length=120
......
......@@ -14,10 +14,10 @@
"pw": "",
"tls": {
"enabled": false,
"trustStore": "server-ca.crt",
"keyStore": "client.pem",
"privateKey": "client-privatekey.pem",
"requireBrokerCertificate": true
"trust_store": "",
"key_store": "",
"private_key": "",
"require_broker_certificate": true
}
}
}
#!/usr/bin/env python3
"""
Copyright 2020 MICRORISC s.r.o.
Copyright 2020-2021 MICRORISC s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
......@@ -24,7 +24,7 @@ from translator import __version__
from translator.cmqtt.mqtt_client import CMqtt
from translator.crest.rest_client import CRest
from translator.utils.config import Config
from translator.utils.tr_ret import TrRet
from translator.utils.error_codes import ErrorCodes
path = pathlib.Path(__file__).parent
......@@ -56,7 +56,7 @@ def main(): # pylint: disable=R1710
# read configuration
ret, config = Config(args.config, args.schema).read()
if ret != TrRet.OK:
if ret != ErrorCodes.OK:
logger.info('Service shutting down.')
return 1
......@@ -65,7 +65,7 @@ def main(): # pylint: disable=R1710
# initialize mqtt client
mqtt_client = CMqtt(config['mqtt']['cid'])
if mqtt_client.setup(config, rest_client) != TrRet.OK:
if mqtt_client.setup(config, rest_client) != ErrorCodes.OK:
logger.info('Service shutting down.')
return 1
......
"""
Copyright 2020 MICRORISC s.r.o.
Copyright 2020-2021 MICRORISC s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
......@@ -19,10 +19,12 @@ import logging
import pathlib
import re
import ssl
from os import path
import paho.mqtt.client as mqtt
from jsonschema import Draft4Validator
from requests.exceptions import RequestException
from translator.utils.tr_ret import TrRet
from translator.utils.error_codes import ErrorCodes
from translator.utils.exceptions import TlsFileNotFoundError
dir_path = pathlib.Path(__file__).parent.parent
......@@ -84,6 +86,14 @@ class CMqtt(mqtt.Client):
schema_data = json.load(file)
return Draft4Validator(schema_data)
def _check_tls_file(self, file_path):
if not file_path:
return None
if not path.exists(file_path):
self.logger.error('Provided file %s does not exist.', file_path)
raise TlsFileNotFoundError
return file_path
def _extract_endpoint(self, topic):
"""
Extracts endpoint from request topic
......@@ -99,7 +109,7 @@ class CMqtt(mqtt.Client):
If the request is not valid, the method publishes error message.
@param request MQTTMessage object representing the published message
@return TrRet.OK if valid, TrRet.SCHEMA_ERROR otherwise
@return ErrorCodes.OK if valid, ErrorCodes.SCHEMA_ERROR otherwise
"""
topic_endpoint = self._extract_endpoint(request.topic)
if re.fullmatch('(get|post|put|patch|delete)/.*', topic_endpoint) is None:
......@@ -107,7 +117,7 @@ class CMqtt(mqtt.Client):
self.logger.error(error_string)
resp_topic = request.topic.replace('requests', 'responses')
self.publish(resp_topic, error_string, 0, False)
return TrRet.TOPIC_ERROR
return ErrorCodes.TOPIC_ERROR
if request.payload:
try:
......@@ -121,7 +131,7 @@ class CMqtt(mqtt.Client):
self.publish(resp_topic, 'Invalid request format: ' +
json_error.msg + ' at line ' + str(json_error.lineno) +
', column ' + str(json_error.colno) + ' on\n' + json_error.doc, 0, False)
return TrRet.JSON_ERROR
return ErrorCodes.JSON_ERROR
schema_errors = sorted(self.validator.iter_errors(payload), key=str)
if schema_errors:
......@@ -131,9 +141,9 @@ class CMqtt(mqtt.Client):
error_string += error.message
resp_topic = request.topic.replace('requests', 'responses')
self.publish(resp_topic, error_string, 0, False)
return TrRet.SCHEMA_ERROR
return ErrorCodes.SCHEMA_ERROR
return TrRet.OK
return ErrorCodes.OK
def on_connect(self, client, userdata, flags, rc): # pylint: disable=W0221 W0236 W0613
"""
......@@ -174,7 +184,7 @@ class CMqtt(mqtt.Client):
"""
self.logger.info('MQTT broker published on ' + msg.topic + ':\n' + msg.payload.decode('utf-8'))
if self._validate_request(msg) == TrRet.OK:
if self._validate_request(msg) == ErrorCodes.OK:
self.endpoint = self._extract_endpoint(msg.topic)
method = self.endpoint.split('/', 1)[0]
uri = '/' + self.endpoint.split('/', 1)[1]
......@@ -254,28 +264,30 @@ class CMqtt(mqtt.Client):
self.username_pw_set(config['mqtt']['user'], config['mqtt']['pw'])
self.enable_logger(logger=self.logger)
# set up tls
tls_config = config['mqtt']['tls']
if tls_config['enabled']:
self.logger.info('Configuring client for TLS.')
self.tls_set(
ca_certs=tls_config['trustStore'],
certfile=tls_config['keyStore'],
keyfile=tls_config['privateKey'],
cert_reqs=ssl.CERT_REQUIRED if tls_config['requireBrokerCertificate'] else ssl.CERT_OPTIONAL,
tls_version=ssl.PROTOCOL_TLS
)
# load schema message
try:
# set up tls
tls_config = config['mqtt']['tls']
if tls_config['enabled']:
self.logger.info('Configuring client for TLS.')
self.tls_set(
ca_certs=self._check_tls_file(tls_config['trust_store']),
certfile=self._check_tls_file(tls_config['key_store']),
keyfile=self._check_tls_file(tls_config['private_key']),
cert_reqs=ssl.CERT_REQUIRED if tls_config['require_broker_certificate'] else ssl.CERT_OPTIONAL,
tls_version=ssl.PROTOCOL_TLS
)
# load schema message
self.validator = CMqtt.load_schema()
return TrRet.OK
return ErrorCodes.OK
except TlsFileNotFoundError:
return ErrorCodes.IO_ERROR
except IOError as io_error:
self.logger.error('%s on file %s', io_error.strerror, io_error.filename)
return TrRet.IO_ERROR
return ErrorCodes.IO_ERROR
except json.JSONDecodeError as json_error:
self.logger.error(
json_error.msg + ' at line ' + str(json_error.lineno) +
', column ' + str(json_error.colno) + ' on\n' + json_error.doc
)
return TrRet.JSON_ERROR
return ErrorCodes.JSON_ERROR
"""
Copyright 2020 MICRORISC s.r.o.
Copyright 2020-2021 MICRORISC s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
......
"""
Copyright 2020 MICRORISC s.r.o.
Copyright 2020-2021 MICRORISC s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
......@@ -18,7 +18,7 @@ import json
import logging
import sys
from jsonschema import Draft4Validator
from translator.utils.tr_ret import TrRet
from translator.utils.error_codes import ErrorCodes
class Config:
......@@ -82,16 +82,16 @@ class Config:
if schema_errors:
for error in schema_errors:
self.logger.error(error)
return TrRet.SCHEMA_ERROR, None
return ErrorCodes.SCHEMA_ERROR, None
self.logger.info('Configuration file successfully validated.')
return TrRet.OK, config
return ErrorCodes.OK, config
except IOError as io_error:
print('Failed to open file ' + io_error.filename, file=sys.stderr)
self.logger.error('%s on file %s', io_error.strerror, io_error.filename)
return TrRet.IO_ERROR, None
return ErrorCodes.IO_ERROR, None
except json.JSONDecodeError as json_error:
print('Failed to load json file due to invalid json file content.', file=sys.stderr)
self.logger.error(json_error.msg + ' at line ' + str(json_error.lineno) +
', column ' + str(json_error.colno) + ' on\n' + json_error.doc)
return TrRet.JSON_ERROR, None
return ErrorCodes.JSON_ERROR, None
"""
Copyright 2020 MICRORISC s.r.o.
Copyright 2020-2021 MICRORISC s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
......@@ -17,7 +17,7 @@ limitations under the License.
from enum import Enum
class TrRet(Enum):
class ErrorCodes(Enum):
"""
Enum class for Translator return codes.
"""
......
"""
Copyright 2020-2021 MICRORISC s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
class Error(Exception):
"""
Base class for derived exceptions
"""
class TlsFileNotFoundError(Error):
"""
Exception indicating that a TLS file was not found
"""
"""
Copyright 2020 MICRORISC s.r.o.
Copyright 2020-2021 MICRORISC s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
......
......@@ -10,7 +10,7 @@
"rest": {
"api_key": "",
"addr": "[::1]",
"port": 8080
"port": 80
},
"mqtt": {
"cid": "IQRF-MQTT-REST-Translator",
......@@ -21,10 +21,10 @@
"pw": "",
"tls": {
"enabled": false,
"trustStore": "server-ca.crt",
"keyStore": "client.pem",
"privateKey": "client-privatekey.pem",
"requireBrokerCertificate": true
"trust_store": "server-ca.crt",
"key_store": "client.pem",
"private_key": "client-private_key.pem",
"require_broker_certificate": true
}
}
}
......@@ -142,17 +142,17 @@
"title": "MQTT TLS configuration",
"example": {
"enabled": false,
"trustStore": "server-ca.crt",
"keyStore": "client.pem",
"privateKey": "client-privatekey.pem",
"requireBrokerCertificate": true
"trust_store": "server-ca.crt",
"key_store": "client.pem",
"private_key": "client-private_key.pem",
"require_broker_certificate": true
},
"required": [
"enabled",
"trustStore",
"keyStore",
"privateKey",
"requireBrokerCertificate"
"trust_store",
"key_store",
"private_key",
"require_broker_certificate"
],
"additionalProperties": false,
"properties": {
......@@ -162,26 +162,26 @@
"title": "TLS enabled",
"example": false
},
"trustStore": {
"$id": "#/properties/mqtt/tls/properties/trustStore",
"trust_store": {
"$id": "#/properties/mqtt/tls/properties/trust_store",
"type": "string",
"title": "MQTT broker certificate",
"example": "server-ca.crt"
},
"keyStore": {
"$id": "#/properties/mqtt/tls/properties/keyStore",
"key_store": {
"$id": "#/properties/mqtt/tls/properties/key_store",
"type": "string",
"title": "MQTT client certificate",
"example": "client.pem"
},
"privateKey": {
"$id": "#/properties/mqtt/tls/properties/privateKey",
"private_key": {
"$id": "#/properties/mqtt/tls/properties/private_key",
"type": "string",
"title": "MQTT client private key",
"example": "client-privatekey.pem"
"example": "client-private_key.pem"
},
"requireBrokerCertificate": {
"$id": "#/properties/mqtt/tls/properties/requireBrokerCertificate",
"require_broker_certificate": {
"$id": "#/properties/mqtt/tls/properties/require_broker_certificate",
"type": "boolean",
"title": "Require certificate from broker?",
"example": true
......
-----BEGIN CERTIFICATE-----
MIIC+zCCAeOgAwIBAgIUJ9rMm02aRkQkAApEh1XHC2SbpJcwDQYJKoZIhvcNAQEL
BQAwDTELMAkGA1UEBhMCQ1owHhcNMjEwMjI4MDgzMDA0WhcNNDgwNzE2MDgzMDA0
WjANMQswCQYDVQQGEwJDWjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMls8XkI3tO9Nmj797qD1HLh7lmm5441Og/xpoF/jqQ/X+AoTETd6dHJmpYrxYvX
TEdvgsz1Uv6oSbpWnARxu+INGYmOuHpL+pp4TiMTgNOnVqQk3J+LY1qrvG4QajLb
gsGGMrMSW0OGH7cmgAx0UPhYpVj42pLdAEYftF9DxHoIEBJMMxCN2pKDADEJFZBh
XE4/krVbJ2CUto6+/U+vfcLZ3pRMttPGJji8R4bcgq6aI3TfKiOI0sDFgJhbcKfy
IjhrvQ3IC5rhgYarPDv98iYqbMixeyeueNuVKSvq4kvdA3eIOM8r76yqSmWVybmM
kMxICMH7CdhpxsNqodwKpOMCAwEAAaNTMFEwHQYDVR0OBBYEFBYyQLEsY4ClRoDq
UwUFMhxgLWiyMB8GA1UdIwQYMBaAFBYyQLEsY4ClRoDqUwUFMhxgLWiyMA8GA1Ud
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEAY2qP885V4xu8cRfpyYwK/
0IFGbcgfPs8efq6NH7Zno9FBm4EKsh5IkPhRSEa/C5N/Kl4gkSmnTPeOftaAJD77
+EFBrFtqBSQAH9Lu9/xDGp8Slx7ArRStzNl+dDQYkQH5xQtHJNOyT7vuql+dIwXa
6Sjy0/7dI8KWbhRr8TRFcNpH80N0kO5dc49V+PlJbThXklL4oofVk//ItINM+OlA
ZfCb39Y/75FCN8/ii70WjHmu9NjEsDluIsnZ7ObmcpXOO7R82mo0hm6D9ibqFPED
PVexbl0YBKfjh/l5fZkNq6bX8CeFjEHj70d0GCh6gR7XT8j7i6pFMSJ/wfEYLYQ=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDKGylOG+ldMZEE
ZJBc9gcw9pDilYvgKzzBPcbBcI9M5zyRQG00ZCo7tayl+FTdlJZOnX2GdxAI2m+Y
XhzH/YKM6IqMvFybLYMc1iloR+jR4YhNf8Hv+Md5vHrFELSW5uy/ZrcSmozFyIQO
YEHqJDJ0d4jQhZIZGwMj4t07mxVdvD+VRgP5MPUpSth+5tuuauHtrgnksGQJ/DVr
xFSGsZRm5iW9RPhYpKw2Mg1Skd8NZ7s9Fl1eXIlgb7sFuAtg+jc5vmoOOddbI11b
F5YYJfM/D+8x8XXXkPP4spdUV/my9WVcHTIEaA8UToRMduwX1HfzI9F/3y9HTEhd
8DfxQO8dAgMBAAECggEANiJEVAmh3jZyk2ykRmIX1wOUFzzbNHYgVdpILHQLzht5
26YYGhpiPZAR6Hq1sIwmHnSMtMGbZ+OqVHOQ3fiHSVrYEY46d84e+lBa0vW/5Me2
DJwVxEMsmuzjLXmmBW1hKHtIuhDvd+KXjIo33ZFIem2yFbhRQ6w6kRgpDdgiRvK1
Bv/G6gSDGFKhdNCHqtzMtAIIPElRrrvHKVmuHruoCiiqaSuvvnt5N+jwYXaIdmbt
+FClSP77AsuI/WqeflZ9WRTsRbQbzbKdRwjHafCaN8HcXTR97ld7f0uknev+bhuj
c6csOq1hADK5nAYjfNSpI5o8EL6NcuO4GPsgpnoOAQKBgQD3LmgQWnU/Ih3oU/bi
jCsisldw2tya9fEC/G8UZm9wAQaDtPc6DZvMtz6QtrIxQ1JX6W0S74a7VNKyZ/pr
5GM9ZSBLYMFxRZqLi1X2S/xXYVHXVR5BbsAPGa7qh6Bso+eCTXKDdMjiH2ZineQ5
zPxTNmMkG7PqGAF724cwAxs6QQKBgQDRURFCBjoEiKcI98dHKiz4PeTuDlNVdX/A
HMcY+kk38U+2xc0aV5aDVjPLyrPt4pfCeyUGCo3+yXRRyFwPaozUj6mfAP/bsFj4
o1z2PQoEvvhWleg/MGpQxzn8SVbbV4iZepru72IatEbAqT029aUZ+IZ27AVDPRmy
ODhqK8Rl3QKBgQCzEM5Ykn9/tgJ+jV08L5kMMLCB1Dueku7/X8pEuBSgyk1i2pWD
W9pzSoiro36hi4i6oSLgZd9wFHnyvrq+sJxoHLtxf+2DVo4n60/h9pge4SS93y7k
7gt6pPt/cbN5IKAVWG/N5auljGPKq2FrsiFVUwAtZ1hGDQ/+H0HnOUjfQQKBgFmv
47Ynltp0dyXXjVKH1sbzNlFuX/ShKQD3E7Q3IgJPanmFHZHo77wMxprOmvQWADK7
syx4f0kppqaYNCIC5J77g5F2yyql0CmRTfbQRqer8BqhlubkZtl/0++uuQG46vXg
W9x/Q8jvb9WDbpBLBtgu7eas0MBzwLvlbP7JDBkdAoGAdEUbn7Ucqna5Hd5twy15
BTCvO+r7eJYQtvtEs/fJAKiIY8OQ+0Q/JOTgwczkf8lXHa2AP6As0SkHjkUywsRT
Z4sUacAFX/bj0AKDt3LJH7aIHHSk9EohZXbcOFscQ7BvITBeduVMguBj5dJYmt5f
cXSmrMA39acghOq7fnvVyig=
-----END PRIVATE KEY-----
"""
Copyright 2020-2021 MICRORISC s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import json
import pathlib
import unittest
import paho.mqtt.client as mqtt
from unittest.mock import Mock
from unittest.mock import Mock, patch
from translator.cmqtt.mqtt_client import CMqtt
from translator.utils.tr_ret import TrRet
from translator.utils.error_codes import ErrorCodes
from translator.utils.exceptions import TlsFileNotFoundError
test_json = {
"rest": {
'api_key': 'testapikey=',
'addr': 'localhost',
'port': 8080,
},
"mqtt": {
'cid': 'test',
'addr': 'localhost',
'port': 1883,
'request_topic': 'reqtopic/',
'response_topic': 'resptopic/',
'user': '',
'pw': '',
'tls': {
'enabled': False,
'trust_store': '',
'key_store': '',
'private_key': '',
'require_broker_certificate': True
}
}
}
class TestCMqtt(unittest.TestCase):
def setUp(self):
self.config = json.loads(self.read(pathlib.Path(__file__).parent.parent / 'utils/data/test_config.json'))
self.config = test_json
self.mqtt_client = CMqtt('Test client')
self.mqtt_client.setup(self.config, None)
self.request = Mock(spec=mqtt.MQTTMessage)
self.request.topic = 'reqtopic/post/test'
self.cert = pathlib.Path(__file__).parent / 'data/example.crt'
@staticmethod
def read(path):
......@@ -22,29 +64,52 @@ class TestCMqtt(unittest.TestCase):
return data
def test_setup_ok(self):
self.assertEqual(self.mqtt_client.setup(self.config, None), TrRet.OK)
self.assertEqual(self.mqtt_client.setup(self.config, None), ErrorCodes.OK)
def test_setup_tls_ok(self):
self.config['mqtt']['tls']['enabled'] = True
self.config['mqtt']['tls']['trust_store'] = self.cert
self.assertEqual(self.mqtt_client.setup(self.config, None), ErrorCodes.OK)
def test_setup_tls_error(self):
self.config['mqtt']['tls']['enabled'] = True
self.config['mqtt']['tls']['trust_store'] = 'nonexistent.crt'
self.assertEqual(self.mqtt_client.setup(self.config, None), ErrorCodes.IO_ERROR)
def test_check_tls_file_none(self):
self.assertIsNone(self.mqtt_client._check_tls_file(''))
def test_check_tls_file_nonexistent(self):
with self.assertRaises(TlsFileNotFoundError):
self.mqtt_client._check_tls_file('nonexistent.crt')
def test_check_tls_file_ok(self):
self.assertEqual(self.mqtt_client._check_tls_file(self.cert), self.cert)
def test_validate_request_none(self):
self.request.payload = None
self.assertEqual(self.mqtt_client._validate_request(self.request), TrRet.OK)
self.assertEqual(self.mqtt_client._validate_request(self.request), ErrorCodes.OK)
def test_validate_request_ok(self):
self.request.payload = b'\x7b\x22\x68\x65\x61\x64\x65\x72\x73\x22\x3a\x20\x7b\x22\x43\x6f\x6e\x74\x65\x6e\x74' \
b'\x2d\x54\x79\x70\x65\x22\x3a\x20\x22\x74\x65\x78\x74\x2f\x70\x6c\x61\x69\x6e\x22\x7d' \
b'\x2c\x20\x22\x62\x6f\x64\x79\x22\x3a\x20\x22\x74\x65\x73\x74\x22\x7d'
self.assertEqual(self.mqtt_client._validate_request(self.request), ErrorCodes.OK)
self.assertEqual(self.mqtt_client._validate_request(self.request), TrRet.OK)
def test_validate_request_topic_error(self):
self.request.payload = None
self.request.topic = 'reqtopic/post'
self.assertEqual(self.mqtt_client._validate_request(self.request), ErrorCodes.TOPIC_ERROR)
def test_validate_request_JSON_ERROR(self):
def test_validate_request_json_error(self):
self.request.payload = b'\x7b\x22\x74\x65\x73\x74\x2c\x7d'
self.assertEqual(self.mqtt_client._validate_request(self.request), TrRet.JSON_ERROR)
self.assertEqual(self.mqtt_client._validate_request(self.request), ErrorCodes.JSON_ERROR)
def test_validate_request_SCHEMA_ERROR(self):
def test_validate_request_schema_error(self):
self.request.payload = b'\x7b\x22\x68\x65\x61\x64\x65\x72\x73\x22\x3a\x20\x7b\x22' \
b'\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x54\x79\x70\x65\x22\x3a' \
b'\x20\x22\x74\x65\x78\x74\x2f\x70\x6c\x61\x69\x6e\x22\x7d\x7d'
self.assertEqual(self.mqtt_client._validate_request(self.request), TrRet.SCHEMA_ERROR)
self.assertEqual(self.mqtt_client._validate_request(self.request), ErrorCodes.SCHEMA_ERROR)
if __name__ == '__main__':
unittest.main()
"""
Copyright 2020-2021 MICRORISC s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from unittest.mock import MagicMock
import unittest
import requests
......
"""
Copyright 2020-2021 MICRORISC s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import pathlib
import unittest
from translator.utils.config import Config
from translator.utils.tr_ret import TrRet