diff --git a/.github/workflows/run-tests/action.yml b/.github/workflows/run-tests/action.yml index 4c047d5..15606f9 100644 --- a/.github/workflows/run-tests/action.yml +++ b/.github/workflows/run-tests/action.yml @@ -13,7 +13,6 @@ runs: uses: actions/setup-python@v4 with: python-version: ${{ inputs.python-version }} - cache: 'pip' - name: Install dependencies shell: ${{ inputs.terminal }} run: | diff --git a/README.md b/README.md index 0b38bb9..1d334c3 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,75 @@ $ python -m oxygen oxygen.gatling path/to/results.log Then `results_robot_output.xml` will be created under `path/to/`. +## Extending Oxygen: writing your own handler + +### [Read the developer guide on how to write your own handler](DEVGUIDE.md) + +### Configuring your handler to Oxygen + +Oxygen knows about different handlers based on the [`config.yml`](https://github.com/eficode/robotframework-oxygen/blob/master/config.yml) file. This configuration file can be interacted with through Oxygen's command line. + +The configuration has the following parts: +```yml +oxygen.junit: # Python module. Oxygen will use this key to try to import the handler + handler: JUnitHandler # Class that Oxygen will initiate after the handler is imported + keyword: run_junit # Keyword that should be used to run the other test tool + tags: # List of tags that by default should be added to the test cases converted with this handler + - oxygen-junit +oxygen.zap: + handler: ZAProxyHandler + keyword: run_zap + tags: oxygen-zap + accepted_risk_level: 2 # Handlers can have their own command line arguments + required_confidence_level: 1 # See [the development guide](DEVGUIDE.md) for more information +``` + +#### `--add-config` + +This argument is used to add new handler configuration to Oxygen: + +```bash +$ python -m oxygen --add-config path/to/your_handler_config.yml +``` + +This file is read and appended to the Oxygen's `config.yml`. Based on the key, Oxygen will try to import you handler. + +### `--reset-config` + +This argument is used to return Oxygen's `config.yml` back to the state it was when the tool was installed: + +```bash +$ python -m oxygen --reset-config +``` + +The command **does not** verify the operation from the user, so be careful. + +### `--print-config` + +This argument prints the current configuration of Oxygen: +```bash +$ python -m oxygen --print-config +Using config file: /path/to/oxygen/src/oxygen/config.yml +oxygen.gatling: + handler: GatlingHandler + keyword: run_gatling + tags: oxygen-gatling +oxygen.junit: + handler: JUnitHandler + keyword: run_junit + tags: + - oxygen-junit +oxygen.zap: + accepted_risk_level: 2 + handler: ZAProxyHandler + keyword: run_zap + required_confidence_level: 1 + tags: oxygen-zap + +$ +``` +Because you can add the configuration to the same handler multiple times, note that only the last entry is in effect. + # Developing Oxygen Clone the Oxygen repository to the environment where you want to the run the tool. @@ -107,7 +176,6 @@ $ invoke --list and the task file [`tasks.py`](https://github.com/eficode/robotframework-oxygen/blob/master/tasks.py). -[Read the developer guide on how to write your own handler](DEVGUIDE.md) # License diff --git a/requirements.txt b/requirements.txt index 7f9413f..da6a5c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,8 @@ mock>=2.0.0 invoke>=1.1.1 coverage>=5.1 testfixtures>=6.14.1 # needed for large dict comparisons to make sense of them -green>=3.1.3 # unit test runner +pytest>=7.4.2 +pytest-cov>=4.1.0 docutils>=0.16 # needed to generate library documentation with libdoc Pygments>=2.6.1 # this one too twine>=3.1.1 # needed for releasing to pypi diff --git a/src/oxygen/__main__.py b/src/oxygen/__main__.py index 5e62726..89cdd34 100644 --- a/src/oxygen/__main__.py +++ b/src/oxygen/__main__.py @@ -1,4 +1,4 @@ -from .oxygen import OxygenCLI +from .oxygen import OxygenCLI, main if __name__ == '__main__': - OxygenCLI().run() + main() diff --git a/src/oxygen/config.py b/src/oxygen/config.py index fdff003..b0fa146 100644 --- a/src/oxygen/config.py +++ b/src/oxygen/config.py @@ -1,3 +1,4 @@ -from os.path import abspath, dirname, join +from pathlib import Path -CONFIG_FILE = join(abspath(dirname(__file__)), 'config.yml') +CONFIG_FILE = Path(__file__).resolve().parent / 'config.yml' +ORIGINAL_CONFIG_FILE = Path(__file__).resolve().parent / 'config_original.yml' diff --git a/src/oxygen/config.yml b/src/oxygen/config.yml index b5c62df..481ab62 100644 --- a/src/oxygen/config.yml +++ b/src/oxygen/config.yml @@ -13,3 +13,4 @@ oxygen.zap: tags: oxygen-zap accepted_risk_level: 2 required_confidence_level: 1 + diff --git a/src/oxygen/config_original.yml b/src/oxygen/config_original.yml new file mode 100644 index 0000000..481ab62 --- /dev/null +++ b/src/oxygen/config_original.yml @@ -0,0 +1,16 @@ +oxygen.junit: + handler: JUnitHandler + keyword: run_junit + tags: + - oxygen-junit +oxygen.gatling: + handler: GatlingHandler + keyword: run_gatling + tags: oxygen-gatling +oxygen.zap: + handler: ZAProxyHandler + keyword: run_zap + tags: oxygen-zap + accepted_risk_level: 2 + required_confidence_level: 1 + diff --git a/src/oxygen/errors.py b/src/oxygen/errors.py index 932a080..4134f13 100644 --- a/src/oxygen/errors.py +++ b/src/oxygen/errors.py @@ -28,3 +28,7 @@ class ResultFileIsNotAFileException(Exception): class MismatchArgumentException(Exception): pass + + +class InvalidConfigurationException(Exception): + pass diff --git a/src/oxygen/oxygen.py b/src/oxygen/oxygen.py index dce6f85..524cb84 100644 --- a/src/oxygen/oxygen.py +++ b/src/oxygen/oxygen.py @@ -1,18 +1,22 @@ +import sys from argparse import ArgumentParser from datetime import datetime, timedelta from inspect import getdoc, signature from io import StringIO from pathlib import Path +from shutil import copy as copy_file from traceback import format_exception from robot.api import ExecutionResult, ResultVisitor, ResultWriter from robot.libraries.BuiltIn import BuiltIn from robot.errors import DataError -from yaml import load, FullLoader +from yaml import load, FullLoader, dump as dump_yaml -from .config import CONFIG_FILE -from .errors import OxygenException +from .config import CONFIG_FILE, ORIGINAL_CONFIG_FILE +from .errors import (OxygenException, + InvalidConfigurationException, + ResultFileNotFoundException) from .robot_interface import RobotInterface from .version import VERSION @@ -25,17 +29,35 @@ class OxygenCore(object): def __init__(self): - with open(CONFIG_FILE, 'r') as infile: + self._config = None + self._handlers = None + + @property + def config(self): + if self._config is None: + self.load_config(CONFIG_FILE) + return self._config + + def load_config(self, config_file): + with open(config_file, 'r') as infile: self._config = load(infile, Loader=FullLoader) - self._handlers = {} - self._register_handlers() + + @property + def handlers(self): + if self._handlers is None: + self._handlers = {} + self._register_handlers() + return self._handlers def _register_handlers(self): - for tool_name, config in self._config.items(): - handler_class = getattr(__import__(tool_name, - fromlist=[config['handler']]), - config['handler']) - handler = handler_class(config) + for tool_name, handler_config in self.config.items(): + try: + handler_class = getattr( + __import__(tool_name, fromlist=[handler_config['handler']]), + handler_config['handler']) + except ModuleNotFoundError as e: + raise InvalidConfigurationException(e) + handler = handler_class(handler_config) self._handlers[tool_name] = handler @@ -53,7 +75,7 @@ def __init__(self, data): def visit_test(self, test): failures = [] - for handler_type, handler in self._handlers.items(): + for handler_type, handler in self.handlers.items(): try: handler.check_for_keyword(test, self.data) except Exception as e: @@ -182,12 +204,12 @@ def __init__(self): def _fetch_handler(self, name): try: return next(filter(lambda h: h.keyword == name, - self._handlers.values())) + self.handlers.values())) except StopIteration: raise OxygenException('No handler for keyword "{}"'.format(name)) def get_keyword_names(self): - return list(handler.keyword for handler in self._handlers.values()) + return list(handler.keyword for handler in self.handlers.values()) def run_keyword(self, name, args, kwargs): handler = self._fetch_handler(name) @@ -210,31 +232,72 @@ class OxygenCLI(OxygenCore): OxygenCLI is a command line interface to transform one test result file to corresponding Robot Framework output.xml ''' - def parse_args(self, parser): + MAIN_LEVEL_CLI_ARGS = { + # we intentionally define `dest` here so we can filter arguments later + '--version': {'action': 'version', + 'dest': 'version'}, + '--add-config': {'type': Path, + 'metavar': 'FILE', + 'dest': 'add_config', + 'help': ('path to YAML file whose content is ' + 'appended to existing Oxygen handler ' + 'configuration')}, + '--reset-config': {'action': 'store_true', + 'dest': 'reset_config', + 'help': ('resets the Oxygen handler ' + 'configuration to a pristine, ' + 'as-freshly-installed version')}, + '--print-config': {'action': 'store_true', + 'dest': 'print_config', + 'help': ('prints current Oxygen handler ' + 'configuration')} + } + def add_arguments(self, parser): + # Add version number here to the arguments as it depends on OxygenCLI + # being initiated already + self.MAIN_LEVEL_CLI_ARGS['--version']['version'] = \ + f'%(prog)s {self.__version__}' + for flag, params in self.MAIN_LEVEL_CLI_ARGS.items(): + parser.add_argument(flag, **params) + subcommands = parser.add_subparsers() - for tool_name, tool_handler in self._handlers.items(): + for tool_name, tool_handler in self.handlers.items(): subcommand_parser = subcommands.add_parser(tool_name) for flags, params in tool_handler.cli().items(): subcommand_parser.add_argument(*flags, **params) subcommand_parser.set_defaults(func=tool_handler.parse_results) + + def parse_args(self, parser): return vars(parser.parse_args()) # returns a dictionary def get_output_filename(self, result_file): + if result_file is None: + raise ResultFileNotFoundException('You did not give any result ' + 'file to convert') filename = Path(result_file) filename = filename.with_suffix('.xml') robot_name = filename.stem + '_robot_output' + filename.suffix filename = filename.with_name(robot_name) return str(filename) - def run(self): - parser = ArgumentParser(prog='oxygen') - parser.add_argument('--version', - action='version', - version=f'%(prog)s {self.__version__}') - args = self.parse_args(parser) - if not args: - parser.error('No arguments given') - output_filename = self.get_output_filename(args['result_file']) + def append_config(self, new_config_path): + with open(new_config_path, 'r') as new_config: + with open(CONFIG_FILE, 'a') as old_config: + old_config.write(new_config.read()) + self.load_config(CONFIG_FILE) + + @staticmethod + def reset_config(): + copy_file(ORIGINAL_CONFIG_FILE, CONFIG_FILE) + OxygenCLI().load_config(CONFIG_FILE) + print('Oxygen handler configuration reset!') + + def print_config(self): + print(f'Using config file: {CONFIG_FILE}') + print(dump_yaml(self.config)) + + def convert_to_robot_result(self, args): + output_filename = self.get_output_filename(args.get('result_file')) parsed_results = args['func']( **{k: v for (k, v) in args.items() if not callable(v)}) robot_suite = RobotInterface().running.build_suite(parsed_results) @@ -243,6 +306,38 @@ def run(self): report=None, stdout=StringIO()) + def run(self): + parser = ArgumentParser(prog='oxygen') + self.add_arguments(parser) + args = self.parse_args(parser) + match args: + case {'add_config': new_config_path} if new_config_path is not None: + return self.append_config(new_config_path) + case {'print_config': should_print} if should_print: + return self.print_config() + case {'add_config': _, + 'reset_config': _, + 'print_config': _, + **rest} if not rest: # user is not trying to invoke main-level arguments, but do not provide other arguments either + parser.error('No arguments given') + case _: + # filter out arguments meant for other cases so that downstream + # handler does not need to know about them + filter_list = [v['dest'] for v in + self.MAIN_LEVEL_CLI_ARGS.values()] + filtered_args = {k: v for k, v in args.items() + if k not in filter_list} + return self.convert_to_robot_result(filtered_args) + +def main(): + '''Main CLI entrypoint + + Also used in __main__.py + ''' + if '--reset-config' in sys.argv: + OxygenCLI.reset_config() + sys.exit(0) + OxygenCLI().run() if __name__ == '__main__': - OxygenCLI().run() + main() diff --git a/tasks.py b/tasks.py index 9d47463..d2d54f6 100644 --- a/tasks.py +++ b/tasks.py @@ -34,17 +34,16 @@ def install(context, package=None): @task(iterable=['test'], help={ 'test': 'Limit unit test execution to specific tests. Must be given ' - 'multiple times to select several targets. See more: ' - 'https://github.com/CleanCut/green/blob/master/cli-options.txt#L5', + 'multiple times to select several targets.' }) def utest(context, test=None): - run(f'green {" ".join(test) if test else UNIT_TESTS}', + run(f'pytest {" ".join(test) if test else UNIT_TESTS} -q --disable-warnings', env={'PYTHONPATH': str(SRCPATH)}, pty=(not system() == 'Windows')) @task def coverage(context): - run(f'green -r {str(UNIT_TESTS)}', + run(f'pytest --cov {UNIT_TESTS}', env={'PYTHONPATH': str(SRCPATH)}, pty=(not system() == 'Windows')) run('coverage html') diff --git a/tests/atest/oxygen_junit_tests.robot b/tests/atest/oxygen_junit_tests.robot index 2cefb43..aacbf9f 100644 --- a/tests/atest/oxygen_junit_tests.robot +++ b/tests/atest/oxygen_junit_tests.robot @@ -16,9 +16,9 @@ Oxygen's unit tests should pass [Tags] oxygen-own-junit Remove file ${JUNIT XML FILE} File should not exist ${JUNIT XML FILE} - ${green}= Get command green + ${pytest}= Get command pytest Run JUnit ${JUNIT XML FILE} - ... ${green} -j ${JUNIT XML FILE} ${EXECDIR} + ... ${pytest} --junit-xml\=${JUNIT XML FILE} ${EXECDIR} File should exist ${JUNIT XML FILE} *** Keywords *** diff --git a/tests/utest/oxygen/test_oxygen_cli.py b/tests/utest/oxygen/test_oxygen_cli.py index 9833f4c..465086c 100644 --- a/tests/utest/oxygen/test_oxygen_cli.py +++ b/tests/utest/oxygen/test_oxygen_cli.py @@ -1,13 +1,15 @@ -from argparse import ArgumentParser +from argparse import ArgumentParser, Namespace from pathlib import Path -from subprocess import check_output, run +from subprocess import check_output, run, STDOUT, CalledProcessError +from tempfile import mkstemp from unittest import TestCase from unittest.mock import ANY, create_autospec, patch, Mock from xml.etree import ElementTree from robot.running.model import TestSuite -from oxygen.oxygen import OxygenCLI +from oxygen.oxygen import OxygenCLI, OxygenCore +from oxygen.config import CONFIG_FILE, ORIGINAL_CONFIG_FILE from ..helpers import RESOURCES_PATH @@ -23,6 +25,15 @@ class TestOxygenCLIEntryPoints(TestCase): quite a hack: https://coverage.readthedocs.io/en/latest/subprocess.html ''' + @classmethod + def tearDownClass(cls): + with open(ORIGINAL_CONFIG_FILE, 'r') as og: + with open(CONFIG_FILE, 'w') as config: + config.write(og.read()) + + def tearDown(self): + self.tearDownClass() + def test_main_level_entrypoint(self): self.verify_cli_help_text('python -m oxygen --help') self.verify_cli_help_text('python -m oxygen -h') @@ -39,8 +50,15 @@ def test_cli_with_no_args(self): self.assertEqual(proc.returncode, 2) self.assertIn('usage: oxygen', proc.stderr) + def _run(self, cmd): + try: + return check_output(cmd, text=True, shell=True, stderr=STDOUT) + except CalledProcessError as e: + print(e.output) # with this, you can actually see the command + raise # output, ie. why it failed + def verify_cli_help_text(self, cmd): - out = check_output(cmd, text=True, shell=True) + out = self._run(cmd) self.assertIn('usage: oxygen', out) self.assertIn('-h, --help', out) @@ -51,9 +69,7 @@ def test_junit_works_on_cli(self): if actual.exists(): actual.unlink() # delete file if exists - check_output(f'python -m oxygen oxygen.junit {target}', - text=True, - shell=True) + self._run(f'python -m oxygen oxygen.junit {target}') example_xml = ElementTree.parse(example).getroot() actual_xml = ElementTree.parse(actual).getroot() @@ -68,6 +84,65 @@ def test_junit_works_on_cli(self): self.assertEqual(example_stat.get('pass'), actual_stat.get('pass')) self.assertEqual(example_stat.get('fail'), actual_stat.get('fail')) + def _validate_handler_names(self, text): + for handler in ('JUnitHandler', 'GatlingHandler', 'ZAProxyHandler'): + self.assertIn(handler, text) + + def test_reset_config(self): + with open(CONFIG_FILE, 'w') as f: + f.write('complete: gibberish') + + self._run(f'python -m oxygen --reset-config') + + with open(CONFIG_FILE, 'r') as f: + config_content = f.read() + self.assertNotIn('complete: gibberish', config_content) + self._validate_handler_names(config_content) + + def test_print_config(self): + out = self._run('python -m oxygen --print-config') + + self.assertIn('Using config file', out) + self._validate_handler_names(out) + + def _make_test_config(self): + _, filepath = mkstemp() + with open(filepath, 'w') as f: + f.write('complete: gibberish') + return filepath + + def test_add_config(self): + filepath = self._make_test_config() + + self._run(f'python -m oxygen --add-config {filepath}') + + with open(CONFIG_FILE, 'r') as f: + config_content = f.read() + self._validate_handler_names(config_content) + self.assertIn('complete: gibberish', config_content) + + def _is_file_content(self, filepath, text): + with open(filepath, 'r') as f: + return bool(text in f.read()) + + def test_main_level_args_override_handler_args(self): + filepath = self._make_test_config() + + cmd = ('python -m oxygen {main_level_arg} ' + f'oxygen.junit {RESOURCES_PATH / "green-junit-example.xml"}') + + self._run(cmd.format(main_level_arg=f'--add-config {filepath}')) + self.assertTrue(self._is_file_content(CONFIG_FILE, 'complete: gibberish')) + + self._run(cmd.format(main_level_arg='--reset-config')) + self.assertFalse(self._is_file_content(CONFIG_FILE, + 'complete: gibberish')) + + + out = self._run(cmd.format(main_level_arg='--print-config')) + self._validate_handler_names(out) + self.assertNotIn('gibberish', out) + class TestOxygenCLI(TestCase): @@ -96,19 +171,42 @@ def test_run(self, mock_parse_args, mock_robot_iface): ) def test_parse_args(self): + '''verifies that `parse_args()` returns a dictionary''' + p = create_autospec(ArgumentParser) + p.parse_args.return_value = create_autospec(Namespace) + + retval = self.cli.parse_args(p) + + self.assertIsInstance(retval, dict) + + def test_add_arguments(self): mock_parser = create_autospec(ArgumentParser) m = Mock() mock_parser.add_subparsers.return_value = m - self.cli.parse_args(mock_parser) + self.cli.add_arguments(mock_parser) + # verify all main-level cli arguments were added + self.assertEqual(len(mock_parser.add_argument.call_args_list), 4) + # verify all built-in handlers were added self.assertEqual(len(m.add_parser.call_args_list), 3) + def _actual(self, path): + return self.cli.get_output_filename(path) + + def _expected(self, path): + return str(Path(path)) + def test_get_output_filename(self): - self.assertEqual(self.cli.get_output_filename('absolute/path/to.file'), - str(Path('absolute/path/to_robot_output.xml'))) - self.assertEqual(self.cli.get_output_filename('path/to/file.xml'), - str(Path('path/to/file_robot_output.xml'))) - self.assertEqual(self.cli.get_output_filename('file.extension'), - str(Path('file_robot_output.xml'))) + for act, exp in ((self._actual('/absolute/path/to.file'), + self._expected('/absolute/path/to_robot_output.xml')), + + (self._actual('path/to/file.xml'), + self._expected('path/to/file_robot_output.xml')), + + (self._actual('file.extension'), + self._expected('file_robot_output.xml'))): + self.assertEqual(act, exp) + + diff --git a/tests/utest/oxygen/test_oxygen_config_file.py b/tests/utest/oxygen/test_oxygen_config_file.py new file mode 100644 index 0000000..8a2bc24 --- /dev/null +++ b/tests/utest/oxygen/test_oxygen_config_file.py @@ -0,0 +1,9 @@ +from unittest import TestCase + +from oxygen.config import CONFIG_FILE, ORIGINAL_CONFIG_FILE + +class TestOxygenCLIEntryPoints(TestCase): + def test_config_and_config_original_match(self): + with open(CONFIG_FILE, 'r') as config: + with open(ORIGINAL_CONFIG_FILE, 'r') as original_config: + self.assertEqual(config.read(), original_config.read()) diff --git a/tests/utest/oxygen/test_oxygen_core.py b/tests/utest/oxygen/test_oxygen_core.py new file mode 100644 index 0000000..eae0352 --- /dev/null +++ b/tests/utest/oxygen/test_oxygen_core.py @@ -0,0 +1,15 @@ +from unittest import TestCase +from oxygen.oxygen import OxygenCore + + +class TestOxygenInitialization(TestCase): + def test_oxygen_core_initializes_without_loading_config(self): + ''' + OxygenCore and all it's subclasses lazy-load the configuration and, + consequently, the handlers. This test makes sure that is not + accidentally changed at some point + ''' + core = OxygenCore() + self.assertEqual(core._config, None) + self.assertEqual(core._handlers, None) + diff --git a/tests/utest/oxygen/test_oxygenlibrary.py b/tests/utest/oxygen/test_oxygenlibrary.py index 21b2774..61f4121 100644 --- a/tests/utest/oxygen/test_oxygenlibrary.py +++ b/tests/utest/oxygen/test_oxygenlibrary.py @@ -21,8 +21,8 @@ def test_initialization(self): def test_config_is_correct(self, mock_config): mock_config.return_value = get_config_as_file() - self.assertGreater(len(self.lib._handlers), 1) - for handler in self.lib._handlers.values(): + self.assertGreater(len(self.lib.handlers), 1) + for handler in self.lib.handlers.values(): self.assertTrue(isinstance(handler, BaseHandler)) self.assertTrue(any(hasattr(handler, kw) for kw in self.EXPECTED_KEYWORDS), diff --git a/tests/utest/robot_interface/test_time_conversions.py b/tests/utest/robot_interface/test_time_conversions.py index 4a4498b..bda2e68 100644 --- a/tests/utest/robot_interface/test_time_conversions.py +++ b/tests/utest/robot_interface/test_time_conversions.py @@ -1,5 +1,8 @@ +from datetime import timedelta from unittest import TestCase +from mock import patch + from oxygen.robot_interface import RobotInterface @@ -29,22 +32,22 @@ def test_should_be_associative(self): timestamp = self.interface.result.ms_to_timestamp(milliseconds) self.assertEqual(timestamp, '20180807 07:01:24.300000') - def _validate_timestamp(self, result): - timestamp = result.ms_to_timestamp(-10) + def _validate_timestamp(self, interface): + timestamp = interface.ms_to_timestamp(-10) expected = '19700101 00:00:00.990000' - import platform - # Particular Windows 10 calculates epoch differently ( T ʖ̯ T) - if platform.system() == 'Windows' and platform.version() == '10.0.19044': - expected = '19700101 02:00:00.990000' self.assertEqual(timestamp, expected) def test_ms_before_epoch_are_reset_to_epoch(self): from oxygen.robot4_interface import RobotResultInterface as RF4ResultIface - self._validate_timestamp(RF4ResultIface()) + with patch.object(RF4ResultIface, 'get_timezone_delta') as m: + m.return_value = timedelta(seconds=7200) + self._validate_timestamp(RF4ResultIface()) from oxygen.robot3_interface import RobotResultInterface as RF3ResultIface - self._validate_timestamp(RF3ResultIface()) + with patch.object(RF3ResultIface, 'get_timezone_delta') as m: + m.return_value = timedelta(seconds=7200) + self._validate_timestamp(RF3ResultIface()) class TestTimestampToMs(TestCase): diff --git a/tests/utest/zap/test_zap_cli.py b/tests/utest/zap/test_zap_cli.py index 76023c0..710dbf9 100644 --- a/tests/utest/zap/test_zap_cli.py +++ b/tests/utest/zap/test_zap_cli.py @@ -1,7 +1,10 @@ import sys + from unittest import TestCase from unittest.mock import ANY, Mock, create_autospec, patch + from robot.running.model import TestSuite + from oxygen.oxygen import OxygenCLI from ..helpers import RESOURCES_PATH @@ -11,11 +14,17 @@ class TestOxygenZapCLI(TestCase): def setUp(self): self.cli = OxygenCLI() - self.handler = self.cli._handlers["oxygen.zap"] + self.handler = self.cli.handlers["oxygen.zap"] self.expected_suite = create_autospec(TestSuite) self.mock = Mock() self.mock.running.build_suite = Mock(return_value=self.expected_suite) + def tearDown(self): + self.cli = None + self.handler = None + self.expected_suite = None + self.mock = None + def test_cli(self): self.assertEqual( self.handler.cli(), @@ -81,7 +90,7 @@ def test_cli_run_with_accepted_risk_level(self, mock_robot_iface): def test_cli_run_with_required_confidence_level(self, mock_robot_iface): mock_robot_iface.return_value = self.mock - cmd_args = f"oxygen oxygen.zap {self.ZAP_XML} " "--required-confidence-level 3" + cmd_args = f"oxygen oxygen.zap {self.ZAP_XML} --required-confidence-level 3" with patch.object(sys, "argv", cmd_args.split()): self.cli.run()