diff --git a/.gitignore b/.gitignore index 676f3d6..0c02b1f 100644 --- a/.gitignore +++ b/.gitignore @@ -165,3 +165,4 @@ cli-export-config.json cli-import-config.json robot_tests/ reports/ +openapi.yml diff --git a/CreatePiPWheel.bat b/CreatePiPWheel.bat deleted file mode 100755 index 956b7de..0000000 --- a/CreatePiPWheel.bat +++ /dev/null @@ -1,7 +0,0 @@ -check-manifest --update -pause -python -m build -pause -twine check dist\* -pause -twine upload dist/* diff --git a/CreatePiPWheel.sh b/CreatePiPWheel.sh deleted file mode 100755 index fd87eb0..0000000 --- a/CreatePiPWheel.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -check-manifest --update -python -m build -twine check dist\* -twine upload dist/* diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 2684f51..ce9ba51 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,25 +1,35 @@ # Contributing -## Setting up project for the first time -1. Write all dependencies into pyproject.toml -2. Create venv (python -m venv .venv) and activate it (".venv\scripts\activate" or "source .venv/bin/activate") -3. Update pip (python -m pip install -U pip) -3. Install pip-tools (pip install pip-tools) -4. Update/Create `requirements.txt` (when dependencies have been updated in pyproject.toml) - - with Development dependencies - > pip-compile --extra dev - - or only with Runtime dependencies - > pip-compile -5. Install dependencies (pip install -U -r requirements.txt) -6. Install project into local venv (pip install -e .[dev]) -## Creating whl and publish -1. Install 'setuptools' (pip install setuptools) -2. Run CreatePipWheel Script +## Setting up project for the first time +1. Create venv and activate it: + ```bash + python -m venv .venv + source .venv/bin/activate # Linux/macOS + .venv\scripts\activate # Windows + ``` +2. Install project with dev dependencies: + ```bash + pip install -e .[dev] + ``` -## Generate documentation in Word format - [Install Pandoc](https://pandoc.org/installing.html) +## Building and publishing -```shell -pandoc -s README.md -M title="imbus TestBench - Robot Code Generator" -M subtitle=Benutzerhandbuch -M toc-title=Inhaltsverzeichnis --toc -o Benutzerhandbuch.docx +```bash +check-manifest --update +python -m build +twine check dist/* +twine upload dist/* ``` + +## Updating the data model + +`testbench2robotframework/model.py` is generated from the TestBench OpenAPI spec using [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator). + +1. Download the OpenAPI YAML from the TestBench Swagger documentation (e.g. `openapi.yml`). +2. Generate the model (settings are in `pyproject.toml` under `[tool.datamodel-codegen]`): + ```bash + invoke generate-model --input openapi.yml + ``` + This runs `datamodel-codegen` and injects `__VERSION__` from the spec's `info.version` field into `model.py`. +3. Review the generated file diff --git a/ExampleConfiguration/json_config.json b/ExampleConfiguration/json_config.json index 9edf3dc..327a62f 100644 --- a/ExampleConfiguration/json_config.json +++ b/ExampleConfiguration/json_config.json @@ -10,8 +10,14 @@ "output-directory": "{root}/Generated", "log-suite-numbering": true, "resource-directory": "{root}/Resources", + "resource-directory-regex": ".*\\[Robot-Resources\\].*", "clean": true, "compound-keyword-logging": "GROUP", + "reference-behaviour": "ATTACHMENT", + "attachment-conflict-behaviour": "USE_EXISTING", + "testcase-splitting-regex": "^StopWithRestart\\..*", + "phase-pattern": "{testcase} : Phase {index}/{length}", + "metadata": {}, "library-mapping": { "SeleniumLibrary": "SeleniumLibrary timeout=10 implicit_wait=1 run_on_failure=Capture Page Screenshot", "SuperRemoteLibrary": "Remote http://127.0.0.1:8270 WITH NAME SuperRemoteLibrary" @@ -20,20 +26,15 @@ "MyKeywords": "{root}/../MyKeywords.resource", "MyOtherKeywords": "{resourceDirectory}/subdir/MyOtherKeywords.resource" }, - "forcedImport": { - "libraries": [ - ], - "resources": - [ - ], - "variables": [ - ] + "forced-import": { + "libraries": [], + "resources": [], + "variables": [] }, - "testCaseSplitPathRegEx": "^StopWithRestart\\..*", "console-logging": { "logLevel": "debug" }, "file-logging": { "logLevel": "info" - } + } } diff --git a/ExampleConfiguration/pyproject_example.toml b/ExampleConfiguration/pyproject_example.toml index 3ea3f3f..1ab38c7 100644 --- a/ExampleConfiguration/pyproject_example.toml +++ b/ExampleConfiguration/pyproject_example.toml @@ -7,9 +7,10 @@ fully-qualified = false output-directory = "{root}/Generated" log-suite-numbering = false clean = true -compound-keyword-logging = true +compound-keyword-logging = "GROUP" resource-directory = "{root}/Resources" -testcase-split-regex = ".*StopWithRestart.*" +resource-directory-regex = ".*\\[Robot-Resources\\].*" +testcase-splitting-regex = ".*StopWithRestart.*" phase-pattern = "{testcase} : Phase {index}/{length}" reference-behaviour = "ATTACHMENT" attachment-conflict-behaviour = "USE_EXISTING" diff --git a/ExampleConfiguration/toml_config.toml b/ExampleConfiguration/toml_config.toml index 3ea3f3f..1ab38c7 100644 --- a/ExampleConfiguration/toml_config.toml +++ b/ExampleConfiguration/toml_config.toml @@ -7,9 +7,10 @@ fully-qualified = false output-directory = "{root}/Generated" log-suite-numbering = false clean = true -compound-keyword-logging = true +compound-keyword-logging = "GROUP" resource-directory = "{root}/Resources" -testcase-split-regex = ".*StopWithRestart.*" +resource-directory-regex = ".*\\[Robot-Resources\\].*" +testcase-splitting-regex = ".*StopWithRestart.*" phase-pattern = "{testcase} : Phase {index}/{length}" reference-behaviour = "ATTACHMENT" attachment-conflict-behaviour = "USE_EXISTING" diff --git a/README.md b/README.md index 26c53d1..81396b9 100644 --- a/README.md +++ b/README.md @@ -1,131 +1,18 @@ # TestBench2RobotFramework -**TestBench2RobotFramework** is a CLI tool used to convert a TestBench JSON report into Robot Framework test suites and to write the execution results provided by Robot Framework to the TestBench report. +testbench2robotframework is a CLI tool to convert a TestBench JSON report into Robot Framework test suites and to write the execution results provided by Robot Framework back to the TestBench report. -## Installation +This can be used for automated test execution with Robot Framework, triggered by a CI/CD pipeline. -You can install TestBench2RobotFramework via pip using the following command: +## Documentation -```powershell -pip install testbench2robotframework -``` +The documentation is available in the [docs](./docs) folder of this repository. -Python 3.10 or higher is required to run this tool. +## Releases -## Remark -TestBench2RobotFramework requires TestBench version >= 4. If you're running an older version please contact the TestBench support for information on how to connect your Version of TestBench to Robot Framework. The TestBench Report can be either be exported via the TestBench Rest API with tools like the testbench-cli-reporter or directly from the client. +Releases are published on GitHub and available via [PyPI](https://pypi.org/project/testbench2robotframework/). -## Usage +## Requirements -TestBench2RobotFramework supports two main use cases, which are described in more detail in the following sections: - -1. Generating Robot Framework test suites from a TestBench report. -2. Fetching results from a Robot Framework output XML file and saving them back to a TestBench report. - -### Generating Robot Framework Test Suites -To generate Robot Framework test suites, use the `generate-tests` subcommand: - -```powershell -testbench2robotframework generate-tests TESTBENCH_REPORT -``` - -This command generates a Robot Framework test suite for each test case set specified in the `TESTBENCH_REPORT`. - -![](./images/testthemen.PNG) -![](./images/generated.PNG) - -The example above demonstrates how Robot Framework test suites are generated based on the *Test Theme Tree* defined in TestBench. - - - -#### Configuration - -There are multiple configuration options available for **TestBench2RobotFramework** that can be used to customize the generated test suites. Options can be specified either via the command line, in a `pyproject.toml` file or in a `robot.toml` file. - -To use options via the command line, the following syntax is used: - -```powershell -testbench2robotframework generate-tests [OPTIONS] TESTBENCH_REPORT -``` - -| Option | Description | -|--------|-------------| -| `-c`, `--config PATH` | Path to a configuration file for TestBench2RobotFramework. | -| `--clean` | Deletes all files present in the output-directory before new test suites are created. | -| `-d`, `--output-directory PATH` | Directory or ZIP archive containing the generated test suites. | -| `--compound-keyword-logging` | Mode for logging compound keywords. Options: `GROUP`, `COMMENT`, or `NONE`. | -| `--fully-qualified` | Calls Robot Framework keywords by their fully qualified names in the generated test suites. | -| `--log-suite-numbering` | Enables logging of the test suite numbering. | -| `--metadata` | Add extra metadata to the settings of the generated Robot Framework test suite. Provide entries as key:value pairs, where *key* is the metadata name and *value* is the corresponding value. Values may also be Python expressions. The special variable '$tcs' gives access to the TestBench Python model of the test case set. | -| `--resource-directory PATH` | Directory containing the Robot Framework resource files. | -| `--resource-directory-regex TEXT` | Regex that can be used to identify the TestBench Subdivision that corresponds to the . Resources will be imported relative to this subdivision based on the test elements structure in TestBench. | -| `--library-regex TEXT` | Regular expression used to identify TestBench subdivisions corresponding to Robot Framework libraries. | -| `--library-root TEXT` | TestBench root subdivision whose direct children correspond to Robot Framework libraries. | -| `--resource-regex TEXT` | Regular expression used to identify TestBench subdivisions corresponding to Robot Framework resources. | -| `--resource-root TEXT` | TestBench root subdivision whose direct children correspond to Robot Framework resources. | -| `--library-mapping TEXT` | Library import statement to use when a keyword from the specified TestBench subdivision is encountered. | -| `--resource-mapping TEXT` | Resource import statement to use when a keyword from the specified TestBench subdivision is encountered. | -| `--help` | Displays the help message and exits. | -| `--version` | Writes the TestBench2RobotFramework, Robot Framework and Python version to console. | - - -### Saving Robot Framework Results - -Saving the results requires a Robot Framework output XML file, along with the original TestBench report from which the test suites were generated. - -Use the following command: - -```powershell -testbench2robotframework fetch-results [OPTIONS] ROBOT_RESULT TESTBENCH_REPORT -``` - -| Option | Description | -|--------|-------------| -| `-c`, `--config PATH` | Path to a configuration file for TestBench2RobotFramework. | -| `-d`, `--output-directory PATH` | Path to the directory or ZIP file where the updated TestBench JSON report (with results) should be saved. | -| `--help` | Displays the help message and exits. | - - - -### Using pyproject.toml -All CLI options available for ``testbench2robotframework`` can also be defined in your ``pyproject.toml`` file, ``robot.toml``, or a workspace-local ``.robot.toml``. This offers a convenient way to store and reuse configuration settings, particularly in larger projects or automated environments. - -#### Example -```toml -[tool.testbench2robotframework] -library-regex = ['(?:.*\.)?(?P[^.]+?)\s*\[Robot-Library\].*'] -resource-regex = ['(?:.*\.)?(?P[^.]+?)\s*\[Robot-Resource\].*'] -library-root = ["RF", "RF-Library"] -resource-root = ["RF-Resource"] -fully-qualified = false -output-directory = "{root}/Generated" -log-suite-numbering = false -clean = true -compound-keyword-logging = GROUP -resource-directory = "{root}/Resources" -resource-directory-regex = ".*\\[Robot-Resources\\].*" -reference-behaviour = "ATTACHMENT" -attachment-conflict-behaviour = "USE_EXISTING" - -[tool.testbench2robotframework.library-mapping] -SeleniumLibrary = "SeleniumLibrary timeout=10 implicit_wait=1 run_on_failure=Capture Page Screenshot" -SuperRemoteLibrary = "Remote http://127.0.0.1:8270 WITH NAME SuperRemoteLibrary" - -[tool.testbench2robotframework.resource-mapping] -MyKeywords = "{root}/../MyKeywords.resource" -MyOtherKeywords = "{resourceDirectory}/subdir/MyOtherKeywords.resource" - -[tool.testbench2robotframework.forced-import] -libraries = ["test.py"] -resources = [] -variables = [] - -[tool.testbench2robotframework.console-logging] -logLevel = "INFO" -logFormat = "%(levelname)s: %(message)s" - -[tool.testbench2robotframework.file-logging] -logLevel = "DEBUG" -logFormat = "%(asctime)s - %(filename)s:%(lineno)d - %(levelname)8s - %(message)s" -fileName = "testbench2robotframework.log" -``` +- Python 3.10 or higher +- TestBench version >= 4 diff --git a/atest/robot/libs/json_config.py b/atest/robot/libs/json_config.py index 1a72a45..5411379 100644 --- a/atest/robot/libs/json_config.py +++ b/atest/robot/libs/json_config.py @@ -43,4 +43,3 @@ def create_json_configuration_file(path: Path): with path.open("w") as json_file: json.dump(data, json_file, indent=2) - diff --git a/atest/robot/libs/pyproject_config.py b/atest/robot/libs/pyproject_config.py index 7437d47..f40ba38 100644 --- a/atest/robot/libs/pyproject_config.py +++ b/atest/robot/libs/pyproject_config.py @@ -1,13 +1,8 @@ import tomli_w -data = { - "tool": { - "testbench2robotframework": { - "generationDirectory": "{root}/toml_config_tests" - } - } -} +data = {"tool": {"testbench2robotframework": {"generationDirectory": "{root}/toml_config_tests"}}} + def create_toml_configuration_file(): with open("pyproject.toml", "w") as toml_file: - toml_file.write(tomli_w.dumps(data)) \ No newline at end of file + toml_file.write(tomli_w.dumps(data)) diff --git a/create_json_schema.py b/create_json_schema.py index 0a1bd91..c678fd8 100644 --- a/create_json_schema.py +++ b/create_json_schema.py @@ -1,20 +1,24 @@ import json import re - -with open("testbench2robotframework/model.py", "r", encoding="utf8") as model_py: +with open("testbench2robotframework/model.py", encoding="utf8") as model_py: model_str = model_py.read() pydantic_model = re.sub(r"(@dataclass\n)(class .*?)(:)", r"\2(BaseModel)\3", model_str) pydantic_model = re.sub(r"( {4}@classmethod.*?)(\nclass|$)", r"\2", pydantic_model, flags=re.DOTALL) -pydantic_model = re.sub(r"from dataclasses import dataclass", r"from pydantic import BaseModel", pydantic_model, flags=re.DOTALL) +pydantic_model = re.sub( + r"from dataclasses import dataclass", + r"from pydantic import BaseModel", + pydantic_model, + flags=re.DOTALL, +) with open("pydantic_model.py", "w", encoding="utf8") as pydantic_model_py: pydantic_model_py.write(pydantic_model) from pydantic_model import * -from yaml import dump, Dumper +from yaml import Dumper, dump with open("model.json", "w") as schema: schema.write(json.dumps(AllModels.model_json_schema(), indent=2)) diff --git a/docs/configuration/_category_.json b/docs/configuration/_category_.json new file mode 100644 index 0000000..6959ebd --- /dev/null +++ b/docs/configuration/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Configuration", + "position": 4 +} \ No newline at end of file diff --git a/docs/configuration/cli_options.md b/docs/configuration/cli_options.md new file mode 100644 index 0000000..3b91e06 --- /dev/null +++ b/docs/configuration/cli_options.md @@ -0,0 +1,112 @@ +--- +sidebar_position: 2 +--- + +# CLI Options Reference + +Complete reference for all command-line options available in TestBench2RobotFramework. + +## Global Options + +These options are available for all commands: + +| Option | Description | +|--------|-------------| +| `--help` | Displays the help message and exits. | +| `--version` | Writes the TestBench2RobotFramework, Robot Framework and Python version to console. | +| `-c`, `--config PATH` | Path to a configuration file for TestBench2RobotFramework. | + +## generate-tests Options + +Options specific to the `generate-tests` subcommand: + +### Output Options + +| Option | Type | Description | +|--------|------|-------------| +| `-d`, `--output-directory PATH` | Path | Directory or ZIP archive containing the generated test suites. | +| `--clean` | Flag | Deletes all files present in the output-directory before new test suites are created. | + +### Keyword & Logging Options + +| Option | Type | Description | +|--------|------|-------------| +| `--compound-keyword-logging` | Choice | Mode for logging compound keywords. Options: `GROUP`, `COMMENT`, or `NONE`. | +| `--fully-qualified` | Flag | Calls Robot Framework keywords by their fully qualified names in the generated test suites. | +| `--log-suite-numbering` | Flag | Enables logging of the test suite numbering. | +| `--metadata TEXT` | Text | Add extra metadata to the settings of the generated Robot Framework test suite. Provide entries as `key:value` pairs. | + +### Resource & Library Options + +| Option | Type | Description | +|--------|------|-------------| +| `--resource-directory PATH` | Path | Directory containing the Robot Framework resource files. | +| `--resource-directory-regex TEXT` | Regex | Regex that can be used to identify the TestBench Subdivision that corresponds to the resource-directory. Resources will be imported relative to this subdivision based on the test elements structure in TestBench. | +| `--library-regex TEXT` | Regex | Regular expression used to identify TestBench subdivisions corresponding to Robot Framework libraries. | +| `--library-root TEXT` | Text | TestBench root subdivision whose direct children correspond to Robot Framework libraries. | +| `--resource-regex TEXT` | Regex | Regular expression used to identify TestBench subdivisions corresponding to Robot Framework resources. | +| `--resource-root TEXT` | Text | TestBench root subdivision whose direct children correspond to Robot Framework resources. | +| `--library-mapping TEXT` | Text | Library import statement to use when a keyword from the specified TestBench subdivision is encountered. | +| `--resource-mapping TEXT` | Text | Resource import statement to use when a keyword from the specified TestBench subdivision is encountered. | + +## fetch-results Options + +Options specific to the `fetch-results` subcommand: + +| Option | Type | Description | +|--------|------|-------------| +| `-d`, `--output-directory PATH` | Path | Path to the directory or ZIP file where the updated TestBench JSON report (with results) should be saved. | + +## Usage Examples + +### Display Help + +```powershell +testbench2robotframework --help +testbench2robotframework generate-tests --help +testbench2robotframework fetch-results --help +``` + +### Check Version + +```powershell +testbench2robotframework --version +``` + +### Generate Tests with Multiple Options + +```powershell +testbench2robotframework generate-tests \ + --clean \ + -d ./Generated \ + --compound-keyword-logging GROUP \ + --fully-qualified \ + --log-suite-numbering \ + my_report.json +``` + +### Fetch Results with Custom Output + +```powershell +testbench2robotframework fetch-results \ + -d ./updated_reports \ + output.xml \ + testbench_report.json +``` + +### Using Configuration File + +```powershell +testbench2robotframework generate-tests -c config.toml my_report.json +``` + +## Option Priority + +When the same option is specified in multiple places, the priority order is: + +1. **Command-line options** (highest priority) +2. **Workspace-local `.robot.toml`** +3. **Project `robot.toml` or `pyproject.toml`** +4. **Default values** (lowest priority) + + diff --git a/docs/configuration/overview.md b/docs/configuration/overview.md new file mode 100644 index 0000000..7dd63b5 --- /dev/null +++ b/docs/configuration/overview.md @@ -0,0 +1,93 @@ +--- +sidebar_position: 1 +--- + +# Configuration Overview + +TestBench2RobotFramework offers flexible configuration options to customize the behavior of test generation and result fetching. + +## Configuration Methods + +You can configure TestBench2RobotFramework using three different methods: + +### 1. Command-Line Options + +Pass options directly when running commands: + +```powershell +testbench2robotframework generate-tests --clean -d ./Generated my_report.json +``` + +✅ **Best for:** Quick, one-time runs and overriding specific settings + +### 2. Configuration Files + +Store settings in configuration files for reusable configurations: + +- `pyproject.toml` - Python project configuration +- `robot.toml` - Robot Framework specific configuration +- `.robot.toml` - Workspace-local configuration + +✅ **Best for:** Team projects, consistent settings, complex configurations + +TestBench2RobotFramework will automatically detect and apply settings from these files when present. + +### 3. Mixed Approach + +Combine both methods - command-line options override configuration file settings: + +```powershell +testbench2robotframework generate-tests -c config.toml --clean my_report.json +``` + +## Configuration Hierarchy + +When multiple configuration methods are used, settings are applied in the following order (later overrides earlier): + +1. Default values +2. `pyproject.toml` or `robot.toml` +3. `.robot.toml` (workspace-local) +4. Command-line options + +## Common Configuration Options + +### Output Settings + +- `output-directory` - Where to save generated files or results +- `clean` - Delete existing files before generating new ones +- `fully-qualified` - Enable/disable fully qualified keyword names in generated test suites +- `compound-keyword-logging` - Control compound TestBench keyword logging (`GROUP`, `COMMENT`, `NONE`) +- `log-suite-numbering` - Enable/disable suite numbering in generated file names +- `testcase-splitting-regex` - Regular expression to split test cases at matching interactions +- `phase-pattern` - Pattern for naming test case phases when splitting + +### Library & Resource Mapping + +- `library-regex` - Pattern to identify TestBench subdivisions as libraries +- `resource-regex` - Pattern to identify TestBench subdivisions as resources +- `library-root` - Root subdivision for libraries +- `resource-root` - Root subdivision for resources +- `library-mapping` - Custom library import statements +- `resource-mapping` - Custom resource import statements +- `resource-directory` - Directory containing Robot Framework resource files +- `resource-directory-regex` - Regex to identify the TestBench subdivision corresponding to the resource directory + +### Metadata + +- `metadata` - Extra metadata key-value pairs added to generated test suite settings + +### Forced Imports + +- `forced-import` - Force import of specific libraries, resources, or variables in every generated suite + +### Attachment & Reference Handling + +- `reference-behaviour` - How to handle references (`ATTACHMENT`, `REFERENCE`, `NONE`) +- `attachment-conflict-behaviour` - How to handle attachment conflicts (`ERROR`, `USE_NEW`, `USE_EXISTING`, `RENAME_NEW`) + +### Logging + +- `console-logging` - Console output log level and format +- `file-logging` - File-based log level, format, and file name + + diff --git a/docs/configuration/pyproject_config.md b/docs/configuration/pyproject_config.md new file mode 100644 index 0000000..cb3171a --- /dev/null +++ b/docs/configuration/pyproject_config.md @@ -0,0 +1,171 @@ +--- +sidebar_position: 3 +--- + +# pyproject.toml Configuration + +All CLI options available for `testbench2robotframework` can be defined in your `pyproject.toml` file, `robot.toml`, or a workspace-local `.robot.toml`. This offers a convenient way to store and reuse configuration settings, particularly in larger projects or automated environments. + +## Basic Structure + +Add a `[tool.testbench2robotframework]` section to your `pyproject.toml`: + +```toml +[tool.testbench2robotframework] +# Your configuration options here +``` + +## Complete Configuration Example + +Here's a comprehensive example with all available options: + +```toml +[tool.testbench2robotframework] +library-regex = ['(?:.*\.)?(?P[^.]+?)\s*\[Robot-Library\].*'] +resource-regex = ['(?:.*\.)?(?P[^.]+?)\s*\[Robot-Resource\].*'] +library-root = ["RF", "RF-Library"] +resource-root = ["RF-Resource"] +fully-qualified = false +output-directory = "{root}/Generated" +log-suite-numbering = false +clean = true +compound-keyword-logging = "GROUP" +resource-directory = "{root}/Resources" +resource-directory-regex = ".*\\[Robot-Resources\\].*" +reference-behaviour = "ATTACHMENT" +attachment-conflict-behaviour = "USE_EXISTING" +testcase-splitting-regex = ".*StopWithRestart.*" +phase-pattern = "{testcase} : Phase {index}/{length}" + +[tool.testbench2robotframework.metadata] +# Example: MyKey = "my value" + +[tool.testbench2robotframework.library-mapping] +SeleniumLibrary = "SeleniumLibrary timeout=10 implicit_wait=1 run_on_failure=Capture Page Screenshot" +SuperRemoteLibrary = "Remote http://127.0.0.1:8270 WITH NAME SuperRemoteLibrary" + +[tool.testbench2robotframework.resource-mapping] +MyKeywords = "{root}/../MyKeywords.resource" +MyOtherKeywords = "{resourceDirectory}/subdir/MyOtherKeywords.resource" + +[tool.testbench2robotframework.forced-import] +libraries = ["test.py"] +resources = [] +variables = [] + +[tool.testbench2robotframework.console-logging] +logLevel = "INFO" +logFormat = "%(levelname)s: %(message)s" + +[tool.testbench2robotframework.file-logging] +logLevel = "DEBUG" +logFormat = "%(asctime)s - %(filename)s:%(lineno)d - %(levelname)8s - %(message)s" +fileName = "testbench2robotframework.log" +``` + +## Configuration Sections + +### Main Configuration + +The main `[tool.testbench2robotframework]` section contains general settings: + +```toml +[tool.testbench2robotframework] +output-directory = "{root}/Generated" +clean = true +fully-qualified = false +log-suite-numbering = false +compound-keyword-logging = "GROUP" +testcase-splitting-regex = ".*StopWithRestart.*" +phase-pattern = "{testcase} : Phase {index}/{length}" +``` + +### Metadata + +Add custom metadata to the settings of generated Robot Framework test suites: + +```toml +[tool.testbench2robotframework.metadata] +MyMetadata = "some value" +``` + +### Library Mapping + +Define custom import statements for libraries: + +```toml +[tool.testbench2robotframework.library-mapping] +SeleniumLibrary = "SeleniumLibrary timeout=10 implicit_wait=1" +MyLibrary = "MyLibrary arg1=value1 arg2=value2" +``` + +### Resource Mapping + +Define custom import statements for resources: + +```toml +[tool.testbench2robotframework.resource-mapping] +MyKeywords = "{root}/../MyKeywords.resource" +CommonKeywords = "{resourceDirectory}/common/keywords.resource" +``` + +### Forced Imports + +Force specific libraries, resources, or variables to be imported: + +```toml +[tool.testbench2robotframework.forced-import] +libraries = ["BuiltIn", "Collections"] +resources = ["common.resource"] +variables = ["variables.py"] +``` + +### Console Logging + +Configure console output: + +```toml +[tool.testbench2robotframework.console-logging] +logLevel = "INFO" +logFormat = "%(levelname)s: %(message)s" +``` + +### File Logging + +Configure file-based logging: + +```toml +[tool.testbench2robotframework.file-logging] +logLevel = "DEBUG" +logFormat = "%(asctime)s - %(filename)s:%(lineno)d - %(levelname)8s - %(message)s" +fileName = "testbench2robotframework.log" +``` + +## Variable Placeholders + +You can use the following placeholders in configuration values: + +- `{root}` - Project root directory +- `{resourceDirectory}` - Configured resource directory path + +**Example:** +```toml +output-directory = "{root}/Generated" +resource-directory = "{root}/Resources" +``` + +## Using robot.toml + +Instead of `pyproject.toml`, you can use `robot.toml` with the same structure: + +```toml +[tool.testbench2robotframework] +output-directory = "./Generated" +clean = true +``` + +## Workspace-Local Configuration + +Create a `.robot.toml` file in your workspace for project-specific settings that override the global configuration. + + diff --git a/docs/getting_started/_category_.json b/docs/getting_started/_category_.json new file mode 100644 index 0000000..49462ce --- /dev/null +++ b/docs/getting_started/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Getting started", + "position": 2 +} \ No newline at end of file diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md new file mode 100644 index 0000000..938ed3d --- /dev/null +++ b/docs/getting_started/installation.md @@ -0,0 +1,31 @@ +--- +sidebar_position: 1 +--- + +# Installation + +## Prerequisites + +Before installing TestBench2RobotFramework, ensure you have: + +- **Python 3.10 or higher** installed on your system +- **pip** package manager (usually comes with Python) + +## Install via pip + +You can install TestBench2RobotFramework via pip using the following command: + +```powershell +pip install testbench2robotframework +``` + +## Verify Installation + +After installation, verify that the tool is correctly installed by checking the version: + +```powershell +testbench2robotframework --version +``` + +This command will display the TestBench2RobotFramework, Robot Framework, and Python versions. + diff --git a/docs/getting_started/quick_start.md b/docs/getting_started/quick_start.md new file mode 100644 index 0000000..09fcebb --- /dev/null +++ b/docs/getting_started/quick_start.md @@ -0,0 +1,54 @@ +--- +sidebar_position: 2 +--- + +# Quick Start + +This guide will help you get started with TestBench2RobotFramework quickly. + +## Basic Workflow + +TestBench2RobotFramework supports two main operations: + +### 1. Generate Robot Framework Test Suites + +Convert a TestBench report into Robot Framework test suites: + +```powershell +testbench2robotframework generate-tests TESTBENCH_REPORT +``` + +This command generates a Robot Framework test suite for each test case set specified in the `TESTBENCH_REPORT`. + +**Example:** +```powershell +testbench2robotframework generate-tests my_testbench_report.zip +``` + +### 2. Fetch and Save Results + +After executing your Robot Framework tests, save the results back to the TestBench report: + +```powershell +testbench2robotframework fetch-results ROBOT_RESULT TESTBENCH_REPORT +``` + +**Example:** +```powershell +testbench2robotframework fetch-results output.xml my_testbench_report.zip +``` + + +## Common Options + +Here are some frequently used options: + +- `-d, --output-directory PATH` - Specify where to save generated files +- `--clean` - Delete existing files before generating new ones +- `-c, --config PATH` - Use a configuration file + +**Example with options:** +```powershell +testbench2robotframework generate-tests -d ./Generated --clean testbench_report.zip +``` + diff --git a/docs/intro.md b/docs/intro.md new file mode 100644 index 0000000..f16d903 --- /dev/null +++ b/docs/intro.md @@ -0,0 +1,36 @@ +--- +sidebar_position: 1 +--- + +# TestBench2RobotFramework + +**TestBench2RobotFramework** is a CLI tool used to convert a TestBench JSON report into Robot Framework test suites and to write the execution results provided by Robot Framework to the TestBench report. + +## Overview + +This tool supports two main use cases: + +1. **Generating Robot Framework test suites** from a TestBench report +2. **Fetching results** from a Robot Framework output XML file and saving them back to a TestBench report + +## Key Features + +- ✅ Convert TestBench reports to Robot Framework test suites +- ✅ Synchronization of Robot Framework test results +- ✅ Flexible configuration via CLI, `pyproject.toml`, or `robot.toml` +- ✅ Support for custom libraries and resources + +## Requirements + +- **Python 3.10 or higher** +- **TestBench version >= 4** + +:::info +If you're running an older version of TestBench, please contact the TestBench support for information on how to connect your version of TestBench to Robot Framework. +::: + +## TestBench Report + +The TestBench Report can be either exported via the TestBench Rest API with tools like the testbench-cli-reporter or directly from the client. + + diff --git a/docs/usage/_category_.json b/docs/usage/_category_.json new file mode 100644 index 0000000..09dee0a --- /dev/null +++ b/docs/usage/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Usage", + "position": 3 +} \ No newline at end of file diff --git a/docs/usage/fetch_results.md b/docs/usage/fetch_results.md new file mode 100644 index 0000000..d339dff --- /dev/null +++ b/docs/usage/fetch_results.md @@ -0,0 +1,77 @@ +--- +sidebar_position: 2 +--- + +# Fetching Results + +Learn how to save Robot Framework execution results back to TestBench reports. + +## Overview + +After executing your Robot Framework tests, you can write the results back to the TestBench report. This requires: + +1. A Robot Framework output XML file (typically `output.xml`) +2. The original TestBench report from which the test suites were generated + +## Basic Command + +Use the `fetch-results` subcommand: + +```powershell +testbench2robotframework fetch-results ROBOT_RESULT TESTBENCH_REPORT +``` + +## Command-Line Options + +The `fetch-results` command supports the following options: + +| Option | Description | +|--------|-------------| +| `-c`, `--config PATH` | Path to a configuration file for TestBench2RobotFramework. | +| `-d`, `--output-directory PATH` | Path to the directory or ZIP file where the updated TestBench JSON report (with results) should be saved. | +| `--help` | Displays the help message and exits. | + +## Examples + +### Basic Result Fetch + +```powershell +testbench2robotframework fetch-results output.xml my_testbench_report.zip +``` + +### With Custom Output Directory + +```powershell +testbench2robotframework fetch-results -d ./results output.xml my_testbench_report.zip +``` + +### With Configuration File + +```powershell +testbench2robotframework fetch-results -c config.toml output.xml my_testbench_report.zip +``` + +## Workflow Example + +Here's a complete workflow from test generation to result fetching: + +```powershell +# 1. Generate test suites from TestBench report +testbench2robotframework generate-tests -d ./Generated testbench_report.zip + +# 2. Execute the generated Robot Framework tests +robot --outputdir ./results ./Generated + +# 3. Save the results back to TestBench +testbench2robotframework fetch-results -d ./updated_report ./results/output.xml testbench_report.zip +``` + +## Result Synchronization + +The tool automatically maps the Robot Framework test results to the corresponding TestBench test cases based on the test structure. This includes: + +- ✅ Test execution status (PASS/FAIL) +- ✅ Execution timestamps +- ✅ Error messages and logs + + diff --git a/docs/usage/generate_tests.md b/docs/usage/generate_tests.md new file mode 100644 index 0000000..d77f09b --- /dev/null +++ b/docs/usage/generate_tests.md @@ -0,0 +1,78 @@ +--- +sidebar_position: 1 +--- + +# Generating Tests + +Learn how to generate Robot Framework test suites from TestBench reports. + +## Basic Command + +To generate Robot Framework test suites, use the `generate-tests` subcommand: + +```powershell +testbench2robotframework generate-tests TESTBENCH_REPORT +``` + +This command generates a Robot Framework test suite for each test case set specified in the `TESTBENCH_REPORT`. + +## Command-Line Options + +The `generate-tests` command supports the following options: + +| Option | Description | +|--------|-------------| +| `-c`, `--config PATH` | Path to a configuration file for TestBench2RobotFramework. | +| `--clean` | Deletes all files present in the output-directory before new test suites are created. | +| `-d`, `--output-directory PATH` | Directory or ZIP archive containing the generated test suites. | +| `--compound-keyword-logging` | Mode for logging compound keywords. Options: `GROUP`, `COMMENT`, or `NONE`. | +| `--fully-qualified` | Calls Robot Framework keywords by their fully qualified names in the generated test suites. | +| `--log-suite-numbering` | Enables logging of the test suite numbering. | +| `--resource-directory PATH` | Directory containing the Robot Framework resource files. | +| `--resource-directory-regex TEXT` | Regex that can be used to identify the TestBench Subdivision that corresponds to the resource-directory. | +| `--library-regex TEXT` | Regular expression used to identify TestBench subdivisions corresponding to Robot Framework libraries. | +| `--library-root TEXT` | TestBench root subdivision whose direct children correspond to Robot Framework libraries. | +| `--resource-regex TEXT` | Regular expression used to identify TestBench subdivisions corresponding to Robot Framework resources. | +| `--resource-root TEXT` | TestBench root subdivision whose direct children correspond to Robot Framework resources. | +| `--library-mapping TEXT` | Library import statement to use when a keyword from the specified TestBench subdivision is encountered. | +| `--resource-mapping TEXT` | Resource import statement to use when a keyword from the specified TestBench subdivision is encountered. | +| `--metadata TEXT` | Add extra metadata to the settings of the generated Robot Framework test suite. Provide entries as `key:value` pairs. | +| `--help` | Displays the help message and exits. | +| `--version` | Writes the TestBench2RobotFramework, Robot Framework and Python version to console. | + +## Examples + +### Basic Test Generation + +```powershell +testbench2robotframework generate-tests my_report.zip +``` + +### With Custom Output Directory + +```powershell +testbench2robotframework generate-tests -d ./Generated my_report.zip +``` + +### Clean Build + +```powershell +testbench2robotframework generate-tests --clean -d ./Generated my_report.zip +``` + +### With Configuration File + +```powershell +testbench2robotframework generate-tests -c config.toml my_report.zip +``` + + +## Resource and Library Mapping + +TestBench2RobotFramework provides flexible options for mapping TestBench subdivisions to Robot Framework libraries and resources: + +- Use `--library-regex` and `--resource-regex` to identify subdivisions with regular expressions +- Use `--library-root` and `--resource-root` to specify root subdivisions +- Use `--library-mapping` and `--resource-mapping` to define custom import statements + +For detailed configuration examples, see the [Configuration Guide](../configuration/pyproject_config.md). diff --git a/oldModel.py b/oldModel.py deleted file mode 100644 index e26c32f..0000000 --- a/oldModel.py +++ /dev/null @@ -1,1153 +0,0 @@ -# generated by datamodel-codegen: -# filename: model.yml -# timestamp: 2023-09-25T09:28:04+00:00 - -from __future__ import annotations - -from typing import List, Optional - -from pydantic import BaseModel, Field - - -class ActivityStatus(BaseModel): - enum: List[str] - title: str - type: str - - -class Content(BaseModel): - title: str - type: str - - -class FilterType(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Key(BaseModel): - title: str - type: str - - -class Name(BaseModel): - title: str - type: str - - -class Properties(BaseModel): - content: Content - filterType: FilterType - key: Key - name: Name - - -class AttachedFilter(BaseModel): - properties: Properties - required: List[str] - title: str - type: str - - -class CallType(BaseModel): - enum: List[str] - title: str - type: str - - -class Description(BaseModel): - title: str - type: str - - -class UniqueId(BaseModel): - title: str - type: str - - -class AnyOfItem(BaseModel): - type: str - - -class Version(BaseModel): - anyOf: List[AnyOfItem] - default: None - title: str - - -class Properties1(BaseModel): - description: Description - key: Key - name: Name - uniqueId: UniqueId - version: Version - - -class ConditionSummary(BaseModel): - properties: Properties1 - required: List[str] - title: str - type: str - - -class DataTypeKind(BaseModel): - enum: List[str] - title: str - type: str - - -class Kind(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Path(BaseModel): - title: str - type: str - - -class UniqueID(BaseModel): - title: str - type: str - - -class Version1(BaseModel): - anyOf: List[AnyOfItem] - title: str - - -class Properties2(BaseModel): - key: Key - kind: Kind - name: Name - path: Path - uniqueID: UniqueID - version: Version1 - - -class DataTypeSummary(BaseModel): - properties: Properties2 - required: List[str] - title: str - type: str - - -class ExecutionStatus(BaseModel): - enum: List[str] - title: str - type: str - - -class ExecutionVerdict(BaseModel): - enum: List[str] - title: str - type: str - - -class FilterType1(BaseModel): - enum: List[str] - title: str - type: str - - -class AnyOfItem2(BaseModel): - field_ref: Optional[str] = Field(None, alias='$ref') - type: Optional[str] = None - - -class Exec(BaseModel): - anyOf: List[AnyOfItem2] - - -class InteractionType(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Items(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Interactions(BaseModel): - items: Items - title: str - type: str - - -class Parameters(BaseModel): - items: Items - title: str - type: str - - -class Spec(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class AnyOfItem3(BaseModel): - type: str - - -class Version2(BaseModel): - anyOf: List[AnyOfItem3] - title: str - - -class Properties3(BaseModel): - exec: Exec - interactionType: InteractionType - interactions: Interactions - key: Key - name: Name - parameters: Parameters - path: Path - spec: Spec - uniqueID: UniqueID - version: Version2 - - -class InteractionDetails(BaseModel): - properties: Properties3 - required: List[str] - title: str - type: str - - -class Comments(BaseModel): - title: str - type: str - - -class CurrentUser(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Duration(BaseModel): - title: str - type: str - - -class References(BaseModel): - items: Items - title: str - type: str - - -class Tester(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Time(BaseModel): - title: str - type: str - - -class Verdict(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Properties4(BaseModel): - comments: Comments - currentUser: CurrentUser - duration: Duration - references: References - tester: Tester - time: Time - verdict: Verdict - - -class InteractionExecutionSummary(BaseModel): - properties: Properties4 - required: List[str] - title: str - type: str - - -class CallId(BaseModel): - title: str - type: str - - -class CallType1(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class PostConditions(BaseModel): - items: Items - title: str - type: str - - -class PreConditions(BaseModel): - items: Items - title: str - type: str - - -class References1(BaseModel): - items: Items - title: str - type: str - - -class SequencePhase(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Properties5(BaseModel): - callId: CallId - callType: CallType1 - comments: Comments - description: Description - postConditions: PostConditions - preConditions: PreConditions - references: References1 - sequencePhase: SequencePhase - - -class InteractionSpecificationSummary(BaseModel): - properties: Properties5 - required: List[str] - title: str - type: str - - -class InteractionType1(BaseModel): - enum: List[str] - title: str - type: str - - -class InteractionVerdict(BaseModel): - enum: List[str] - title: str - type: str - - -class IsVariantsMarker(BaseModel): - title: str - type: str - - -class Properties6(BaseModel): - isVariantsMarker: IsVariantsMarker - key: Key - name: Name - - -class Keyword(BaseModel): - properties: Properties6 - required: List[str] - title: str - type: str - - -class ParameterDefinitionType(BaseModel): - enum: List[str] - title: str - type: str - - -class DataType(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class DefinitionType(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class UseType(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Value(BaseModel): - anyOf: List[AnyOfItem3] - title: str - - -class ValueType(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Properties7(BaseModel): - dataType: DataType - definitionType: DefinitionType - key: Key - name: Name - useType: UseType - value: Value - valueType: ValueType - - -class ParameterSummary(BaseModel): - properties: Properties7 - required: List[str] - title: str - type: str - - -class ParameterUseType(BaseModel): - enum: List[str] - title: str - type: str - - -class ParameterValueType(BaseModel): - enum: List[str] - title: str - type: str - - -class Priority(BaseModel): - enum: List[str] - title: str - type: str - - -class Type(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Properties8(BaseModel): - path: Path - type: Type - - -class Reference(BaseModel): - properties: Properties8 - required: List[str] - title: str - type: str - - -class ReferenceType(BaseModel): - enum: List[str] - title: str - type: str - - -class Edited(BaseModel): - title: str - type: str - - -class Properties9(BaseModel): - edited: Edited - key: Key - - -class RequirementReference(BaseModel): - properties: Properties9 - required: List[str] - title: str - type: str - - -class SequencePhase1(BaseModel): - enum: List[str] - title: str - type: str - - -class SpecificationStatus(BaseModel): - enum: List[str] - title: str - type: str - - -class AnyOfItem5(BaseModel): - field_ref: Optional[str] = Field(None, alias='$ref') - type: Optional[str] = None - - -class Exec1(BaseModel): - anyOf: List[AnyOfItem5] - - -class Interactions1(BaseModel): - items: Items - title: str - type: str - - -class Parameters1(BaseModel): - items: Items - title: str - type: str - - -class Properties10(BaseModel): - exec: Exec1 - interactions: Interactions1 - parameters: Parameters1 - spec: Spec - uniqueID: UniqueID - - -class TestCaseDetails(BaseModel): - properties: Properties10 - required: List[str] - title: str - type: str - - -class ActualDuration(BaseModel): - title: str - type: str - - -class Items8(BaseModel): - type: str - - -class Defects(BaseModel): - items: Items8 - title: str - type: str - - -class ExecStatus(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Items9(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Keywords(BaseModel): - items: Items9 - title: str - type: str - - -class PlannedDuration(BaseModel): - title: str - type: str - - -class References2(BaseModel): - items: Items9 - title: str - type: str - - -class Status(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Tester1(BaseModel): - anyOf: List[AnyOfItem5] - - -class Udfs(BaseModel): - items: Items9 - title: str - type: str - - -class AnyOfItem7(BaseModel): - type: str - - -class Version3(BaseModel): - anyOf: List[AnyOfItem7] - title: str - - -class Properties11(BaseModel): - actualDuration: ActualDuration - comments: Comments - currentUser: CurrentUser - defects: Defects - execStatus: ExecStatus - key: Key - keywords: Keywords - plannedDuration: PlannedDuration - references: References2 - status: Status - tester: Tester1 - udfs: Udfs - verdict: Verdict - version: Version3 - - -class TestCaseExecutionDetails(BaseModel): - properties: Properties11 - required: List[str] - title: str - type: str - - -class Comments3(BaseModel): - default: str - title: str - type: str - - -class Items12(BaseModel): - type: str - - -class AnyOfItem8(BaseModel): - items: Optional[Items12] = None - type: str - - -class Defects1(BaseModel): - anyOf: List[AnyOfItem8] - default: None - title: str - - -class AllOfItem(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class ExecStatus1(BaseModel): - allOf: List[AllOfItem] - default: str - - -class Status1(BaseModel): - allOf: List[AllOfItem] - default: str - - -class AnyOfItem9(BaseModel): - field_ref: Optional[str] = Field(None, alias='$ref') - type: Optional[str] = None - - -class Tester2(BaseModel): - anyOf: List[AnyOfItem9] - default: None - - -class Verdict2(BaseModel): - allOf: List[AllOfItem] - default: str - - -class Properties12(BaseModel): - comments: Comments3 - defects: Defects1 - execStatus: ExecStatus1 - key: Key - status: Status1 - tester: Tester2 - verdict: Verdict2 - - -class TestCaseExecutionSummary(BaseModel): - properties: Properties12 - required: List[str] - title: str - type: str - - -class Exec2(BaseModel): - anyOf: List[AnyOfItem9] - - -class Numbering(BaseModel): - title: str - type: str - - -class Items13(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class TestCases(BaseModel): - items: Items13 - title: str - type: str - - -class Properties13(BaseModel): - exec: Exec2 - key: Key - name: Name - numbering: Numbering - spec: Spec - testCases: TestCases - uniqueID: UniqueID - - -class TestCaseSetDetails(BaseModel): - properties: Properties13 - required: List[str] - title: str - type: str - - -class Comments4(BaseModel): - title: str - type: str - - -class Keywords1(BaseModel): - items: Items13 - title: str - type: str - - -class Udfs1(BaseModel): - items: Items13 - title: str - type: str - - -class Properties14(BaseModel): - comments: Comments4 - key: Key - keywords: Keywords1 - udfs: Udfs1 - - -class TestCaseSetExecutionSummary(BaseModel): - properties: Properties14 - required: List[str] - title: str - type: str - - -class AnyOfItem11(BaseModel): - type: str - - -class DueDate(BaseModel): - anyOf: List[AnyOfItem11] - title: str - - -class Keywords2(BaseModel): - items: Items13 - title: str - type: str - - -class PostConditions1(BaseModel): - items: Items13 - title: str - type: str - - -class PreConditions1(BaseModel): - items: Items13 - title: str - type: str - - -class AnyOfItem12(BaseModel): - field_ref: Optional[str] = Field(None, alias='$ref') - type: Optional[str] = None - - -class Priority1(BaseModel): - anyOf: List[AnyOfItem12] - - -class References3(BaseModel): - items: Items13 - title: str - type: str - - -class Requirements(BaseModel): - items: Items13 - title: str - type: str - - -class Responsible(BaseModel): - anyOf: List[AnyOfItem12] - - -class ReviewComment(BaseModel): - title: str - type: str - - -class Reviewer(BaseModel): - anyOf: List[AnyOfItem12] - - -class Status2(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Udfs2(BaseModel): - items: Items13 - title: str - type: str - - -class Properties15(BaseModel): - description: Description - dueDate: DueDate - key: Key - keywords: Keywords2 - postConditions: PostConditions1 - preConditions: PreConditions1 - priority: Priority1 - references: References3 - requirements: Requirements - responsible: Responsible - reviewComment: ReviewComment - reviewer: Reviewer - status: Status2 - udfs: Udfs2 - - -class TestCaseSetSpecificationSummary(BaseModel): - properties: Properties15 - required: List[str] - title: str - type: str - - -class Keywords3(BaseModel): - items: Items13 - title: str - type: str - - -class Requirements1(BaseModel): - items: Items13 - title: str - type: str - - -class Udfs3(BaseModel): - items: Items13 - title: str - type: str - - -class Properties16(BaseModel): - comments: Comments4 - key: Key - keywords: Keywords3 - requirements: Requirements1 - udfs: Udfs3 - - -class TestCaseSpecificationDetails(BaseModel): - properties: Properties16 - required: List[str] - title: str - type: str - - -class Requirements2(BaseModel): - items: Items13 - title: str - type: str - - -class Properties17(BaseModel): - comments: Comments4 - key: Key - requirements: Requirements2 - - -class TestCaseSpecificationSummary(BaseModel): - properties: Properties17 - required: List[str] - title: str - type: str - - -class Exec3(BaseModel): - anyOf: List[AnyOfItem12] - default: None - - -class Index(BaseModel): - title: str - type: str - - -class Properties18(BaseModel): - exec: Exec3 - index: Index - spec: Spec - uniqueID: UniqueID - - -class TestCaseSummary(BaseModel): - properties: Properties18 - required: List[str] - title: str - type: str - - -class Locker(BaseModel): - anyOf: List[AnyOfItem12] - - -class Properties19(BaseModel): - key: Key - locker: Locker - status: Status2 - - -class TestStructureAutomation(BaseModel): - properties: Properties19 - required: List[str] - title: str - type: str - - -class ExecStatus2(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Locker1(BaseModel): - anyOf: List[AnyOfItem12] - - -class Verdict3(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Properties20(BaseModel): - execStatus: ExecStatus2 - key: Key - locker: Locker1 - status: Status2 - verdict: Verdict3 - - -class TestStructureExecution(BaseModel): - properties: Properties20 - required: List[str] - title: str - type: str - - -class Locker2(BaseModel): - anyOf: List[AnyOfItem12] - - -class Properties21(BaseModel): - key: Key - locker: Locker2 - status: Status2 - - -class TestStructureSpecification(BaseModel): - properties: Properties21 - required: List[str] - title: str - type: str - - -class Nodes(BaseModel): - items: Items13 - title: str - type: str - - -class Root(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Properties22(BaseModel): - nodes: Nodes - root: Root - - -class TestStructureTree(BaseModel): - properties: Properties22 - required: List[str] - title: str - type: str - - -class Automation(BaseModel): - anyOf: List[AnyOfItem12] - - -class BaseInformation(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class ElementType(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Execution(BaseModel): - anyOf: List[AnyOfItem12] - - -class Filters(BaseModel): - items: Items13 - title: str - type: str - - -class Specification(BaseModel): - anyOf: List[AnyOfItem12] - - -class Properties23(BaseModel): - automation: Automation - baseInformation: BaseInformation - elementType: ElementType - execution: Execution - filters: Filters - specification: Specification - - -class TestStructureTreeNode(BaseModel): - properties: Properties23 - required: List[str] - title: str - type: str - - -class MatchesFilter(BaseModel): - title: str - type: str - - -class OrderPos(BaseModel): - title: str - type: str - - -class ParentKey(BaseModel): - title: str - type: str - - -class Properties24(BaseModel): - key: Key - matchesFilter: MatchesFilter - name: Name - numbering: Numbering - orderPos: OrderPos - parentKey: ParentKey - uniqueID: UniqueID - - -class TestStructureTreeNodeInformation(BaseModel): - properties: Properties24 - required: List[str] - title: str - type: str - - -class TestStructureTreeNodeType(BaseModel): - enum: List[str] - title: str - type: str - - -class UdfType(BaseModel): - enum: List[str] - title: str - type: str - - -class Value1(BaseModel): - title: str - type: str - - -class Properties25(BaseModel): - key: Key - name: Name - value: Value1 - valueType: ValueType - - -class UserDefinedField(BaseModel): - properties: Properties25 - required: List[str] - title: str - type: str - - -class Properties26(BaseModel): - key: Key - name: Name - - -class UserReference(BaseModel): - properties: Properties26 - required: List[str] - title: str - type: str - - -class FieldDefs(BaseModel): - ActivityStatus: ActivityStatus - AttachedFilter: AttachedFilter - CallType: CallType - ConditionSummary: ConditionSummary - DataTypeKind: DataTypeKind - DataTypeSummary: DataTypeSummary - ExecutionStatus: ExecutionStatus - ExecutionVerdict: ExecutionVerdict - FilterType: FilterType1 - InteractionDetails: InteractionDetails - InteractionExecutionSummary: InteractionExecutionSummary - InteractionSpecificationSummary: InteractionSpecificationSummary - InteractionType: InteractionType1 - InteractionVerdict: InteractionVerdict - Keyword: Keyword - ParameterDefinitionType: ParameterDefinitionType - ParameterSummary: ParameterSummary - ParameterUseType: ParameterUseType - ParameterValueType: ParameterValueType - Priority: Priority - Reference: Reference - ReferenceType: ReferenceType - RequirementReference: RequirementReference - SequencePhase: SequencePhase1 - SpecificationStatus: SpecificationStatus - TestCaseDetails: TestCaseDetails - TestCaseExecutionDetails: TestCaseExecutionDetails - TestCaseExecutionSummary: TestCaseExecutionSummary - TestCaseSetDetails: TestCaseSetDetails - TestCaseSetExecutionSummary: TestCaseSetExecutionSummary - TestCaseSetSpecificationSummary: TestCaseSetSpecificationSummary - TestCaseSpecificationDetails: TestCaseSpecificationDetails - TestCaseSpecificationSummary: TestCaseSpecificationSummary - TestCaseSummary: TestCaseSummary - TestStructureAutomation: TestStructureAutomation - TestStructureExecution: TestStructureExecution - TestStructureSpecification: TestStructureSpecification - TestStructureTree: TestStructureTree - TestStructureTreeNode: TestStructureTreeNode - TestStructureTreeNodeInformation: TestStructureTreeNodeInformation - TestStructureTreeNodeType: TestStructureTreeNodeType - UdfType: UdfType - UserDefinedField: UserDefinedField - UserReference: UserReference - - -class TestCaseDetails1(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class TestCaseSetDetails1(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class TestStructureTree1(BaseModel): - field_ref: str = Field(..., alias='$ref') - - -class Properties27(BaseModel): - TestCaseDetails: TestCaseDetails1 - TestCaseSetDetails: TestCaseSetDetails1 - TestStructureTree: TestStructureTree1 - - -class Model(BaseModel): - field_defs: FieldDefs = Field(..., alias='$defs') - properties: Properties27 - required: List[str] - title: str - type: str diff --git a/pydantic_model.py b/pydantic_model.py deleted file mode 100644 index 74a6d05..0000000 --- a/pydantic_model.py +++ /dev/null @@ -1,469 +0,0 @@ -# pylint: skip-file -from __future__ import annotations - -from pydantic import BaseModel -from enum import Enum, auto -from typing import List, Optional - - -class StrEnum(str, Enum): - def __new__(cls, *args): - for arg in args: - if not isinstance(arg, (str, auto)): - raise TypeError(f"Values of StrEnums must be strings: {repr(arg)} is a {type(arg)}") - return super().__new__(cls, *args) - - def __str__(self): - return self.value - - def _generate_next_value_(name, *_): - return name - - -class TestFilterType(StrEnum): - TestTheme = "TestTheme" - TestCaseSet = "TestCaseSet" - TestCase = "TestCase" - - -class TestStructureElementType(StrEnum): - Root = "Root" - TestTheme = "TestTheme" - TestCaseSet = "TestCaseSet" - - -class Priority(StrEnum): - Undefined = "Undefined" - Low = "Low" - Middle = "Middle" - High = "High" - - -class ReferenceType(StrEnum): - Reference = "Reference" - Hyperlink = "Hyperlink" - Attachment = "Attachment" - - -class SpecStatus(StrEnum): - NotPlanned = "NotPlanned" - Planned = "Planned" - InProgress = "InProgress" - InReview = "InReview" - Released = "Released" - - -class InteractionVerdict(StrEnum): - Pass = "Pass" - Fail = "Fail" - Skipped = "Skipped" - ToVerify = "ToVerify" - Warn = "Warn" - Undefined = "Undefined" - Blocked = "Blocked" - - -class VerdictStatus(StrEnum): - Undefined = "Undefined" - ToVerify = "ToVerify" - Fail = "Fail" - Pass = "Pass" - - -class ActivityStatus(StrEnum): - NotPlanned = "NotPlanned" - Planned = "Planned" - Assigned = "Assigned" - Running = "Running" - Canceled = "Canceled" - Skipped = "Skipped" - Performed = "Performed" - - -class ExecStatus(StrEnum): - NotBlocked = "NotBlocked" - Blocked = "Blocked" - - -class UDFType(StrEnum): - String = "String" - Enumeration = "Enumeration" - Boolean = "Boolean" - - -class SequencePhase(StrEnum): - Setup = "Setup" - TestStep = "TestStep" - Teardown = "Teardown" - - -class InteractionCallType(StrEnum): - Check = "Check" - Flow = "Flow" - - -class InteractionType(StrEnum): - Compound = "Compound" - Atomic = "Atomic" - Textual = "Textual" - - -class ParameterDefinitionType(StrEnum): - DETAILED = "DETAILED" - ARRAY = "ARRAY" - ATOMIC = "ATOMIC" - - -class ParameterEvaluationType(StrEnum): - CallByReference = "CallByReference" - CallByValue = "CallByValue" - CallByReferenceMandatory = "CallByReferenceMandatory" - - -class RepresentativeType(StrEnum): - Text = "Text" - Placeholder = "Placeholder" - Attachment = "Attachment" - Hyperlink = "Hyperlink" - Reference = "Reference" - - -class KindOfDataType(StrEnum): - Regular = "Regular" - Reference = "Reference" - Global = "Global" - AcceptingGlobal = "AcceptingGlobal" - - -class ProjectMember(BaseModel): - userkey: str - userLogin: str - userName: str - projectkey: str - projectName: str - roles: List[str] - - -class ProjectDetails(BaseModel): - key: str - creationTime: str - name: str - status: str - visibility: bool - tovsCount: int - cyclesCount: int - description: str - lockerKey: Optional[int] = None - startDate: Optional[str] = None - endDate: Optional[str] = None - - -class TOVDetails(BaseModel): - key: str - creationTime: str - name: str - status: str - visibility: bool - cyclesCount: int - description: str - lockerKey: Optional[int] = None - startDate: Optional[str] = None - endDate: Optional[str] = None - - -class CycleDetails(BaseModel): - key: str - creationTime: str - name: str - status: str - visibility: bool - description: str - startDate: Optional[str] = None - endDate: Optional[str] = None - - -class UserDetails(BaseModel): - key: str - login: str - name: str - email: str - passwordExpired: bool - active: bool - - -class UserSummary(BaseModel): - key: str - login: str - name: str - active: bool - - -class UserDefinedField(BaseModel): - key: str - name: str - value: str - udfType: UDFType - - -class Keyword(BaseModel): - key: str - name: str - isVariantsMarker: bool - - -class Reference(BaseModel): # TODO: May be changed. Differs to OpenApi.YML - type: ReferenceType - path: str - - -class UserReference(BaseModel): - key: str - name: str - - -class RequirementReference(BaseModel): - key: str - edited: bool - - -class ConditionSummary(BaseModel): - key: str - uniqueID: str - name: str - description: str - version: Optional[str] = None - - -class TestCaseSetSpecificationSummary(BaseModel): - key: str - description: str - reviewComment: str - status: SpecStatus - priority: Priority - responsible: Optional[UserReference] - dueDate: Optional[str] - reviewer: Optional[UserReference] - udfs: List[UserDefinedField] - keywords: List[Keyword] - # references: List[Reference] #TODO: MUST BE CHANGED IN THE FUTURE AGAIN!!! - references: List[str] - requirements: List[RequirementReference] - preConditions: List[ConditionSummary] - postConditions: List[ConditionSummary] - - -class TestCaseSpecificationDetails(BaseModel): - key: str - comments: str - udfs: List[UserDefinedField] - keywords: List[Keyword] - requirements: List[RequirementReference] - - -class TestCaseSetExecutionSummary(BaseModel): - key: str - comments: str - udfs: List[UserDefinedField] - keywords: List[Keyword] - - -class TestCaseSpecificationSummary(BaseModel): - key: str - comments: str - requirements: List[RequirementReference] - - -class TestCaseExecutionSummary(BaseModel): - key: str - status: ActivityStatus - execStatus: ExecStatus - verdict: VerdictStatus - comments: str - defects: List[str] - tester: Optional[UserReference] = None - - -class TestCaseSummary(BaseModel): - uniqueID: str - index: int - spec: TestCaseSpecificationSummary - exec: Optional[TestCaseExecutionSummary] = None - - -class TestCaseExecutionDetails(BaseModel): - key: str - status: ActivityStatus - execStatus: ExecStatus - verdict: VerdictStatus - plannedDuration: int - actualDuration: int - currentUser: UserReference - comments: str # TODO: Insert htmlComment - version: Optional[str] - defects: List[str] - udfs: List[UserDefinedField] - keywords: List[Keyword] - # references: List[Reference] #TODO: MUST BE CHANGED IN THE FUTURE AGAIN!!! - references: List[str] - tester: Optional[UserReference] = None - - -class TestCaseSetDetails(BaseModel): - key: str - numbering: str - uniqueID: str - name: str - spec: TestCaseSetSpecificationSummary - testCases: List[TestCaseSummary] - exec: Optional[TestCaseSetExecutionSummary] = None - - -class InteractionExecutionSummary(BaseModel): - verdict: InteractionVerdict - time: str - duration: int - currentUser: UserReference - tester: Optional[UserReference] - comments: str - # references: List[Reference] #TODO: MUST BE CHANGED IN THE FUTURE AGAIN!!! - references: List[str] - - -class InteractionSpecificationSummary(BaseModel): - callKey: str - sequencePhase: SequencePhase - callType: InteractionCallType - description: str - comments: str - # references: List[Reference] #TODO: MUST BE CHANGED IN THE FUTURE AGAIN!!! - references: List[str] - preConditions: List[ConditionSummary] - postConditions: List[ConditionSummary] - - -class DataTypeSummary(BaseModel): - key: str - name: str - kind: KindOfDataType - version: Optional[str] - path: str - uniqueID: str - - -class ParameterSummary(BaseModel): - key: str - name: str - value: Optional[str] - valueType: RepresentativeType - definitionType: ParameterDefinitionType - evaluationType: ParameterEvaluationType - dataType: Optional[DataTypeSummary] - - -class InteractionDetails(BaseModel): - key: str - uniqueID: str - name: str - version: Optional[str] - interactionType: InteractionType - path: str - spec: InteractionSpecificationSummary - exec: Optional[InteractionExecutionSummary] - parameters: List[ParameterSummary] - interactions: List[InteractionDetails] - - -class TestCaseDetails(BaseModel): - uniqueID: str - spec: TestCaseSpecificationDetails - interactions: List[InteractionDetails] - parameters: List[ParameterSummary] - exec: Optional[TestCaseExecutionDetails] = None - - -class TestStructureSpecification(BaseModel): - key: str - locker: Optional[UserReference] - status: SpecStatus - - -class TestStructureAutomation(BaseModel): - key: str - locker: Optional[UserReference] - status: SpecStatus - - -class TestStructureExecution(BaseModel): - key: str - locker: Optional[UserReference] - status: ActivityStatus - execStatus: ExecStatus - verdict: VerdictStatus - - -class AttachedFilter(BaseModel): - key: str - name: str - filterType: TestFilterType - content: str - - -class TestStructureTreeNodeInformation(BaseModel): - key: str - numbering: str - parentKey: str - name: str - uniqueID: str - orderPos: int - matchesFilter: bool - - -class TestStructureTreeNode(BaseModel): - elementType: TestStructureElementType - base: TestStructureTreeNodeInformation - spec: Optional[TestStructureSpecification] - aut: Optional[TestStructureAutomation] - exec: Optional[TestStructureExecution] - filters: List[AttachedFilter] - - -class TestStructureTree(BaseModel): - root: Optional[TestStructureTreeNode] - nodes: List[TestStructureTreeNode] - - -class AllModels(BaseModel): - ProjectMember: ProjectMember - ProjectDetails: ProjectDetails - TOVDetails: TOVDetails - CycleDetails: CycleDetails - UserDetails: UserDetails - TestStructureTree: TestStructureTree - TestCaseDetails: TestCaseDetails - InteractionDetails: InteractionDetails - InteractionSpecificationSummary: InteractionSpecificationSummary - InteractionExecutionSummary: InteractionExecutionSummary - ParameterSummary: ParameterSummary - TestCaseSpecificationDetails: TestCaseSpecificationDetails - TestCaseExecutionDetails: TestCaseExecutionDetails - TestStructureSpecification: TestStructureSpecification - TestStructureAutomation: TestStructureAutomation - TestStructureExecution: TestStructureExecution - AttachedFilter: AttachedFilter - TestStructureTreeNodeInformation: TestStructureTreeNodeInformation - TestStructureTreeNode: TestStructureTreeNode - TestCaseExecutionSummary: TestCaseExecutionSummary - TestCaseSetExecutionSummary: TestCaseSetExecutionSummary - ActivityStatus: ActivityStatus - ExecStatus: ExecStatus - VerdictStatus: VerdictStatus - SpecStatus: SpecStatus - DataTypeSummary: DataTypeSummary - TestCaseSetDetails: TestCaseSetDetails - TestCaseSetSpecificationSummary: TestCaseSetSpecificationSummary - TestCaseSpecificationSummary: TestCaseSpecificationSummary - TestCaseSummary: TestCaseSummary - - - - diff --git a/pyproject.toml b/pyproject.toml index 44e0fd5..09fa074 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ dev = [ "pytest-cov", "pytest-spec", "robotframework-tidy", + "datamodel-code-generator", "ruff", "twine", ] @@ -66,7 +67,7 @@ pretty = true [tool.ruff] -target-version = "py39" +target-version = "py310" line-length = 100 lint.select = [ "A", @@ -147,6 +148,13 @@ disable = ''' max-nested-blocks = 3 +[tool.datamodel-codegen] +input-file-type = "openapi" +output-model-type = "dataclasses.dataclass" +target-python-version = "3.10" +output = "testbench2robotframework/model.py" +formatters = ["ruff-format", "ruff-check"] + [tool.robotidy] transform = [ "InlineIf", diff --git a/tasks.py b/tasks.py index a35af3b..6c45cd7 100644 --- a/tasks.py +++ b/tasks.py @@ -1,8 +1,48 @@ -from invoke import Context, task import os +import re from pathlib import Path + +from invoke import Context, task from robot.run import run_cli +MODEL_OUTPUT = "testbench2robotframework/model.py" + + +@task +def generate_model(c: Context, input="openapi.yml") -> None: + """Regenerates model.py from an OpenAPI spec and injects __VERSION__.""" + if not Path(input).exists(): + raise FileNotFoundError(f"OpenAPI spec not found: {input}") + c.run(f"datamodel-codegen --input {input}") + with open(input, encoding="utf-8") as f: + content = f.read() + match = re.search(r"version:\s*['\"]?([^'\"\n]+)", content) + version = match.group(1).strip() if match else "unknown" + model_path = Path(MODEL_OUTPUT) + model_content = model_path.read_text(encoding="utf-8") + # Inject __VERSION__ + model_content = model_content.replace( + "from __future__ import annotations", + f'from __future__ import annotations\n\n__VERSION__ = "{version}"', + ) + # datamodel-codegen generates `field: Literal["X"]` without a default, which + # violates the dataclass rule that non-default fields can't follow default fields. + # Using field(default=..., init=False) so inherited classes (where Python preserves + # the parent field's position in the MRO dict) don't break either. + model_content = model_content.replace( + "from dataclasses import dataclass\n", + "from dataclasses import dataclass, field\n", + ) + model_content = re.sub( + r'(\w+): (Literal\["([^"]+)"\])(\s*$)', + r'\1: \2 = field(default="\3", init=False)\4', + model_content, + flags=re.MULTILINE, + ) + model_path.write_text(model_content, encoding="utf-8") + print(f"Generated {MODEL_OUTPUT} with __VERSION__ = '{version}'") + + @task def run_atest(c: Context) -> None: """Runs Robot Framework atests.""" @@ -26,4 +66,4 @@ def run_atest(c: Context) -> None: ] ) finally: - os.chdir(orgdir) \ No newline at end of file + os.chdir(orgdir) diff --git a/testbench2robotframework/__init__.py b/testbench2robotframework/__init__.py index f24e6f4..128e63f 100644 --- a/testbench2robotframework/__init__.py +++ b/testbench2robotframework/__init__.py @@ -17,4 +17,4 @@ from .testbench2robotframework import testbench2robotframework # noqa: F401 -__version__ = "0.9.2b3" +__version__ = "1.0.0" diff --git a/testbench2robotframework/cli.py b/testbench2robotframework/cli.py index a144073..3891917 100644 --- a/testbench2robotframework/cli.py +++ b/testbench2robotframework/cli.py @@ -21,10 +21,17 @@ ) from .json_reader import read_json from .testbench2robotframework import testbench2robotframework +from .utils import ALLOWED_SERVER_VERSIONS -TESTBENCH2ROBOTFRAMEWORK_DESCRIPTION = """TestBench2RobotFramework converts a TestBench JSON-report +TESTBENCH2ROBOTFRAMEWORK_DESCRIPTION = ( + """TestBench2RobotFramework converts a TestBench JSON-report to Robot Framework test suites and enhances the TestBench Report - with the execution results provided by Robot Framework.""" + with the execution results provided by Robot Framework. The version your TestBench report + was generated with must be compatible with the version of testbench2robotframework you are using. + Supported versions for this version of testbench2robotframework are: """ + + ", ".join(ALLOWED_SERVER_VERSIONS) + + "." +) GENERATE_HELP = """Command to convert a TestBench JSON-report to Robot Framework test suites.""" FETCH_HELP = """Command to fetch execution results from a Robot Framework result XML and to write the results to a TestBench JSON-report.""" @@ -56,7 +63,8 @@ def parse_subdivision_mapping( help="Writes the TestBench2RobotFramework, Robot Framework and Python version to console.", message=( f"TestBench2RobotFramework {__version__} with " - f"Robot Framework {robot.version.get_full_version()}" + f"Robot Framework {robot.version.get_full_version()}. " + f"Compatible with TestBench server versions: {', '.join(ALLOWED_SERVER_VERSIONS)}." ), ) @click.help_option("-h", "--help") @@ -123,9 +131,9 @@ def testbench2robotframework_cli(): "--metadata", multiple=True, callback=parse_subdivision_mapping, - help="""Add extra metadata to the settings of the generated Robot Framework test suite. - Provide entries as key:value pairs, where *key* is the metadata name and *value* is the corresponding value. - Values may also be Python expressions. + help="""Add extra metadata to the settings of the generated Robot Framework test suite. + Provide entries as key:value pairs, where *key* is the metadata name and *value* is the corresponding value. + Values may also be Python expressions. The special variable '$tcs' gives access to the TestBench Python model of the test case set.""", ) @click.option( @@ -198,8 +206,8 @@ def generate_tests( # noqa: PLR0913 else: configuration["log-suite-numbering"] = configuration.get("log-suite-numbering", False) configuration["metadata"] = metadata or configuration.get("metadata", {}) - configuration["compound-keyword-logging"] = ( - compound_keyword_logging or configuration.get("compound-keyword-logging", "GROUP") + configuration["compound-keyword-logging"] = compound_keyword_logging or configuration.get( + "compound-keyword-logging", "GROUP" ) configuration["resource-directory"] = ( resource_directory.as_posix() diff --git a/testbench2robotframework/config.py b/testbench2robotframework/config.py index 7361fcd..e645f77 100644 --- a/testbench2robotframework/config.py +++ b/testbench2robotframework/config.py @@ -189,6 +189,7 @@ class Configuration: attachmentConflictBehaviour: AttachmentConflictBehaviour clean: bool compound_keyword_logging: CompoundKeywordLogging + create_output_zip: bool forced_import: ForcedImport fully_qualified: bool library_regex: list[str] @@ -197,7 +198,7 @@ class Configuration: loggingConfiguration: LoggingConfig metadata: dict[str, str] output_directory: str - phasePattern: str + phase_pattern: str referenceBehaviour: ReferenceBehaviour resource_directory: str resource_directory_regex: str @@ -210,6 +211,7 @@ class Configuration: def from_dict(cls, dictionary) -> Configuration: return cls( clean=dictionary.get("clean", True), + create_output_zip=dictionary.get("create-output-zip", False), library_regex=dictionary.get( "library-regex", [DEFAULT_LIBRARY_REGEX] ), @@ -236,7 +238,7 @@ def from_dict(cls, dictionary) -> Configuration: "\\", "/" ), testCaseSplitPathRegEx=dictionary.get("testcase-splitting-regex", ".*StopWithRestart.*"), - phasePattern=dictionary.get("phasePattern", "{testcase} : Phase {index}/{length}"), + phase_pattern=dictionary.get("phase-pattern", "{testcase} : Phase {index}/{length}"), referenceBehaviour=ReferenceBehaviour( dictionary.get("reference-behaviour", "ATTACHMENT").upper() ), diff --git a/testbench2robotframework/execution_artifacts.py b/testbench2robotframework/execution_artifacts.py index 6081933..7bcb20b 100644 --- a/testbench2robotframework/execution_artifacts.py +++ b/testbench2robotframework/execution_artifacts.py @@ -1,7 +1,6 @@ import shutil import sys from pathlib import Path -from typing import Optional from urllib.parse import unquote from .config import AttachmentConflictBehaviour, ReferenceBehaviour @@ -26,9 +25,9 @@ def __init__( self.tb_references: list[ReferenceAssignment] = tb_references self.output_xml = output_xml self.attachment_folder = attachment_folder - self._key: Optional[int] = None + self._key: int | None = None - def add_artifact(self, artifact: str) -> Optional[str]: + def add_artifact(self, artifact: str) -> str | None: """ Adds an artifact to the storage based on the reference behaviour. @@ -135,7 +134,7 @@ def _copy_attachment(self, artifact_value: str) -> str: return self._use_new_attachment(filename, artifact_value, attachment_folder_path) return self._dispatch_attachment_copy(filename, artifact_value, attachment_folder_path) - def _process_artifact(self, artifact: str) -> Optional[str]: + def _process_artifact(self, artifact: str) -> str | None: artifact_info = ExecutionArtifactInfo(artifact, self.output_xml) artifact_value = artifact_info.get_attachment_value() if not artifact_value: @@ -150,7 +149,7 @@ def _process_artifact(self, artifact: str) -> Optional[str]: return None return self._copy_attachment(artifact_value) - def _process_reference(self, artifact: str) -> Optional[str]: + def _process_reference(self, artifact: str) -> str | None: artifact_info = ExecutionArtifactInfo(artifact, self.output_xml) artifact_value = artifact_info.get_reference_value() if not artifact_value: @@ -160,14 +159,14 @@ def _process_reference(self, artifact: str) -> Optional[str]: return None return artifact_value - def _process_unknown(self, artifact: str) -> Optional[str]: + def _process_unknown(self, artifact: str) -> str | None: logger.error( f"Unknown reference behaviour '{self.reference_behaviour}'." f"Cannot add artifact '{artifact}'." ) return None - def _process_no_references_allowed(self, artifact: str) -> Optional[str]: + def _process_no_references_allowed(self, artifact: str) -> str | None: logger.warning( f"Reference behaviour is set to NONE." f"Reference '{artifact}' will not be added to report." @@ -214,7 +213,7 @@ def __init__(self, artifact: str, output_xml: str) -> None: self.artifact = unquoted_artifact self.output_xml = output_xml - def get_reference_value(self) -> Optional[str]: + def get_reference_value(self) -> str | None: unquoted_path = Path(self.artifact) if not unquoted_path.exists(): robot_output_dir = Path(self.output_xml).parent @@ -230,7 +229,7 @@ def get_reference_value(self) -> Optional[str]: return None return str(unquoted_path) - def get_attachment_value(self) -> Optional[str]: + def get_attachment_value(self) -> str | None: unquoted_path = Path(self.artifact) if not unquoted_path.exists(): robot_output_dir = Path(self.output_xml).parent diff --git a/testbench2robotframework/json_reader.py b/testbench2robotframework/json_reader.py index 7199efe..394e57e 100644 --- a/testbench2robotframework/json_reader.py +++ b/testbench2robotframework/json_reader.py @@ -3,7 +3,6 @@ from dataclasses import dataclass from json import JSONDecodeError from pathlib import Path -from typing import Optional from .log import logger from .model import ( @@ -36,7 +35,7 @@ def metadata(self) -> dict[str, str]: class TestBenchJsonReader: def __init__(self, json_dir) -> None: self.json_dir = json_dir - self._test_theme_tree: Optional[TestStructureTree] = None + self._test_theme_tree: TestStructureTree | None = None self._test_case_sets: dict[str, TestCaseSetDetails] = {} self._test_cases: dict[str, TestCaseDetails] = {} if not json_dir: @@ -116,20 +115,20 @@ def get_test_case_uids(self, test_case_set_uid: str) -> list[str]: test_case_set = self.test_case_sets[test_case_set_uid] return [tc.uniqueID for tc in test_case_set.testCases] - def read_test_case_set(self, uid) -> Optional[TestCaseSetDetails]: + def read_test_case_set(self, uid) -> TestCaseSetDetails | None: tcs_dict = read_json(str(Path(self.json_dir, f"{uid}.json"))) if tcs_dict is None: return None return from_dict(TestCaseSetDetails, tcs_dict) - def read_test_case(self, uid) -> Optional[TestCaseDetails]: + def read_test_case(self, uid) -> TestCaseDetails | None: tc_dict = read_json(str(Path(self.json_dir, f"{uid}.json"))) if tc_dict is None: return None # return None # TODO: wenn nicht da dann Fehler? return from_dict(TestCaseDetails, tc_dict) - def read_test_theme_tree(self, is_tov=False) -> Optional[TestStructureTree]: + def read_test_theme_tree(self, is_tov=False) -> TestStructureTree | None: test_structure_tree = read_json(str(Path(self.json_dir, TEST_STRUCTURE_TREE_FILE))) if test_structure_tree is None: return None diff --git a/testbench2robotframework/json_writer.py b/testbench2robotframework/json_writer.py index 151794e..5819a3d 100644 --- a/testbench2robotframework/json_writer.py +++ b/testbench2robotframework/json_writer.py @@ -2,7 +2,6 @@ from dataclasses import asdict from enum import Enum from pathlib import Path -from typing import Union from .config import Configuration from .log import logger @@ -19,7 +18,7 @@ def write_test_structure_element( json_dir: str, - test_structure_element: Union[TestStructureTree, TestCaseSetDetails, TestCaseDetails], + test_structure_element: TestStructureTree | TestCaseSetDetails | TestCaseDetails, ) -> None: if isinstance(test_structure_element, TestStructureTree): filepath = Path(json_dir) / Path(TEST_STRUCTURE_TREE_FILE + ".json") diff --git a/testbench2robotframework/model.py b/testbench2robotframework/model.py index 68667cf..4939a13 100644 --- a/testbench2robotframework/model.py +++ b/testbench2robotframework/model.py @@ -1,1909 +1,1924 @@ -# generated by datamodel-codegen: -# filename: openapi.yml -# timestamp: 2026-03-31T06:28:07+00:00 - -from __future__ import annotations - -from dataclasses import dataclass -from enum import Enum -from typing import List, Optional, Union - - -class TestElementStatus(Enum): - InProgress = 'InProgress' - Released = 'Released' - - -class ContentType(Enum): - txt = 'txt' - xml = 'xml' - - -@dataclass -class AdvancedContent: - contentType: Optional[ContentType] = None - contentExtID: Optional[str] = None - content: Optional[str] = None - - -@dataclass -class OptionalAdvancedContent: - optional: Optional[AdvancedContent] = None - - -@dataclass -class OptionalUser: - optional: Optional[str] = None - - -class ProjectStatus(Enum): - Planned = 'Planned' - Active = 'Active' - Finished = 'Finished' - Closed = 'Closed' - - -class DefectMetricType(Enum): - Status = 'Status' - Priority = 'Priority' - Classification = 'Classification' - - -class ProjectTreeNodeType(Enum): - Project = 'Project' - Version = 'Version' - Cycle = 'Cycle' - - -class Severity(Enum): - Information = 'Information' - Warning = 'Warning' - Error = 'Error' - - -class UDFType(Enum): - String = 'String' - Enumeration = 'Enumeration' - Boolean = 'Boolean' - - -class TestLabelVisibilityType(Enum): - OnlyPrivate = 'OnlyPrivate' - OnlyPublic = 'OnlyPublic' - All = 'All' - - -class AutStatus(Enum): - NotPlanned = 'NotPlanned' - Planned = 'Planned' - InProgress = 'InProgress' - InReview = 'InReview' - Released = 'Released' - - -class SpecStatus(Enum): - NotPlanned = 'NotPlanned' - Planned = 'Planned' - InProgress = 'InProgress' - InReview = 'InReview' - Released = 'Released' - - -class Priority(Enum): - Undefined = 'Undefined' - Low = 'Low' - Middle = 'Middle' - High = 'High' - - -class ActivityStatus(Enum): - NotPlanned = 'NotPlanned' - Planned = 'Planned' - Assigned = 'Assigned' - Running = 'Running' - Skipped = 'Skipped' - Canceled = 'Canceled' - Performed = 'Performed' - - -class ExecStatus(Enum): - NotBlocked = 'NotBlocked' - Blocked = 'Blocked' - - -class VerdictStatus(Enum): - Undefined = 'Undefined' - ToVerify = 'ToVerify' - Fail = 'Fail' - Pass = 'Pass' - - -class KeywordVerdict(Enum): - Pass = 'Pass' - Fail = 'Fail' - Skipped = 'Skipped' - ToVerify = 'ToVerify' - Warn = 'Warn' - Undefined = 'Undefined' - Blocked = 'Blocked' - - -class SequencePhase(Enum): - Setup = 'Setup' - TestStep = 'TestStep' - Teardown = 'Teardown' - - -class KeywordCallType(Enum): - Flow = 'Flow' - Check = 'Check' - - -class KeywordType(Enum): - Atomic = 'Atomic' - Compound = 'Compound' - Textual = 'Textual' - - -class OperationalState(Enum): - Enabled = 'Enabled' - Disabled = 'Disabled' - - -class GlobalHumanRole(Enum): - Administrator = 'Administrator' - ProjectAdministrator = 'ProjectAdministrator' - ProjectUser = 'ProjectUser' - - -class ProjectRole(Enum): - TestManager = 'TestManager' - TestDesigner = 'TestDesigner' - TestProgrammer = 'TestProgrammer' - Tester = 'Tester' - ReadOnlyDesigner = 'ReadOnlyDesigner' - ReadOnlyImplementer = 'ReadOnlyImplementer' - ReadOnlyTester = 'ReadOnlyTester' - - -class TOVExchangeFormat(Enum): - xml = 'xml' - json = 'json' - inherited = 'inherited' - - -class ProjectExchangeFormat(Enum): - default_xml = 'default_xml' - default_json = 'default_json' - - -class ImportResult(Enum): - Imported = 'Imported' - PartiallyImported = 'PartiallyImported' - NotImported = 'NotImported' - - -class ExecutionMode(Enum): - execute = 'execute' - continue_ = 'continue' - view = 'view' - simulate = 'simulate' - - -@dataclass -class UserGlobalRoles: - roles: List[GlobalHumanRole] - - -@dataclass -class UserProjectRoles: - userKey: str - roles: List[ProjectRole] - - -@dataclass -class ProjectUser: - key: str - name: str - login: str - projectRoles: List[ProjectRole] - - -@dataclass -class LicenseWarning: - expiresSoon: bool - baseCountExceeded: bool - gracedForeignLicense: bool - - -@dataclass -class LoginData: - login: str - password: str - force: Optional[bool] = None - context: Optional[str] = None - - -@dataclass -class ChangePasswordData: - login: str - password: str - newPassword: str - - -@dataclass -class LoginSession: - userKey: str - login: str - sessionToken: str - globalRoles: List[str] - internalUserManagement: bool - serverVersion: str - licenseWarning: Optional[LicenseWarning] = None - - -@dataclass -class ActiveUser: - login: str - contexts: List[str] - - -@dataclass -class TestLabel: - key: str - name: str - ownerKey: str - visibility: bool - libraryKey: Optional[str] = None - - -@dataclass -class TestLabelDataForUpdate: - name: Optional[str] = None - visibility: Optional[bool] = None - - -@dataclass -class TestLabelDataForInsert: - name: str - adaptName: Optional[bool] = True - - -@dataclass -class ProjectMember: - userKey: str - userLogin: str - userName: str - projectKey: str - projectName: str - roles: List[ProjectRole] - - -@dataclass -class UserInfo: - userKey: str - userLogin: str - userName: str - - -@dataclass -class ProjectSummary: - key: str - creationTime: str - name: str - status: ProjectStatus - visibility: bool - tovsCount: int - cyclesCount: int - description: str - lockerKey: Optional[str] = None - startDate: Optional[str] = None - endDate: Optional[str] = None - - -@dataclass -class ProjectContext: - tovName: str - cycleName: Optional[str] = None - executionMode: Optional[ExecutionMode] = None - - -@dataclass -class ProjectDetails: - key: str - creationTime: str - name: str - status: ProjectStatus - visibility: bool - tovsCount: int - cyclesCount: int - description: str - lockerKey: Optional[str] = None - startDate: Optional[str] = None - endDate: Optional[str] = None - projectContext: Optional[ProjectContext] = None - - -@dataclass -class ProjectInspection: - inspectionKey: str - severity: Severity - - -@dataclass -class ProjectCustomer: - customerName: str - customerAddress: str - testLab: str - placeOfInspection: str - contactPerson: str - - -@dataclass -class ProjectCreation: - pass - - -@dataclass -class UnknownProject(ProjectCreation): - pass - - -@dataclass -class NewProject(ProjectCreation): - pass - - -@dataclass -class ProjectImported(ProjectCreation): - project: str - - -@dataclass -class TOVCreation: - pass - - -@dataclass -class UnknownTOV(TOVCreation): - pass - - -@dataclass -class NewTOV(TOVCreation): - pass - - -@dataclass -class TOVCloned(TOVCreation): - tov: str - tovKey: Optional[str] = None - - -@dataclass -class TOVClonedFromSameProject(TOVCreation): - tov: str - tovKey: Optional[str] = None - - -@dataclass -class TOVClonedFromDifferentProject(TOVCreation): - project: str - tov: str - tovKey: Optional[str] = None - - -@dataclass -class TOVImportedAsNew(TOVCreation): - project: str - tov: str - - -@dataclass -class TOVImportedAsClone(TOVCreation): - project: str - tov: str - tovCloned: str - - -@dataclass -class TOVImportedAsNewFromPlugin(TOVCreation): - plugin: str - tov: str - - -@dataclass -class TOVImportedAsCloneFromPlugin(TOVCreation): - plugin: str - tov: str - tovCloned: str - - -@dataclass -class TOVImported(TOVCreation): - project: str - tov: str - - -@dataclass -class TOVDerivedFromSameProject(TOVCreation): - baseTOV: str - variantsDefinition: str - baseTOVKey: Optional[str] = None - variantsDefinitionKey: Optional[str] = None - - -@dataclass -class TOVDerivedFromDifferentProject(TOVCreation): - project: str - baseTOV: str - variantsDefinition: str - baseTOVKey: Optional[str] = None - variantsDefinitionKey: Optional[str] = None - - -@dataclass -class ProjectDetailsResponse: - projectKey: str - name: str - inspection: List[ProjectInspection] - testObjectName: str - id: str - customer: ProjectCustomer - status: ProjectStatus - visible: bool - testIntelligence: bool - description: str - creation: ProjectCreation - instantOfCreation: str - onlyAdminsMayManageUDFs: bool - variantsManagementEnabled: str - startDate: Optional[str] = None - endDate: Optional[str] = None - - -@dataclass -class TOVSummary: - key: str - creationTime: str - name: str - status: ProjectStatus - visibility: bool - cyclesCount: int - description: str - exchangeFormat: TOVExchangeFormat - lockerKey: Optional[str] = None - startDate: Optional[str] = None - endDate: Optional[str] = None - - -@dataclass -class TOVDetails: - key: str - creationTime: str - name: str - status: ProjectStatus - visibility: bool - cyclesCount: int - description: str - exchangeFormat: TOVExchangeFormat - lockerKey: Optional[str] = None - startDate: Optional[str] = None - endDate: Optional[str] = None - - -@dataclass -class TOVResponse: - name: str - visible: bool - projectKey: str - description: str - isBaseTov: bool - creation: TOVCreation - key: str - status: ProjectStatus - testingIntelligence: bool - instantOfCreation: str - cloningVisibility: bool - endDate: Optional[str] = None - startDate: Optional[str] = None - - -@dataclass -class OptionalLocalDate: - optional: Optional[str] = None - - -@dataclass -class CycleSummary: - key: str - creationTime: str - name: str - status: ProjectStatus - visibility: bool - description: str - startDate: Optional[str] = None - endDate: Optional[str] = None - - -@dataclass -class CycleDetails: - key: str - creationTime: str - name: str - status: ProjectStatus - visibility: bool - description: str - startDate: Optional[str] = None - endDate: Optional[str] = None - - -@dataclass -class CycleNode: - nodeType: ProjectTreeNodeType - key: str - name: str - creationTime: str - status: ProjectStatus - visibility: bool - - -@dataclass -class UserDetails: - key: str - login: str - name: str - email: str - passwordExpired: bool - active: bool - - -@dataclass -class UserDataForInsert: - login: str - password: str - name: str - email: str - - -@dataclass -class UserDataForUpdate: - login: Optional[str] = None - name: Optional[str] = None - email: Optional[str] = None - passwordExpired: Optional[bool] = None - active: Optional[bool] = None - - -@dataclass -class UserSummary: - key: str - login: str - name: str - active: bool - - -@dataclass -class TestBenchVersions: - version: str - databaseVersion: str - revision: str - - -@dataclass -class ServerLocations: - jBossHost: str - jBossJNDIPort: int - legacyPlayHost: str - legacyPlayPort: int - - -@dataclass -class ActionFailure: - code: int - message: str - description: Optional[str] = None - - -@dataclass -class UserDefinedField: - key: str - name: str - value: str - udfType: UDFType - - -@dataclass -class UDFEnumerationValue: - valueKey: str - valueName: str - - -@dataclass -class Tag: - key: str - name: str - isVariantsMarker: bool - - -@dataclass -class UserReference: - key: str - name: str - - -@dataclass -class RequirementAssignment: - key: str - name: str - id: str - extendedId: str - version: str - owner: str - status: str - priority: str - repositoryId: str - - -@dataclass -class ReportItemsSummary: - testThemesCount: int - testCaseSetsCount: int - testCasesCount: int - - -@dataclass -class ServerLocation: - host: str - port: int - - -@dataclass -class ReportExportOptions: - pass - - -@dataclass -class ReportScope: - projectKey: str - tovKey: str - cycleKey: Optional[str] = None - - -@dataclass -class ReportCreation: - creator: UserInfo - startDate: str - endDate: str - scope: ReportScope - exportOptions: ReportExportOptions - summary: ReportItemsSummary - - -@dataclass -class ReportMetaInformation: - formatVersion: str - serverLocation: ServerLocation - reportCreation: ReportCreation - serverVersions: TestBenchVersions - - -@dataclass -class RequirementReference: - key: str - edited: bool - - -@dataclass -class MetricsDistribution: - name: str - count: int - percentage: int - - -@dataclass -class DefectsDistribution: - statusDistribution: Optional[List[MetricsDistribution]] = None - priorityDistribution: Optional[List[MetricsDistribution]] = None - classDistribution: Optional[List[MetricsDistribution]] = None - - -@dataclass -class ConditionSummary: - key: str - uniqueID: str - name: str - description: str - version: Optional[str] = None - - -@dataclass -class SpecificationSummary: - key: str - description: str - reviewComment: str - status: SpecStatus - priority: Priority - locker: Optional[UserReference] = None - responsible: Optional[UserReference] = None - dueDate: Optional[str] = None - reviewer: Optional[UserReference] = None - - -@dataclass -class TestCaseSetSpecificationSummary: - key: str - description: str - reviewComment: str - status: SpecStatus - priority: Priority - preConditions: List[ConditionSummary] - postConditions: List[ConditionSummary] - udfs: List[UserDefinedField] - tags: List[Tag] - references: List[str] - requirements: List[RequirementReference] - responsible: Optional[UserReference] = None - dueDate: Optional[str] = None - reviewer: Optional[UserReference] = None - - -@dataclass -class TestCaseSpecificationDetails: - key: str - comments: str - udfs: List[UserDefinedField] - tags: List[Tag] - requirements: List[RequirementReference] - version: Optional[str] = None - - -@dataclass -class TestThemeSpecification: - key: str - description: str - reviewComment: str - status: SpecStatus - priority: Priority - udfs: List[UserDefinedField] - tags: List[Tag] - requirements: List[RequirementReference] - references: List[str] - dueDate: Optional[str] = None - reviewer: Optional[UserReference] = None - responsible: Optional[UserReference] = None - - -@dataclass -class TestCaseSetExecutionSummary: - key: str - comments: str - udfs: List[UserDefinedField] - tags: List[Tag] - - -@dataclass -class TestCaseSpecificationSummary: - key: str - comments: str - requirements: List[RequirementReference] - - -@dataclass -class TestCaseExecutionSummary: - key: str - status: ActivityStatus - execStatus: ExecStatus - verdict: VerdictStatus - defects: List[str] - comments: str - tester: Optional[UserReference] = None - - -@dataclass -class TestCaseSummary: - uniqueID: str - index: int - spec: TestCaseSpecificationSummary - exec: Optional[TestCaseExecutionSummary] = None - - -@dataclass -class TestCaseExecutionDetails: - key: str - status: ActivityStatus - execStatus: ExecStatus - verdict: VerdictStatus - plannedDuration: int - actualDuration: int - currentUser: UserReference - comments: str - defects: List[str] - udfs: List[UserDefinedField] - tags: List[Tag] - references: List[str] - version: Optional[str] = None - tester: Optional[UserReference] = None - - -class KindOfDataType(Enum): - Regular = 'Regular' - Reference = 'Reference' - Global = 'Global' - AcceptingGlobal = 'AcceptingGlobal' - - -@dataclass -class DataTypeSummary: - key: str - kind: KindOfDataType - name: str - path: str - uniqueID: str - version: Optional[str] = None - - -class ParameterDefinitionType(Enum): - DetailedInstance = 'DetailedInstance' - InstanceTable = 'InstanceTable' - AtomicInstance = 'AtomicInstance' - - -@dataclass -class ParameterValue: - name: str - key: str - dtSequenceKeys: List[str] - - -@dataclass -class InstanceArrayValue: - name: str - isDefaultValue: bool - - -class ParameterEvaluationType(Enum): - CallByValue = 'CallByValue' - CallByReference = 'CallByReference' - CallByReferenceMandatory = 'CallByReferenceMandatory' - - -class RepresentativeType(Enum): - Text = 'Text' - Placeholder = 'Placeholder' - Attachment = 'Attachment' - Hyperlink = 'Hyperlink' - Reference = 'Reference' - - -class ArgumentValueType(Enum): - EquivalenceClass = 'EquivalenceClass' - Representative = 'Representative' - InstancesArray = 'InstancesArray' - CBRRepresentative = 'CBRRepresentative' - - -class TestCaseDetailsOrigin(Enum): - Fallback = 'Fallback' - Generated = 'Generated' - Rejected = 'Rejected' - Restored = 'Restored' - Upgraded = 'Upgraded' - - -class ReferenceKind(Enum): - Reference = 'Reference' - Link = 'Link' - Attachment = 'Attachment' - Hyperlink = 'Hyperlink' - - -class TestFilterType(Enum): - TestTheme = 'TestTheme' - TestCaseSet = 'TestCaseSet' - TestCase = 'TestCase' - - -@dataclass -class FilterInfo: - name: str - filterType: TestFilterType - testThemeUID: Optional[str] = None - - -@dataclass -class TovStructureOptions(ReportExportOptions): - treeRootUID: Optional[str] = None - suppressFilteredData: Optional[bool] = None - suppressEmptyTestThemes: Optional[bool] = None - filters: Optional[List[FilterInfo]] = None - - -@dataclass -class CycleStructureOptions: - treeRootUID: Optional[str] = None - basedOnExecution: Optional[bool] = None - suppressFilteredData: Optional[bool] = None - suppressNotExecutable: Optional[bool] = None - suppressEmptyTestThemes: Optional[bool] = None - filters: Optional[List[FilterInfo]] = None - - -@dataclass -class CycleReportOptions(ReportExportOptions): - treeRootUID: Optional[str] = None - executionMode: Optional[ExecutionMode] = None - suppressFilteredData: Optional[bool] = None - suppressNotExecutable: Optional[bool] = None - suppressEmptyTestThemes: Optional[bool] = None - filters: Optional[List[FilterInfo]] = None - - -@dataclass -class DefectAttribute: - name: str - value: str - - -@dataclass -class ProjectDefectField: - values: List[str] - defaultValue: Optional[str] = None - - -@dataclass -class DefectUDF: - name: str - udfType: UDFType - isMandatory: bool - values: Optional[List[str]] = None - - -@dataclass -class TestStructureExecution: - status: ActivityStatus - execStatus: ExecStatus - verdict: VerdictStatus - - -@dataclass -class TestStructureItemExecution(TestStructureExecution): - key: str - locker: Optional[UserReference] = None - - -@dataclass -class TestCaseExecution(TestStructureExecution): - key: str - - -@dataclass -class TestStructureSpecification: - pass - - -@dataclass -class TestStructureItemSpecification(TestStructureSpecification): - key: str - status: SpecStatus - locker: Optional[UserReference] = None - - -@dataclass -class TestCaseSpecification(TestStructureSpecification): - key: str - - -@dataclass -class TestStructureItemBaseInformation: - key: str - numbering: str - path: str - parentKey: str - name: str - uniqueID: str - matchesFilter: bool - - -@dataclass -class TestCaseBaseInformation: - numbering: str - parentKey: str - name: str - uniqueID: str - matchesFilter: bool - - -@dataclass -class TestStructureAutomation: - key: str - status: AutStatus - locker: Optional[UserReference] = None - - -class TestStructureElementType(Enum): - RootNode = 'RootNode' - TestThemeNode = 'TestThemeNode' - TestCaseSetNode = 'TestCaseSetNode' - TestCaseNode = 'TestCaseNode' - - -@dataclass -class AttachedFilter: - key: str - name: str - filterType: TestFilterType - content: str - - -@dataclass -class CreatedJob: - jobID: str - - -@dataclass -class JobProgress: - totalItemsCount: int - handledItemsCount: int - - -@dataclass -class ReportingResult: - pass - - -@dataclass -class ReportingFailure(ReportingResult): - error: ActionFailure - - -@dataclass -class ReportingSuccess(ReportingResult): - reportName: str - - -@dataclass -class ExecutionImportingResult: - pass - - -@dataclass -class ExecutionImportingFailure(ExecutionImportingResult): - error: ActionFailure - - -@dataclass -class CheckedInElement: - elementName: str - newlyCheckedIn: bool - versionName: str - - -@dataclass -class CreatedDefect: - foreignKey: str - createdKey: str - - -@dataclass -class CreatedReference: - foreignKey: str - createdKey: str - - -@dataclass -class TestCaseExecutionImportResult: - key: str - executionKey: str - uid: str - importResult: ImportResult - warnings: List[ActionFailure] - error: Optional[ActionFailure] = None - - -@dataclass -class CheckInData: - comment: str - label: Optional[str] = None - - -@dataclass -class ExecutionImportSimulationOptions: - fileName: str - treeRootUID: Optional[str] = None - filters: Optional[List[FilterInfo]] = None - - -@dataclass -class UploadedFile: - fileName: str - - -@dataclass -class TestStructureElement: - key: str - uniqueID: str - name: str - executionKey: str - - -@dataclass -class TestThemeExecution: - key: str - status: ActivityStatus - execStatus: ExecStatus - verdict: VerdictStatus - udfs: List[UserDefinedField] - tags: List[Tag] - references: List[str] - comments: Optional[str] = None - responsible: Optional[UserReference] = None - - -@dataclass -class TestThemeDetails: - key: str - name: str - uniqueID: str - numbering: str - path: str - spec: TestThemeSpecification - exec: Optional[TestThemeExecution] = None - - -@dataclass -class TestObjectVersionCSVReportOptions: - reportRootUID: Optional[str] = None - fields: Optional[List[str]] = None - characterEncoding: Optional[str] = None - - -@dataclass -class JWTResponse: - accessToken: str - expiresAt: str - - -@dataclass -class TestCycleCSVReportOptions: - reportRootUID: Optional[str] = None - fields: Optional[List[str]] = None - characterEncoding: Optional[str] = None - basedOn: Optional[str] = None - - -class UDFLocation(Enum): - TestThemesInSpecification = 'TestThemesInSpecification' - TestCaseSetsInSpecification = 'TestCaseSetsInSpecification' - TestCasesInSpecification = 'TestCasesInSpecification' - TestThemesInExecution = 'TestThemesInExecution' - TestCaseSetsInExecution = 'TestCaseSetsInExecution' - TestCasesInExecution = 'TestCasesInExecution' - - -class UDFAbsolutePosition(Enum): - First = 'First' - Last = 'Last' - - -@dataclass -class AfterUDF: - udfKey: str - - -@dataclass -class BeforeUDF: - udfKey: str - - -@dataclass -class UDFForUpdate: - name: Optional[str] = None - isMandatory: Optional[bool] = None - definedFor: Optional[List[UDFLocation]] = None - udfType: Optional[UDFType] = None - enumerationValues: Optional[List[UDFEnumerationValue]] = None - - -@dataclass -class UDFsMoveResponse: - targetKeys: List[str] - - -@dataclass -class TestFilter: - key: str - name: str - owner: str - pluginName: Optional[str] = None - - -@dataclass -class UDFUpdatedResponse: - pass - - -@dataclass -class UDFForRestriction: - onlyAdminsMayManageUDFs: bool - - -@dataclass -class UDFRestrictionResponse: - projectKey: str - onlyAdminsMayManageUDFs: bool - - -@dataclass -class CycleCreation: - pass - - -@dataclass -class CycleCloned(CycleCreation): - cycle: str - - -@dataclass -class CycleImported(CycleCreation): - project: str - tov: str - cycle: str - - -@dataclass -class CycleImportedAsNew(CycleCreation): - tov: str - cycle: str - - -@dataclass -class CycleImportedAsClone(CycleCreation): - tov: str - cycle: str - clonedCycle: str - - -@dataclass -class CycleImportedAsNewFromPlugin(CycleCreation): - plugin: str - tov: str - cycle: str - - -@dataclass -class CycleClonedFromPlugin(CycleCreation): - plugin: str - cycle: str - - -@dataclass -class CycleImportedAsCloneFromPlugin(CycleCreation): - plugin: str - tov: str - cycle: str - clonedCycle: str - - -@dataclass -class UnknownCycle(CycleCreation): - pass - - -@dataclass -class NewCycle(CycleCreation): - pass - - -@dataclass -class NewPassword: - newPassword: str - - -@dataclass -class ImageDetails: - key: str - suffix: str - imageData: str - - -@dataclass -class UdfValueForImport: - udfKey: str - value: str - - -@dataclass -class RichTextForImport: - html: Optional[str] = None - plain: Optional[str] = None - - -@dataclass -class ExecutionResultForImport: - status: ActivityStatus - execStatus: ExecStatus - verdict: VerdictStatus - timestamp: Optional[str] = None - - -@dataclass -class TestCaseExecutionForImport: - uniqueID: str - testCaseExecutionKey: str - result: ExecutionResultForImport - durationMillis: int - testerKey: Optional[str] = None - comments: Optional[RichTextForImport] = None - defects: Optional[List[str]] = None - udfs: Optional[List[UdfValueForImport]] = None - references: Optional[List[str]] = None - - -@dataclass -class TestCaseSetExecutionForImport: - testCaseSetKey: str - executionKey: str - durationMillis: int - testCases: List[TestCaseExecutionForImport] - testerKey: Optional[str] = None - comments: Optional[RichTextForImport] = None - udfs: Optional[List[UdfValueForImport]] = None - - -@dataclass -class KeywordCallExecution: - verdict: KeywordVerdict - duration: int - currentUser: UserReference - comments: str - references: List[str] - defects: List[str] - time: Optional[str] = None - tester: Optional[UserReference] = None - - -class Permission(Enum): - AccessSecuredData = 'AccessSecuredData' - DeleteUserAccount = 'DeleteUserAccount' - DeleteUserSession = 'DeleteUserSession' - DownloadReportFile = 'DownloadReportFile' - ImportExecutionResults = 'ImportExecutionResults' - ModifyGlobalTestLabels = 'ModifyGlobalTestLabels' - ModifyProjectDetails = 'ModifyProjectDetails' - ModifyProjectUDFs = 'ModifyProjectUDFs' - ModifySpecifications = 'ModifySpecifications' - ModifySpecManagementInfo = 'ModifySpecManagementInfo' - ModifySpecPriorityAndDueDate = 'ModifySpecPriorityAndDueDate' - ModifyTestElements = 'ModifyTestElements' - ModifyTestLabels = 'ModifyTestLabels' - ModifyUserData = 'ModifyUserData' - ModifyUserRolesInProject = 'ModifyUserRolesInProject' - PrivatizeGlobalTestLabels = 'PrivatizeGlobalTestLabels' - ReadActiveUsersList = 'ReadActiveUsersList' - ReadCompleteProjectsList = 'ReadCompleteProjectsList' - ReadCompleteUsersList = 'ReadCompleteUsersList' - ReadCycleReport = 'ReadCycleReport' - ReadCycleReportOverRMI = 'ReadCycleReportOverRMI' - ReadCycleRequirements = 'ReadCycleRequirements' - ReadDefectsMetricDistribution = 'ReadDefectsMetricDistribution' - ReadExecutionImportingJobDetails = 'ReadExecutionImportingJobDetails' - ReadInvisibleProjectContent = 'ReadInvisibleProjectContent' - ReadOwnProjectsList = 'ReadOwnProjectsList' - ReadOwnUserDetails = 'ReadOwnUserDetails' - ReadProjectDefectsAndTheirAssignments = 'ReadProjectDefectsAndTheirAssignments' - ReadProjectDetails = 'ReadProjectDetails' - ReadProjectExportOverRMI = 'ReadProjectExportOverRMI' - ReadProjectHierarchy = 'ReadProjectHierarchy' - ReadProjectMembers = 'ReadProjectMembers' - ReadProjectUDFs = 'ReadProjectUDFs' - ReadReportingJobDetails = 'ReadReportingJobDetails' - ReadTestCaseDetails = 'ReadTestCaseDetails' - ReadTestCaseSetDetails = 'ReadTestCaseSetDetails' - ReadTestElements = 'ReadTestElements' - ReadTestLabels = 'ReadTestLabels' - ReadTestThemeDetails = 'ReadTestThemeDetails' - ReadTestThemeStatusDistribution = 'ReadTestThemeStatusDistribution' - ReadTestThemeTree = 'ReadTestThemeTree' - ReadTovReport = 'ReadTovReport' - ReadTovReportOverRMI = 'ReadTovReportOverRMI' - ReadTovRequirements = 'ReadTovRequirements' - ReadUserDetails = 'ReadUserDetails' - ReadUserMemberships = 'ReadUserMemberships' - ReadUserSessions = 'ReadUserSessions' - RestrictProjectUDFs = 'RestrictProjectUDFs' - SynchronizeUsers = 'SynchronizeUsers' - UnlockForeignSpecs = 'UnlockForeignSpecs' - UnlockForeignTestElements = 'UnlockForeignTestElements' - - -@dataclass -class DefaultValue: - name: str - valueType: ArgumentValueType - - -@dataclass -class SubdivisionDetails: - key: str - name: str - uniqueID: str - description: str - path: str - references: List[str] - locker: Optional[UserReference] = None - parentUniqueID: Optional[str] = None - libraryKey: Optional[str] = None - - -@dataclass -class KeywordParameterForInsert: - name: str - dataTypeKey: Optional[str] = None - evaluationType: Optional[ParameterEvaluationType] = None - - -@dataclass -class ParameterDetails: - key: str - name: str - definitionType: ParameterDefinitionType - evaluationType: ParameterEvaluationType - dataTypeKey: Optional[str] = None - defaultValue: Optional[DefaultValue] = None - signatureID: Optional[str] = None - - -@dataclass -class TOVNode: - nodeType: ProjectTreeNodeType - key: str - name: str - creationTime: str - status: ProjectStatus - visibility: bool - exchangeFormat: TOVExchangeFormat - children: List[CycleNode] - - -@dataclass -class UDF: - udfKey: str - name: str - projectKey: str - isMandatory: bool - definedFor: List[UDFLocation] - - -@dataclass -class BooleanUDF(UDF): - pass - - -@dataclass -class EnumerationUDF(UDF): - enumerationValues: List[UDFEnumerationValue] - - -@dataclass -class StringUDF(UDF): - pass - - -@dataclass -class ReferenceAssignment: - key: str - value: str - referenceType: ReferenceKind - versionName: Optional[str] = None - - -@dataclass -class RepresentativeValue: - name: str - valueType: RepresentativeType - isDefaultValue: bool - - -@dataclass -class AssignedDefect: - key: str - title: str - id: str - description: str - identicalVersionKey: str - status: str - priority: str - classification: str - creationTime: str - references: List[str] - udfs: List[DefectAttribute] - version: Optional[str] = None - tester: Optional[str] = None - lastEditTime: Optional[str] = None - lastEditorKey: Optional[str] = None - defectManagementSystem: Optional[str] = None - defectManagementProject: Optional[str] = None - - -@dataclass -class ExternalDefectManagement: - system: str - project: str - udfs: List[DefectUDF] - - -@dataclass -class DefectConfig: - status: ProjectDefectField - classification: ProjectDefectField - priority: ProjectDefectField - external: Optional[ExternalDefectManagement] = None - - -@dataclass -class TestStructureTreeNode: - elementType: TestStructureElementType - - -@dataclass -class RootNode(TestStructureTreeNode): - base: TestStructureItemBaseInformation - filters: List[AttachedFilter] - - -@dataclass -class TestThemeNode(TestStructureTreeNode): - base: TestStructureItemBaseInformation - filters: List[AttachedFilter] - spec: Optional[TestStructureItemSpecification] = None - aut: Optional[TestStructureAutomation] = None - exec: Optional[TestStructureItemExecution] = None - - -@dataclass -class TestCaseSetNode(TestStructureTreeNode): - base: TestStructureItemBaseInformation - spec: Optional[TestStructureItemSpecification] = None - aut: Optional[TestStructureAutomation] = None - exec: Optional[TestStructureItemExecution] = None - - -@dataclass -class TestCaseNode(TestStructureTreeNode): - base: TestCaseBaseInformation - spec: Optional[TestCaseSpecification] = None - exec: Optional[TestCaseExecution] = None - - -@dataclass -class TestStructureTree: - nodes: List[Union[TestThemeNode, TestCaseSetNode, TestCaseNode]] - root: Optional[Union[RootNode, TestThemeNode, TestCaseSetNode, TestCaseNode]] = None - - -@dataclass -class ReportingCompletion: - time: str - result: ReportingResult - - -@dataclass -class ExecutionImportingCompletion: - time: str - result: ExecutionImportingResult - - -@dataclass -class TestCaseSetExecutionImportResult: - key: str - executionKey: str - name: str - uid: str - finished: bool - testCases: List[TestCaseExecutionImportResult] - error: Optional[ActionFailure] = None - - -@dataclass -class ExecutionImportOptions: - fileName: str - treeRootUID: Optional[str] = None - useExistingDefect: Optional[bool] = None - discardTesterInformation: Optional[bool] = None - defaultTester: Optional[str] = None - filters: Optional[List[FilterInfo]] = None - checkInData: Optional[CheckInData] = None - - -@dataclass -class JWTDataOptions: - permissions: List[Permission] - projectKey: Optional[str] = None - tovKey: Optional[str] = None - cycleKey: Optional[str] = None - subject: Optional[str] = None - expiresAfterSeconds: Optional[int] = None - - -@dataclass -class UDFPosition: - after: Optional[AfterUDF] = None - before: Optional[BeforeUDF] = None - absolute: Optional[UDFAbsolutePosition] = None - - -@dataclass -class UDFForMove: - udfKeys: List[str] - newPositions: UDFPosition - - -@dataclass -class StringUDFUpdatedResponse(UDFUpdatedResponse): - stringUDF: StringUDF - affectedFilters: List[TestFilter] - - -@dataclass -class BooleanUDFUpdatedResponse(UDFUpdatedResponse): - booleanUdf: BooleanUDF - affectedFilters: List[TestFilter] - - -@dataclass -class EnumerationUDFUpdatedResponse(UDFUpdatedResponse): - enumerationUDF: EnumerationUDF - affectedFilters: List[TestFilter] - - -@dataclass -class CycleResponse: - name: str - visible: bool - projectKey: str - description: str - creation: CycleCreation - key: str - tovKey: str - status: ProjectStatus - testingIntelligence: bool - instantOfCreation: str - endDate: Optional[str] = None - startDate: Optional[str] = None - - -@dataclass -class ImageInfo: - key: str - value: ImageDetails - - -@dataclass -class KeywordDetails: - key: str - name: str - uniqueID: str - status: TestElementStatus - defaultCallType: KeywordCallType - description: str - path: str - parameters: List[ParameterDetails] - preConditions: List[ConditionSummary] - postConditions: List[ConditionSummary] - references: List[str] - locker: Optional[UserReference] = None - version: Optional[str] = None - parentUniqueID: Optional[str] = None - libraryKey: Optional[str] = None - advancedContent: Optional[AdvancedContent] = None - - -@dataclass -class ProjectNode: - nodeType: ProjectTreeNodeType - key: str - name: str - creationTime: str - status: ProjectStatus - visibility: bool - exchangeFormat: ProjectExchangeFormat - children: List[TOVNode] - - -@dataclass -class ParameterSummary: - definitionType: ParameterDefinitionType - key: str - name: str - evaluationType: ParameterEvaluationType - dataType: Optional[DataTypeSummary] = None - value: Optional[str] = None - valueType: Optional[RepresentativeType] = None - representativeValue: Optional[RepresentativeValue] = None - parameterValue: Optional[ParameterValue] = None - instanceArrayValue: Optional[InstanceArrayValue] = None - alias: Optional[str] = None - - -@dataclass -class ReportingJob: - id: str - projectKey: str - owner: str - start: str - progress: Optional[JobProgress] = None - completion: Optional[ReportingCompletion] = None - - -@dataclass -class ExecutionImportingJob: - id: str - projectKey: str - owner: str - start: str - progress: Optional[JobProgress] = None - completion: Optional[ExecutionImportingCompletion] = None - - -@dataclass -class ExecutionImportingSuccess(ExecutionImportingResult): - testCaseSets: List[TestCaseSetExecutionImportResult] - checkedInTestStructureElements: List[TestStructureElement] - checkedInTestElements: List[CheckedInElement] - createdDefects: List[CreatedDefect] - createdReferences: List[CreatedReference] - - -@dataclass -class UDFForInsert: - name: str - isMandatory: bool - definedFor: List[UDFLocation] - udfType: UDFType - enumerationName: Optional[List[str]] = None - position: Optional[UDFPosition] = None - - -@dataclass -class RichTextInfo: - html: str - images: List[ImageInfo] - - -@dataclass -class KeywordCallSpecification: - key: str - name: str - sequencePhase: SequencePhase - callType: KeywordCallType - comments: str - callParameters: List[ParameterSummary] - keywordType: Optional[KeywordType] = None - description: Optional[str] = None - keywordKey: Optional[str] = None - callingKeywordKey: Optional[str] = None - - -@dataclass -class KeywordCall: - sequenceID: str - numbering: str - spec: KeywordCallSpecification - parentID: Optional[str] = None - exec: Optional[KeywordCallExecution] = None - - -@dataclass -class KeywordForInsert: - parentKey: str - name: str - parameters: List[KeywordParameterForInsert] - uid: Optional[str] = None - description: Optional[RichTextInfo] = None - advancedContent: Optional[AdvancedContent] = None - callType: Optional[KeywordCallType] = None - - -@dataclass -class SubdivisionForInsert: - name: str - parentKey: Optional[str] = None - uid: Optional[str] = None - description: Optional[RichTextInfo] = None - - -@dataclass -class SubdivisionForUpdate: - name: Optional[str] = None - description: Optional[RichTextInfo] = None - - -@dataclass -class KeywordDetailsForUpdate: - name: Optional[str] = None - description: Optional[RichTextInfo] = None - callType: Optional[KeywordCallType] = None - locker: Optional[OptionalUser] = None - advancedContent: Optional[OptionalAdvancedContent] = None - - -@dataclass -class SpecificationDetailsForUpdate: - responsible: Optional[OptionalUser] = None - reviewer: Optional[OptionalUser] = None - locker: Optional[OptionalUser] = None - priority: Optional[Priority] = None - dueDate: Optional[OptionalLocalDate] = None - description: Optional[RichTextInfo] = None - reviewComment: Optional[RichTextInfo] = None - - -@dataclass -class ProjectDetailsForUpdate: - name: Optional[str] = None - testObjectName: Optional[str] = None - id: Optional[str] = None - customerName: Optional[str] = None - customerAddress: Optional[str] = None - contactPerson: Optional[str] = None - testLab: Optional[str] = None - placeOfInspection: Optional[str] = None - status: Optional[ProjectStatus] = None - isVisibleToTester: Optional[bool] = None - isTestingIntelligenceActive: Optional[bool] = None - description: Optional[RichTextInfo] = None - startDate: Optional[OptionalLocalDate] = None - endDate: Optional[OptionalLocalDate] = None - inspections: Optional[List[str]] = None - variantsManagementEnabled: Optional[OperationalState] = None - - -@dataclass -class TOVForUpdate: - name: Optional[str] = None - endDate: Optional[OptionalLocalDate] = None - visible: Optional[bool] = None - description: Optional[RichTextInfo] = None - isBaseTov: Optional[bool] = None - status: Optional[ProjectStatus] = None - testingIntelligence: Optional[bool] = None - startDate: Optional[OptionalLocalDate] = None - cloningVisibility: Optional[bool] = None - - -@dataclass -class TestCaseSetDetails: - key: str - numbering: str - path: str - uniqueID: str - name: str - spec: TestCaseSetSpecificationSummary - testCases: List[TestCaseSummary] - testSequence: List[KeywordCall] - parameters: List[ParameterDetails] - keywords: List[KeywordDetails] - exec: Optional[TestCaseSetExecutionSummary] = None - - -@dataclass -class TestCaseDetails: - uniqueID: str - spec: TestCaseSpecificationDetails - testSequence: List[KeywordCall] - parameters: List[ParameterSummary] - keywords: List[KeywordDetails] - exec: Optional[TestCaseExecutionDetails] = None - origin: Optional[TestCaseDetailsOrigin] = None - - -@dataclass -class CycleForUpdate: - name: Optional[str] = None - endDate: Optional[OptionalLocalDate] = None - visible: Optional[bool] = None - description: Optional[RichTextInfo] = None - status: Optional[ProjectStatus] = None - testingIntelligence: Optional[bool] = None - startDate: Optional[OptionalLocalDate] = None +# generated by datamodel-codegen: +# filename: openapi.yml +# timestamp: 2026-05-21T13:56:04+00:00 + +from __future__ import annotations + +__VERSION__ = "4.0.45" + +from dataclasses import dataclass +from enum import Enum + + +class TestElementStatus(Enum): + InProgress = "InProgress" + Released = "Released" + + +class ContentType(Enum): + txt = "txt" + xml = "xml" + + +class ReferenceUpdateMode(Enum): + Replace = "Replace" + Extend = "Extend" + + +@dataclass +class AdvancedContent: + contentType: ContentType | None = None + contentExtID: str | None = None + content: str | None = None + + +@dataclass +class OptionalAdvancedContent: + optional: AdvancedContent | None = None + + +@dataclass +class OptionalUser: + optional: str | None = None + + +class ProjectStatus(Enum): + Planned = "Planned" + Active = "Active" + Finished = "Finished" + Closed = "Closed" + + +class DefectMetricType(Enum): + Status = "Status" + Priority = "Priority" + Classification = "Classification" + + +class ProjectTreeNodeType(Enum): + Project = "Project" + Version = "Version" + Cycle = "Cycle" + + +class Severity(Enum): + Information = "Information" + Warning = "Warning" + Error = "Error" + + +class UDFType(Enum): + String = "String" + Enumeration = "Enumeration" + Boolean = "Boolean" + + +class TestLabelVisibilityType(Enum): + OnlyPrivate = "OnlyPrivate" + OnlyPublic = "OnlyPublic" + All = "All" + + +class AutStatus(Enum): + NotPlanned = "NotPlanned" + Planned = "Planned" + InProgress = "InProgress" + InReview = "InReview" + Released = "Released" + + +class SpecStatus(Enum): + NotPlanned = "NotPlanned" + Planned = "Planned" + InProgress = "InProgress" + InReview = "InReview" + Released = "Released" + + +class Priority(Enum): + Undefined = "Undefined" + Low = "Low" + Middle = "Middle" + High = "High" + + +class ActivityStatus(Enum): + NotPlanned = "NotPlanned" + Planned = "Planned" + Assigned = "Assigned" + Running = "Running" + Skipped = "Skipped" + Canceled = "Canceled" + Performed = "Performed" + + +class ExecStatus(Enum): + NotBlocked = "NotBlocked" + Blocked = "Blocked" + + +class VerdictStatus(Enum): + Undefined = "Undefined" + ToVerify = "ToVerify" + Fail = "Fail" + Pass = "Pass" + + +class KeywordVerdict(Enum): + Pass = "Pass" + Fail = "Fail" + Skipped = "Skipped" + ToVerify = "ToVerify" + Warn = "Warn" + Undefined = "Undefined" + Blocked = "Blocked" + + +class SequencePhase(Enum): + Setup = "Setup" + TestStep = "TestStep" + Teardown = "Teardown" + + +class KeywordCallType(Enum): + Flow = "Flow" + Check = "Check" + + +class KeywordType(Enum): + Atomic = "Atomic" + Compound = "Compound" + Textual = "Textual" + + +class OperationalState(Enum): + Enabled = "Enabled" + Disabled = "Disabled" + + +class GlobalHumanRole(Enum): + Administrator = "Administrator" + ProjectAdministrator = "ProjectAdministrator" + ProjectUser = "ProjectUser" + + +class ProjectRole(Enum): + TestManager = "TestManager" + TestDesigner = "TestDesigner" + TestProgrammer = "TestProgrammer" + Tester = "Tester" + ReadOnlyDesigner = "ReadOnlyDesigner" + ReadOnlyImplementer = "ReadOnlyImplementer" + ReadOnlyTester = "ReadOnlyTester" + + +class TOVExchangeFormat(Enum): + xml = "xml" + json = "json" + inherited = "inherited" + + +class ProjectExchangeFormat(Enum): + default_xml = "default_xml" + default_json = "default_json" + + +class ImportResult(Enum): + Imported = "Imported" + PartiallyImported = "PartiallyImported" + NotImported = "NotImported" + + +class ExecutionMode(Enum): + execute = "execute" + continue_ = "continue" + view = "view" + simulate = "simulate" + + +@dataclass +class UserGlobalRoles: + roles: list[GlobalHumanRole] + + +@dataclass +class UserProjectRoles: + userKey: str + roles: list[ProjectRole] + + +@dataclass +class ProjectUser: + key: str + name: str + login: str + projectRoles: list[ProjectRole] + + +@dataclass +class LicenseWarning: + expiresSoon: bool + baseCountExceeded: bool + gracedForeignLicense: bool + + +@dataclass +class LoginData: + login: str + password: str + force: bool | None = None + context: str | None = None + + +@dataclass +class ChangePasswordData: + login: str + password: str + newPassword: str + + +@dataclass +class LoginSession: + userKey: str + login: str + sessionToken: str + globalRoles: list[str] + internalUserManagement: bool + serverVersion: str + licenseWarning: LicenseWarning | None = None + + +@dataclass +class ActiveUser: + login: str + contexts: list[str] + + +@dataclass +class TestLabel: + key: str + name: str + ownerKey: str + visibility: bool + libraryKey: str | None = None + + +@dataclass +class TestLabelDataForUpdate: + name: str | None = None + visibility: bool | None = None + + +@dataclass +class TestLabelDataForInsert: + name: str + adaptName: bool | None = True + + +@dataclass +class ProjectMember: + userKey: str + userLogin: str + userName: str + projectKey: str + projectName: str + roles: list[ProjectRole] + + +@dataclass +class UserInfo: + userKey: str + userLogin: str + userName: str + + +@dataclass +class ProjectSummary: + key: str + creationTime: str + name: str + status: ProjectStatus + visibility: bool + tovsCount: int + cyclesCount: int + description: str + lockerKey: str | None = None + startDate: str | None = None + endDate: str | None = None + + +@dataclass +class ProjectContext: + tovName: str + cycleName: str | None = None + executionMode: ExecutionMode | None = None + + +@dataclass +class ProjectDetails: + key: str + creationTime: str + name: str + status: ProjectStatus + visibility: bool + tovsCount: int + cyclesCount: int + description: str + exchangeFormat: ProjectExchangeFormat + lockerKey: str | None = None + startDate: str | None = None + endDate: str | None = None + projectContext: ProjectContext | None = None + + +@dataclass +class ProjectInspection: + inspectionKey: str + severity: Severity + + +@dataclass +class ProjectCustomer: + customerName: str + customerAddress: str + testLab: str + placeOfInspection: str + contactPerson: str + + +@dataclass +class ProjectCreation: + pass + + +@dataclass +class UnknownProject(ProjectCreation): + pass + + +@dataclass +class NewProject(ProjectCreation): + pass + + +@dataclass +class ProjectImported(ProjectCreation): + project: str + + +@dataclass +class TOVCreation: + pass + + +@dataclass +class UnknownTOV(TOVCreation): + pass + + +@dataclass +class NewTOV(TOVCreation): + pass + + +@dataclass +class TOVCloned(TOVCreation): + tov: str + tovKey: str | None = None + + +@dataclass +class TOVClonedFromSameProject(TOVCreation): + tov: str + tovKey: str | None = None + + +@dataclass +class TOVClonedFromDifferentProject(TOVCreation): + project: str + tov: str + tovKey: str | None = None + + +@dataclass +class TOVImportedAsNew(TOVCreation): + project: str + tov: str + + +@dataclass +class TOVImportedAsClone(TOVCreation): + project: str + tov: str + tovCloned: str + + +@dataclass +class TOVImportedAsNewFromPlugin(TOVCreation): + plugin: str + tov: str + + +@dataclass +class TOVImportedAsCloneFromPlugin(TOVCreation): + plugin: str + tov: str + tovCloned: str + + +@dataclass +class TOVImported(TOVCreation): + project: str + tov: str + + +@dataclass +class TOVDerivedFromSameProject(TOVCreation): + baseTOV: str + variantsDefinition: str + baseTOVKey: str | None = None + variantsDefinitionKey: str | None = None + + +@dataclass +class TOVDerivedFromDifferentProject(TOVCreation): + project: str + baseTOV: str + variantsDefinition: str + baseTOVKey: str | None = None + variantsDefinitionKey: str | None = None + + +@dataclass +class ProjectDetailsResponse: + projectKey: str + name: str + inspection: list[ProjectInspection] + testObjectName: str + id: str + customer: ProjectCustomer + status: ProjectStatus + visible: bool + testIntelligence: bool + description: str + creation: ProjectCreation + instantOfCreation: str + onlyAdminsMayManageUDFs: bool + variantsManagementEnabled: str + exchangeFormat: ProjectExchangeFormat + startDate: str | None = None + endDate: str | None = None + + +@dataclass +class TOVSummary: + key: str + creationTime: str + name: str + status: ProjectStatus + visibility: bool + cyclesCount: int + description: str + exchangeFormat: TOVExchangeFormat + lockerKey: str | None = None + startDate: str | None = None + endDate: str | None = None + + +@dataclass +class TOVDetails: + key: str + creationTime: str + name: str + status: ProjectStatus + visibility: bool + cyclesCount: int + description: str + exchangeFormat: TOVExchangeFormat + lockerKey: str | None = None + startDate: str | None = None + endDate: str | None = None + + +@dataclass +class TOVResponse: + name: str + visible: bool + projectKey: str + description: str + isBaseTov: bool + creation: TOVCreation + key: str + status: ProjectStatus + testingIntelligence: bool + instantOfCreation: str + cloningVisibility: bool + exchangeFormat: TOVExchangeFormat + endDate: str | None = None + startDate: str | None = None + + +@dataclass +class OptionalLocalDate: + optional: str | None = None + + +@dataclass +class CycleSummary: + key: str + creationTime: str + name: str + status: ProjectStatus + visibility: bool + description: str + startDate: str | None = None + endDate: str | None = None + + +@dataclass +class CycleDetails: + key: str + creationTime: str + name: str + status: ProjectStatus + visibility: bool + description: str + startDate: str | None = None + endDate: str | None = None + + +@dataclass +class CycleNode: + nodeType: ProjectTreeNodeType + key: str + name: str + creationTime: str + status: ProjectStatus + visibility: bool + + +@dataclass +class UserDetails: + key: str + login: str + name: str + email: str + passwordExpired: bool + active: bool + + +@dataclass +class UserDataForInsert: + login: str + password: str + name: str + email: str + + +@dataclass +class UserDataForUpdate: + login: str | None = None + name: str | None = None + email: str | None = None + passwordExpired: bool | None = None + active: bool | None = None + + +@dataclass +class UserSummary: + key: str + login: str + name: str + active: bool + + +@dataclass +class TestBenchVersions: + version: str + databaseVersion: str + revision: str + + +@dataclass +class ServerLocations: + jBossHost: str + jBossJNDIPort: int + legacyPlayHost: str + legacyPlayPort: int + + +@dataclass +class ActionFailure: + code: int + message: str + description: str | None = None + + +@dataclass +class UserDefinedField: + key: str + name: str + value: str + udfType: UDFType + + +@dataclass +class UDFEnumerationValue: + valueKey: str + valueName: str + + +@dataclass +class Tag: + key: str + name: str + isVariantsMarker: bool + + +@dataclass +class UserReference: + key: str + name: str + + +@dataclass +class RequirementAssignment: + key: str + name: str + id: str + extendedId: str + version: str + owner: str + status: str + priority: str + repositoryId: str + + +@dataclass +class ReportItemsSummary: + testThemesCount: int + testCaseSetsCount: int + testCasesCount: int + + +@dataclass +class ServerLocation: + host: str + port: int + + +@dataclass +class ReportExportOptions: + pass + + +@dataclass +class ReportScope: + projectKey: str + tovKey: str + cycleKey: str | None = None + + +@dataclass +class ReportCreation: + creator: UserInfo + startDate: str + endDate: str + scope: ReportScope + summary: ReportItemsSummary + exportOptions: ReportExportOptions | None = None + + +@dataclass +class ReportMetaInformation: + formatVersion: str + serverLocation: ServerLocation + reportCreation: ReportCreation + serverVersions: TestBenchVersions + + +@dataclass +class RequirementReference: + key: str + edited: bool + + +@dataclass +class MetricsDistribution: + name: str + count: int + percentage: int + + +@dataclass +class DefectsDistribution: + statusDistribution: list[MetricsDistribution] | None = None + priorityDistribution: list[MetricsDistribution] | None = None + classDistribution: list[MetricsDistribution] | None = None + + +@dataclass +class ConditionSummary: + key: str + uniqueID: str + name: str + description: str + version: str | None = None + + +@dataclass +class SpecificationSummary: + key: str + description: str + reviewComment: str + status: SpecStatus + priority: Priority + locker: UserReference | None = None + responsible: UserReference | None = None + dueDate: str | None = None + reviewer: UserReference | None = None + + +@dataclass +class TestCaseSetSpecificationSummary: + key: str + description: str + reviewComment: str + status: SpecStatus + priority: Priority + preConditions: list[ConditionSummary] + postConditions: list[ConditionSummary] + udfs: list[UserDefinedField] + tags: list[Tag] + references: list[str] + requirements: list[RequirementReference] + responsible: UserReference | None = None + dueDate: str | None = None + reviewer: UserReference | None = None + + +@dataclass +class TestCaseSpecificationDetails: + key: str + comments: str + udfs: list[UserDefinedField] + tags: list[Tag] + requirements: list[RequirementReference] + version: str | None = None + + +@dataclass +class TestThemeSpecification: + key: str + description: str + reviewComment: str + status: SpecStatus + priority: Priority + udfs: list[UserDefinedField] + tags: list[Tag] + requirements: list[RequirementReference] + references: list[str] + dueDate: str | None = None + reviewer: UserReference | None = None + responsible: UserReference | None = None + + +@dataclass +class TestCaseSetExecutionSummary: + key: str + comments: str + udfs: list[UserDefinedField] + tags: list[Tag] + + +@dataclass +class TestCaseSpecificationSummary: + key: str + comments: str + requirements: list[RequirementReference] + + +@dataclass +class TestCaseExecutionSummary: + key: str + status: ActivityStatus + execStatus: ExecStatus + verdict: VerdictStatus + defects: list[str] + comments: str + tester: UserReference | None = None + + +@dataclass +class TestCaseSummary: + uniqueID: str + index: int + spec: TestCaseSpecificationSummary + exec: TestCaseExecutionSummary | None = None + + +@dataclass +class TestCaseExecutionDetails: + key: str + status: ActivityStatus + execStatus: ExecStatus + verdict: VerdictStatus + plannedDuration: int + actualDuration: int + currentUser: UserReference + comments: str + defects: list[str] + udfs: list[UserDefinedField] + tags: list[Tag] + references: list[str] + version: str | None = None + tester: UserReference | None = None + + +class KindOfDataType(Enum): + Regular = "Regular" + Reference = "Reference" + Global = "Global" + AcceptingGlobal = "AcceptingGlobal" + + +@dataclass +class DataTypeSummary: + key: str + kind: KindOfDataType + name: str + path: str + uniqueID: str + version: str | None = None + + +class ParameterDefinitionType(Enum): + DetailedInstance = "DetailedInstance" + InstanceTable = "InstanceTable" + AtomicInstance = "AtomicInstance" + + +@dataclass +class ParameterValue: + name: str + key: str + dtSequenceKeys: list[str] + + +@dataclass +class InstanceArrayValue: + name: str + isDefaultValue: bool + + +class ParameterEvaluationType(Enum): + CallByValue = "CallByValue" + CallByReference = "CallByReference" + CallByReferenceMandatory = "CallByReferenceMandatory" + + +class RepresentativeType(Enum): + Text = "Text" + Placeholder = "Placeholder" + Attachment = "Attachment" + Hyperlink = "Hyperlink" + Reference = "Reference" + + +class ArgumentValueType(Enum): + EquivalenceClass = "EquivalenceClass" + Representative = "Representative" + InstancesArray = "InstancesArray" + CBRRepresentative = "CBRRepresentative" + + +class TestCaseDetailsOrigin(Enum): + Fallback = "Fallback" + Generated = "Generated" + Rejected = "Rejected" + Restored = "Restored" + Upgraded = "Upgraded" + + +class ReferenceKind(Enum): + Reference = "Reference" + Link = "Link" + Hyperlink = "Hyperlink" + Attachment = "Attachment" + + +class TestFilterType(Enum): + TestTheme = "TestTheme" + TestCaseSet = "TestCaseSet" + TestCase = "TestCase" + + +@dataclass +class FilterInfo: + name: str + filterType: TestFilterType + testThemeUID: str | None = None + + +@dataclass +class TovStructureOptions(ReportExportOptions): + treeRootUID: str | None = None + suppressFilteredData: bool | None = None + suppressEmptyTestThemes: bool | None = None + filters: list[FilterInfo] | None = None + + +@dataclass +class CycleStructureOptions: + treeRootUID: str | None = None + basedOnExecution: bool | None = None + suppressFilteredData: bool | None = None + suppressNotExecutable: bool | None = None + suppressEmptyTestThemes: bool | None = None + filters: list[FilterInfo] | None = None + + +@dataclass +class CycleReportOptions(ReportExportOptions): + treeRootUID: str | None = None + executionMode: ExecutionMode | None = None + suppressFilteredData: bool | None = None + suppressNotExecutable: bool | None = None + suppressEmptyTestThemes: bool | None = None + filters: list[FilterInfo] | None = None + + +@dataclass +class DefectAttribute: + name: str + value: str + + +@dataclass +class ProjectDefectField: + values: list[str] + defaultValue: str | None = None + + +@dataclass +class DefectUDF: + name: str + udfType: UDFType + isMandatory: bool + values: list[str] | None = None + + +@dataclass +class TestStructureExecution: + status: ActivityStatus + execStatus: ExecStatus + verdict: VerdictStatus + + +@dataclass +class TestStructureItemExecution(TestStructureExecution): + key: str + locker: UserReference | None = None + + +@dataclass +class TestCaseExecution(TestStructureExecution): + key: str + + +@dataclass +class TestStructureSpecification: + pass + + +@dataclass +class TestStructureItemSpecification(TestStructureSpecification): + key: str + status: SpecStatus + locker: UserReference | None = None + + +@dataclass +class TestCaseSpecification(TestStructureSpecification): + key: str + + +@dataclass +class TestStructureItemBaseInformation: + key: str + numbering: str + path: str + parentKey: str + name: str + uniqueID: str + matchesFilter: bool + + +@dataclass +class TestCaseBaseInformation: + numbering: str + parentKey: str + name: str + uniqueID: str + matchesFilter: bool + + +@dataclass +class TestStructureAutomation: + key: str + status: AutStatus + locker: UserReference | None = None + + +class TestStructureElementType(Enum): + RootNode = "RootNode" + TestThemeNode = "TestThemeNode" + TestCaseSetNode = "TestCaseSetNode" + TestCaseNode = "TestCaseNode" + + +@dataclass +class AttachedFilter: + key: str + name: str + filterType: TestFilterType + content: str + + +@dataclass +class CreatedJob: + jobID: str + + +@dataclass +class JobProgress: + totalItemsCount: int + handledItemsCount: int + + +@dataclass +class ReportingResult: + pass + + +@dataclass +class ReportingFailure(ReportingResult): + error: ActionFailure + + +@dataclass +class ReportingSuccess(ReportingResult): + reportName: str + + +@dataclass +class ExecutionImportingResult: + pass + + +@dataclass +class ExecutionImportingFailure(ExecutionImportingResult): + error: ActionFailure + + +@dataclass +class CheckedInElement: + elementName: str + newlyCheckedIn: bool + versionName: str + + +@dataclass +class CreatedDefect: + foreignKey: str + createdKey: str + + +@dataclass +class CreatedReference: + foreignKey: str + createdKey: str + newFileName: str | None = None + + +@dataclass +class TestCaseExecutionImportResult: + key: str + executionKey: str + uid: str + importResult: ImportResult + warnings: list[ActionFailure] + error: ActionFailure | None = None + + +@dataclass +class CheckInData: + comment: str + label: str | None = None + + +@dataclass +class ExecutionImportSimulationOptions: + fileName: str + treeRootUID: str | None = None + filters: list[FilterInfo] | None = None + + +@dataclass +class UploadedFile: + fileName: str + + +@dataclass +class TestStructureElement: + key: str + uniqueID: str + name: str + executionKey: str + + +@dataclass +class TestThemeExecution: + key: str + status: ActivityStatus + execStatus: ExecStatus + verdict: VerdictStatus + udfs: list[UserDefinedField] + tags: list[Tag] + references: list[str] + comments: str | None = None + responsible: UserReference | None = None + + +@dataclass +class TestThemeDetails: + key: str + name: str + uniqueID: str + numbering: str + path: str + spec: TestThemeSpecification + exec: TestThemeExecution | None = None + + +@dataclass +class TestObjectVersionCSVReportOptions: + reportRootUID: str | None = None + fields: list[str] | None = None + characterEncoding: str | None = None + + +@dataclass +class JWTResponse: + accessToken: str + expiresAt: str + + +@dataclass +class TestCycleCSVReportOptions: + reportRootUID: str | None = None + fields: list[str] | None = None + characterEncoding: str | None = None + basedOn: str | None = None + + +class UDFLocation(Enum): + TestThemesInSpecification = "TestThemesInSpecification" + TestCaseSetsInSpecification = "TestCaseSetsInSpecification" + TestCasesInSpecification = "TestCasesInSpecification" + TestThemesInExecution = "TestThemesInExecution" + TestCaseSetsInExecution = "TestCaseSetsInExecution" + TestCasesInExecution = "TestCasesInExecution" + + +class UDFAbsolutePosition(Enum): + First = "First" + Last = "Last" + + +@dataclass +class AfterUDF: + udfKey: str + + +@dataclass +class BeforeUDF: + udfKey: str + + +@dataclass +class UDFForUpdate: + name: str | None = None + isMandatory: bool | None = None + definedFor: list[UDFLocation] | None = None + udfType: UDFType | None = None + enumerationValues: list[UDFEnumerationValue] | None = None + + +@dataclass +class UDFsMoveResponse: + targetKeys: list[str] + + +@dataclass +class TestFilter: + key: str + name: str + owner: str + pluginName: str | None = None + + +@dataclass +class UDFUpdatedResponse: + pass + + +@dataclass +class UDFForRestriction: + onlyAdminsMayManageUDFs: bool + + +@dataclass +class UDFRestrictionResponse: + projectKey: str + onlyAdminsMayManageUDFs: bool + + +@dataclass +class CycleCreation: + pass + + +@dataclass +class CycleCloned(CycleCreation): + cycle: str + + +@dataclass +class CycleImported(CycleCreation): + project: str + tov: str + cycle: str + + +@dataclass +class CycleImportedAsNew(CycleCreation): + tov: str + cycle: str + + +@dataclass +class CycleImportedAsClone(CycleCreation): + tov: str + cycle: str + clonedCycle: str + + +@dataclass +class CycleImportedAsNewFromPlugin(CycleCreation): + plugin: str + tov: str + cycle: str + + +@dataclass +class CycleClonedFromPlugin(CycleCreation): + plugin: str + cycle: str + + +@dataclass +class CycleImportedAsCloneFromPlugin(CycleCreation): + plugin: str + tov: str + cycle: str + clonedCycle: str + + +@dataclass +class UnknownCycle(CycleCreation): + pass + + +@dataclass +class NewCycle(CycleCreation): + pass + + +@dataclass +class NewPassword: + newPassword: str + + +@dataclass +class ImageDetails: + key: str + suffix: str + imageData: str + + +@dataclass +class UdfValueForImport: + udfKey: str + value: str + + +@dataclass +class RichTextForImport: + html: str | None = None + plain: str | None = None + + +@dataclass +class ExecutionResultForImport: + status: ActivityStatus + execStatus: ExecStatus + verdict: VerdictStatus + timestamp: str | None = None + + +@dataclass +class TestCaseExecutionForImport: + uniqueID: str + testCaseExecutionKey: str + result: ExecutionResultForImport + durationMillis: int + testerKey: str | None = None + comments: RichTextForImport | None = None + defects: list[str] | None = None + udfs: list[UdfValueForImport] | None = None + references: list[str] | None = None + + +@dataclass +class TestCaseSetExecutionForImport: + testCaseSetKey: str + executionKey: str + durationMillis: int + testCases: list[TestCaseExecutionForImport] + testerKey: str | None = None + comments: RichTextForImport | None = None + udfs: list[UdfValueForImport] | None = None + + +@dataclass +class KeywordCallExecution: + verdict: KeywordVerdict + duration: int + currentUser: UserReference + comments: str + references: list[str] + defects: list[str] + time: str | None = None + tester: UserReference | None = None + + +class Permission(Enum): + AccessSecuredData = "AccessSecuredData" + AdministerServer = "AdministerServer" + DeleteUserAccount = "DeleteUserAccount" + DeleteUserSession = "DeleteUserSession" + DownloadReportFile = "DownloadReportFile" + ExecuteOthersTestCaseSets = "ExecuteOthersTestCaseSets" + ImportExecutionResults = "ImportExecutionResults" + ModifyGlobalTestLabels = "ModifyGlobalTestLabels" + ModifyProjectDetails = "ModifyProjectDetails" + ModifyProjectUDFs = "ModifyProjectUDFs" + ModifySpecifications = "ModifySpecifications" + ModifySpecManagementInfo = "ModifySpecManagementInfo" + ModifySpecPriorityAndDueDate = "ModifySpecPriorityAndDueDate" + ModifyTestElements = "ModifyTestElements" + ModifyTestLabels = "ModifyTestLabels" + ModifyUserData = "ModifyUserData" + ModifyUserRolesInProject = "ModifyUserRolesInProject" + PrivatizeGlobalTestLabels = "PrivatizeGlobalTestLabels" + ReadActiveUsersList = "ReadActiveUsersList" + ReadCompleteProjectsList = "ReadCompleteProjectsList" + ReadCompleteUsersList = "ReadCompleteUsersList" + ReadCycleReport = "ReadCycleReport" + ReadCycleReportOverRMI = "ReadCycleReportOverRMI" + ReadCycleRequirements = "ReadCycleRequirements" + ReadDefectsMetricDistribution = "ReadDefectsMetricDistribution" + ReadExecutionImportingJobDetails = "ReadExecutionImportingJobDetails" + ReadInvisibleProjectContent = "ReadInvisibleProjectContent" + ReadOwnProjectsList = "ReadOwnProjectsList" + ReadOwnUserDetails = "ReadOwnUserDetails" + ReadProjectDefectsAndTheirAssignments = "ReadProjectDefectsAndTheirAssignments" + ReadProjectDetails = "ReadProjectDetails" + ReadProjectExportOverRMI = "ReadProjectExportOverRMI" + ReadProjectHierarchy = "ReadProjectHierarchy" + ReadProjectMembers = "ReadProjectMembers" + ReadProjectUDFs = "ReadProjectUDFs" + ReadReportingJobDetails = "ReadReportingJobDetails" + ReadTestCaseDetails = "ReadTestCaseDetails" + ReadTestCaseSetDetails = "ReadTestCaseSetDetails" + ReadTestElements = "ReadTestElements" + ReadTestLabels = "ReadTestLabels" + ReadTestThemeDetails = "ReadTestThemeDetails" + ReadTestThemeStatusDistribution = "ReadTestThemeStatusDistribution" + ReadTestThemeTree = "ReadTestThemeTree" + ReadTovReport = "ReadTovReport" + ReadTovReportOverRMI = "ReadTovReportOverRMI" + ReadTovRequirements = "ReadTovRequirements" + ReadUserDetails = "ReadUserDetails" + ReadUserMemberships = "ReadUserMemberships" + ReadUserSessions = "ReadUserSessions" + RestrictProjectUDFs = "RestrictProjectUDFs" + SynchronizeUsers = "SynchronizeUsers" + UnlockForeignSpecs = "UnlockForeignSpecs" + UnlockForeignTestElements = "UnlockForeignTestElements" + + +@dataclass +class DefaultValue: + name: str + valueType: ArgumentValueType + + +@dataclass +class SubdivisionDetails: + key: str + name: str + uniqueID: str + description: str + path: str + references: list[str] + locker: UserReference | None = None + parentUniqueID: str | None = None + libraryKey: str | None = None + + +@dataclass +class KeywordParameterForInsert: + name: str + dataTypeKey: str | None = None + evaluationType: ParameterEvaluationType | None = None + + +@dataclass +class ParameterDetails: + key: str + name: str + definitionType: ParameterDefinitionType + evaluationType: ParameterEvaluationType + dataTypeKey: str | None = None + defaultValue: DefaultValue | None = None + signatureID: str | None = None + + +@dataclass +class TOVNode: + nodeType: ProjectTreeNodeType + key: str + name: str + creationTime: str + status: ProjectStatus + visibility: bool + exchangeFormat: TOVExchangeFormat + children: list[CycleNode] + + +@dataclass +class UDF: + udfKey: str + name: str + projectKey: str + isMandatory: bool + definedFor: list[UDFLocation] + + +@dataclass +class BooleanUDF(UDF): + pass + + +@dataclass +class EnumerationUDF(UDF): + enumerationValues: list[UDFEnumerationValue] + + +@dataclass +class StringUDF(UDF): + pass + + +@dataclass +class ReferenceAssignment: + key: str + value: str + referenceType: ReferenceKind + versionName: str | None = None + + +@dataclass +class RepresentativeValue: + name: str + valueType: RepresentativeType + isDefaultValue: bool + + +@dataclass +class AssignedDefect: + key: str + title: str + id: str + description: str + identicalVersionKey: str + status: str + priority: str + classification: str + creationTime: str + references: list[str] + udfs: list[DefectAttribute] + version: str | None = None + tester: str | None = None + lastEditTime: str | None = None + lastEditorKey: str | None = None + defectManagementSystem: str | None = None + defectManagementProject: str | None = None + + +@dataclass +class ExternalDefectManagement: + system: str + project: str + udfs: list[DefectUDF] + + +@dataclass +class DefectConfig: + status: ProjectDefectField + classification: ProjectDefectField + priority: ProjectDefectField + external: ExternalDefectManagement | None = None + + +@dataclass +class TestStructureTreeNode: + elementType: TestStructureElementType + + +@dataclass +class RootNode(TestStructureTreeNode): + base: TestStructureItemBaseInformation + filters: list[AttachedFilter] + + +@dataclass +class TestThemeNode(TestStructureTreeNode): + base: TestStructureItemBaseInformation + filters: list[AttachedFilter] + spec: TestStructureItemSpecification | None = None + aut: TestStructureAutomation | None = None + exec: TestStructureItemExecution | None = None + + +@dataclass +class TestCaseSetNode(TestStructureTreeNode): + base: TestStructureItemBaseInformation + spec: TestStructureItemSpecification | None = None + aut: TestStructureAutomation | None = None + exec: TestStructureItemExecution | None = None + + +@dataclass +class TestCaseNode(TestStructureTreeNode): + base: TestCaseBaseInformation + spec: TestCaseSpecification | None = None + exec: TestCaseExecution | None = None + + +@dataclass +class TestStructureTree: + nodes: list[TestThemeNode | TestCaseSetNode | TestCaseNode] + root: RootNode | TestThemeNode | TestCaseSetNode | TestCaseNode | None = None + + +@dataclass +class ReportingCompletion: + time: str + result: ReportingResult + + +@dataclass +class ExecutionImportingCompletion: + time: str + result: ExecutionImportingResult + + +@dataclass +class TestCaseSetExecutionImportResult: + key: str + executionKey: str + name: str + uid: str + finished: bool + testCases: list[TestCaseExecutionImportResult] + error: ActionFailure | None = None + + +@dataclass +class ExecutionImportOptions: + fileName: str + treeRootUID: str | None = None + useExistingDefect: bool | None = None + discardTesterInformation: bool | None = None + defaultTester: str | None = None + updateReferencesList: ReferenceUpdateMode | None = None + filters: list[FilterInfo] | None = None + checkInData: CheckInData | None = None + + +@dataclass +class JWTDataOptions: + permissions: list[Permission] + projectKey: str | None = None + tovKey: str | None = None + cycleKey: str | None = None + subject: str | None = None + expiresAfterSeconds: int | None = None + + +@dataclass +class UDFPosition: + after: AfterUDF | None = None + before: BeforeUDF | None = None + absolute: UDFAbsolutePosition | None = None + + +@dataclass +class UDFForMove: + udfKeys: list[str] + newPositions: UDFPosition + + +@dataclass +class StringUDFUpdatedResponse(UDFUpdatedResponse): + stringUDF: StringUDF + affectedFilters: list[TestFilter] + + +@dataclass +class BooleanUDFUpdatedResponse(UDFUpdatedResponse): + booleanUdf: BooleanUDF + affectedFilters: list[TestFilter] + + +@dataclass +class EnumerationUDFUpdatedResponse(UDFUpdatedResponse): + enumerationUDF: EnumerationUDF + affectedFilters: list[TestFilter] + + +@dataclass +class CycleResponse: + name: str + visible: bool + projectKey: str + description: str + creation: CycleCreation + key: str + tovKey: str + status: ProjectStatus + testingIntelligence: bool + instantOfCreation: str + endDate: str | None = None + startDate: str | None = None + + +@dataclass +class ImageInfo: + key: str + value: ImageDetails + + +@dataclass +class KeywordDetails: + key: str + name: str + uniqueID: str + status: TestElementStatus + defaultCallType: KeywordCallType + description: str + path: str + parameters: list[ParameterDetails] + preConditions: list[ConditionSummary] + postConditions: list[ConditionSummary] + references: list[str] + locker: UserReference | None = None + version: str | None = None + parentUniqueID: str | None = None + libraryKey: str | None = None + advancedContent: AdvancedContent | None = None + + +@dataclass +class ProjectNode: + nodeType: ProjectTreeNodeType + key: str + name: str + creationTime: str + status: ProjectStatus + visibility: bool + exchangeFormat: ProjectExchangeFormat + children: list[TOVNode] + + +@dataclass +class ParameterSummary: + definitionType: ParameterDefinitionType + key: str + name: str + evaluationType: ParameterEvaluationType + dataType: DataTypeSummary | None = None + value: str | None = None + valueType: RepresentativeType | None = None + representativeValue: RepresentativeValue | None = None + parameterValue: ParameterValue | None = None + instanceArrayValue: InstanceArrayValue | None = None + parentAlias: str | None = None + rootAlias: str | None = None + usesCount: int | None = None + + +@dataclass +class ReportingJob: + id: str + projectKey: str + owner: str + start: str + progress: JobProgress | None = None + completion: ReportingCompletion | None = None + + +@dataclass +class ExecutionImportingJob: + id: str + projectKey: str + owner: str + start: str + progress: JobProgress | None = None + completion: ExecutionImportingCompletion | None = None + + +@dataclass +class ExecutionImportingSuccess(ExecutionImportingResult): + testCaseSets: list[TestCaseSetExecutionImportResult] + checkedInTestStructureElements: list[TestStructureElement] + checkedInTestElements: list[CheckedInElement] + createdDefects: list[CreatedDefect] + createdReferences: list[CreatedReference] + + +@dataclass +class UDFForInsert: + name: str + isMandatory: bool + definedFor: list[UDFLocation] + udfType: UDFType + enumerationName: list[str] | None = None + position: UDFPosition | None = None + + +@dataclass +class RichTextInfo: + html: str + images: list[ImageInfo] + + +@dataclass +class KeywordCallSpecification: + key: str + name: str + sequencePhase: SequencePhase + callType: KeywordCallType + comments: str + callParameters: list[ParameterSummary] + keywordType: KeywordType | None = None + description: str | None = None + keywordKey: str | None = None + callingKeywordKey: str | None = None + + +@dataclass +class KeywordCall: + sequenceID: str + numbering: str + spec: KeywordCallSpecification + parentID: str | None = None + exec: KeywordCallExecution | None = None + + +@dataclass +class KeywordForInsert: + parentKey: str + name: str + parameters: list[KeywordParameterForInsert] + uid: str | None = None + description: RichTextInfo | None = None + advancedContent: AdvancedContent | None = None + callType: KeywordCallType | None = None + + +@dataclass +class SubdivisionForInsert: + name: str + parentKey: str | None = None + uid: str | None = None + description: RichTextInfo | None = None + + +@dataclass +class SubdivisionForUpdate: + name: str | None = None + description: RichTextInfo | None = None + + +@dataclass +class KeywordDetailsForUpdate: + name: str | None = None + description: RichTextInfo | None = None + callType: KeywordCallType | None = None + locker: OptionalUser | None = None + advancedContent: OptionalAdvancedContent | None = None + + +@dataclass +class SpecificationDetailsForUpdate: + responsible: OptionalUser | None = None + reviewer: OptionalUser | None = None + locker: OptionalUser | None = None + priority: Priority | None = None + dueDate: OptionalLocalDate | None = None + description: RichTextInfo | None = None + reviewComment: RichTextInfo | None = None + + +@dataclass +class ProjectDetailsForUpdate: + name: str | None = None + testObjectName: str | None = None + id: str | None = None + customerName: str | None = None + customerAddress: str | None = None + contactPerson: str | None = None + testLab: str | None = None + placeOfInspection: str | None = None + status: ProjectStatus | None = None + isVisibleToTester: bool | None = None + isTestingIntelligenceActive: bool | None = None + description: RichTextInfo | None = None + startDate: OptionalLocalDate | None = None + endDate: OptionalLocalDate | None = None + inspections: list[str] | None = None + variantsManagementEnabled: OperationalState | None = None + + +@dataclass +class TOVForUpdate: + name: str | None = None + endDate: OptionalLocalDate | None = None + visible: bool | None = None + description: RichTextInfo | None = None + isBaseTov: bool | None = None + status: ProjectStatus | None = None + testingIntelligence: bool | None = None + startDate: OptionalLocalDate | None = None + cloningVisibility: bool | None = None + + +@dataclass +class TestCaseSetDetails: + key: str + numbering: str + path: str + uniqueID: str + name: str + spec: TestCaseSetSpecificationSummary + testCases: list[TestCaseSummary] + testSequence: list[KeywordCall] + parameters: list[ParameterDetails] + keywords: list[KeywordDetails] + exec: TestCaseSetExecutionSummary | None = None + + +@dataclass +class TestCaseDetails: + uniqueID: str + spec: TestCaseSpecificationDetails + testSequence: list[KeywordCall] + parameters: list[ParameterSummary] + keywords: list[KeywordDetails] + exec: TestCaseExecutionDetails | None = None + origin: TestCaseDetailsOrigin | None = None + + +@dataclass +class CycleForUpdate: + name: str | None = None + endDate: OptionalLocalDate | None = None + visible: bool | None = None + description: RichTextInfo | None = None + status: ProjectStatus | None = None + testingIntelligence: bool | None = None + startDate: OptionalLocalDate | None = None diff --git a/testbench2robotframework/result_writer.py b/testbench2robotframework/result_writer.py index b9dda75..b8d70ac 100644 --- a/testbench2robotframework/result_writer.py +++ b/testbench2robotframework/result_writer.py @@ -7,7 +7,6 @@ from datetime import datetime, timedelta, timezone from pathlib import Path from shutil import copytree -from typing import Optional from robot.result import Keyword, ResultVisitor, TestCase, TestSuite @@ -30,9 +29,9 @@ RichTextForImport, SequencePhase, TestCaseDetails, - TestCaseExecutionDetails, TestCaseExecutionForImport, TestCaseSetExecutionForImport, + UserReference, VerdictStatus, ) from .utils import directory_to_zip, get_directory @@ -42,6 +41,18 @@ except ImportError: Group = None + +def _empty_keyword_call_execution() -> KeywordCallExecution: + return KeywordCallExecution( + verdict=KeywordVerdict.Undefined, + duration=0, + currentUser=UserReference(key="", name=""), + comments="", + references=[], + defects=[], + ) + + BACKGROUND_COLOR = { "PASS": "#04AF91", "FAIL": "#ce3e01", @@ -67,7 +78,7 @@ class ResultWriter(ResultVisitor): def __init__( self, json_report: str, - json_result: Optional[str], + json_result: str | None, config: Configuration, output_xml, listener_uid=None, @@ -78,7 +89,7 @@ def __init__( self.reference_behaviour = config.referenceBehaviour self.attachment_conflict_behaviour = config.attachmentConflictBehaviour self.tempdir = tempfile.TemporaryDirectory(dir=os.curdir) - self._test_setup_passed: Optional[bool] = None + self._test_setup_passed: bool | None = None if json_result is None: self.json_result = self.json_dir self.json_result_path = self.json_dir @@ -95,9 +106,18 @@ def __init__( self.test_suites: dict[str, TestSuite] = {} self.keywords: list[Keyword] = [] self.itb_test_case_catalog: dict[str, TestCaseDetails] = {} - self.phase_pattern = config.phasePattern + self.phase_pattern = config.phase_pattern self.test_chain: list[TestCase] = [] - self.main_protocol = from_dict(ExecutionImportingSuccess, {"testCaseSets": [], "checkedInTestStructureElements":[], "checkedInTestElements": [], "createdDefects":[], "createdReferences":[]}) + self.main_protocol = from_dict( + ExecutionImportingSuccess, + { + "testCaseSets": [], + "checkedInTestStructureElements": [], + "checkedInTestElements": [], + "createdDefects": [], + "createdReferences": [], + }, + ) def _create_artifact_storage(self): return ExecutionArtifactStorage( @@ -113,9 +133,7 @@ def start_suite(self, suite: TestSuite): self.test_suites[suite.metadata["uniqueID"]] = suite self.protocol_test_cases: list[TestCaseExecutionForImport] = [] - def _get_keywords_by_type( - self, keywords: list[KeywordCall], keyword_type: KeywordType - ): + def _get_keywords_by_type(self, keywords: list[KeywordCall], keyword_type: KeywordType): for keyword in keywords: if not keyword.spec: continue @@ -143,8 +161,6 @@ def end_test(self, test: TestCase): self.protocol_test_case: TestCaseExecutionForImport = TestCaseExecutionForImport( test_uid, itb_test_case.exec.key, None, None, None ) - if itb_test_case.exec is None: - itb_test_case.exec = from_dict(TestCaseExecutionDetails, {}) if itb_test_case.exec.key in ["", "-1"]: logger.warning( f"Test case {itb_test_case.uniqueID} was not exported based on " @@ -159,15 +175,13 @@ def end_test(self, test: TestCase): ) self._set_atomic_keywords_execution_result(atomic_keywords, self.test_chain) for keyword in compound_keywords: - self._set_compound_keyword_execution_verdict( - keyword, itb_test_case.testSequence - ) + self._set_compound_keyword_execution_verdict(keyword, itb_test_case.testSequence) textual_steps = list( self._get_keywords_by_type(itb_test_case.testSequence, KeywordType.Textual) ) for step in textual_steps: if step.exec is None: - step.exec = from_dict(KeywordCallExecution, {}) + step.exec = _empty_keyword_call_execution() step.exec.verdict = KeywordVerdict.Skipped self._set_itb_testcase_execution_result(itb_test_case, self.test_chain) self._set_itb_testcase_execution_comment(itb_test_case, self.test_chain) @@ -320,12 +334,8 @@ def _set_atomic_keywords_execution_result( atomic_keywords, SequencePhase.Teardown ) self._set_keyword_verdicts(setup_keywords, test_chain_setup, SequencePhase.Setup) - self._set_keyword_verdicts( - test_step_keywords, test_chain_body, SequencePhase.TestStep - ) - self._set_keyword_verdicts( - teardown_keywords, test_chain_teardown, SequencePhase.Teardown - ) + self._set_keyword_verdicts(test_step_keywords, test_chain_body, SequencePhase.TestStep) + self._set_keyword_verdicts(teardown_keywords, test_chain_teardown, SequencePhase.Teardown) def _set_keyword_verdicts( self, @@ -335,7 +345,7 @@ def _set_keyword_verdicts( ): for index, tb_keyword in enumerate(keyword_list): if tb_keyword.exec is None: - tb_keyword.exec = from_dict(KeywordCallExecution, {}) + tb_keyword.exec = _empty_keyword_call_execution() if sequence_phase == SequencePhase.TestStep and not self._test_setup_passed: tb_keyword.exec.verdict = KeywordVerdict.Skipped continue @@ -385,9 +395,7 @@ def _get_keyword_exec_from_keyword(self, keyword: Keyword) -> KeywordCallExecuti }, ) - def _check_matching_keyword_name( - self, rf_keyword: Keyword, tb_keyword: KeywordCall - ) -> None: + def _check_matching_keyword_name(self, rf_keyword: Keyword, tb_keyword: KeywordCall) -> None: if not is_normalized_equal( rf_keyword.kwname, tb_keyword.spec.name ) and not is_normalized_equal(rf_keyword.kwname.split(".")[-1], tb_keyword.spec.name): @@ -456,17 +464,15 @@ def _set_compound_keyword_execution_verdict( self, compound_keyword: KeywordCall, test_steps: list[KeywordCall] ): if compound_keyword.exec is None: - compound_keyword.exec = from_dict(KeywordCallExecution, {}) + compound_keyword.exec = _empty_keyword_call_execution() compound_keyword.exec.verdict = KeywordVerdict.Skipped - children = list( - filter(lambda ts: ts.parentID == compound_keyword.sequenceID, test_steps) - ) + children = list(filter(lambda ts: ts.parentID == compound_keyword.sequenceID, test_steps)) for child in children: if child.exec is None: logger.debug( f"Child keyword {child.uniqueID} had no exec details and therefore ignored." ) - child.exec = from_dict(KeywordCallExecution, {}) + child.exec = _empty_keyword_call_execution() if child.spec.keywordType == KeywordType.Compound: self._set_compound_keyword_execution_verdict(child, test_steps) if child.spec.keywordType == KeywordType.Textual: @@ -477,9 +483,7 @@ def _set_compound_keyword_execution_verdict( if child.exec.verdict is KeywordVerdict.Pass: compound_keyword.exec.verdict = KeywordVerdict.Pass - compound_keyword.exec.duration = sum( - [keyword.exec.duration for keyword in children] - ) + compound_keyword.exec.duration = sum([keyword.exec.duration for keyword in children]) compound_keyword.exec.time = children[-1].exec.time @staticmethod @@ -684,7 +688,7 @@ def __init__(self, name, index, length): self.length = int(length) -def get_test_chain(test_name: str, phase_pattern: str) -> Optional[TestChain]: +def get_test_chain(test_name: str, phase_pattern: str) -> TestChain | None: matcher = re.match(get_test_chain_pattern(phase_pattern), test_name) if matcher: return TestChain(*matcher.groups()) diff --git a/testbench2robotframework/robotframework2testbench.py b/testbench2robotframework/robotframework2testbench.py index 81326a3..4fc2909 100644 --- a/testbench2robotframework/robotframework2testbench.py +++ b/testbench2robotframework/robotframework2testbench.py @@ -1,9 +1,10 @@ import sys from pathlib import Path -from typing import Optional from robot.api import ExecutionResult +from testbench2robotframework.utils import perform_version_check + from .config import Configuration from .log import logger, setup_logger from .result_writer import ResultWriter @@ -12,13 +13,15 @@ def robot2testbench( json_input_report: str, robot_result_xml: str, - json_output_result: Optional[str] = None, - config: Optional[dict] = None, + json_output_result: str | None = None, + config: dict | None = None, ): if not Path(json_input_report).exists(): sys.exit("Could not find json directory or zip file at the given path.") if not Path(robot_result_xml).exists(): sys.exit("Robot result xml does not exist at the given path.") + perform_version_check(Path(json_input_report)) + configuration = Configuration.from_dict(config) setup_logger(configuration) logger.debug("Configuration loaded.") diff --git a/testbench2robotframework/testbench2rf.py b/testbench2robotframework/testbench2rf.py index aa5343f..eff37a5 100644 --- a/testbench2robotframework/testbench2rf.py +++ b/testbench2robotframework/testbench2rf.py @@ -97,8 +97,20 @@ def __init__(self, test_case_details: TestCaseDetails, config: Configuration) -> self.rf_keyword_call_information: list[RFKeywordCallInformation] = [] self.used_imports: dict[str, set[str]] = {} self.config = config - self.lib_pattern_list = [re.compile(pattern) for pattern in config.library_regex] - self.res_pattern_list = [re.compile(pattern) for pattern in config.resource_regex] + + # Validate and compile regex patterns + for pattern in config.library_regex: + self._validate_regex_pattern(pattern, "library") + for pattern in config.resource_regex: + self._validate_regex_pattern(pattern, "resource") + + self.lib_pattern_list = [ + re.compile(pattern, re.IGNORECASE) for pattern in config.library_regex + ] + self.res_pattern_list = [ + re.compile(pattern, re.IGNORECASE) for pattern in config.resource_regex + ] + for keyword in test_case_details.testSequence: self._get_keyword_call(keyword) self.rf_tags = self._get_tags(test_case_details) @@ -106,6 +118,36 @@ def __init__(self, test_case_details: TestCaseDetails, config: Configuration) -> self.teardown_keyword: Keyword | None = None # TODO description + @staticmethod + def _validate_regex_pattern(pattern: str, pattern_type: str) -> None: + """Validate that regex pattern has correct capture groups. + + Args: + pattern: The regex pattern to validate + pattern_type: Type description for error messages (e.g., "library" or "resource") + + Raises: + ValueError: If pattern doesn't meet requirements + """ + try: + compiled = re.compile(pattern, re.IGNORECASE) + except re.error as e: + raise ValueError(f"Invalid {pattern_type} regex pattern '{pattern}': {e}") + + num_groups = compiled.groups + + if num_groups == 0: + raise ValueError( + f"{pattern_type.capitalize()} regex pattern must contain at least one capture group: '{pattern}'" + ) + + if num_groups > 1: + if "resourceName" not in compiled.groupindex: + raise ValueError( + f"{pattern_type.capitalize()} regex pattern with multiple capture groups must have " + f"one named 'resourceName': '{pattern}'" + ) + @staticmethod def _get_tags(test_case_details: TestCaseDetails) -> list[str]: tags = [tag.name for tag in test_case_details.spec.tags] @@ -191,13 +233,11 @@ def _append_atomic_ia( ) ) - def _get_keyword_import( - self, test_step: TBKeywordCall, keyword_path: str - ) -> tuple[str, str]: + def _get_keyword_import(self, test_step: TBKeywordCall, keyword_path: str) -> tuple[str, str]: for pattern in self.lib_pattern_list: match = pattern.search(keyword_path) if match: - return LIBRARY_IMPORT_TYPE, match.group("resourceName").strip() + return LIBRARY_IMPORT_TYPE, match.group(1).strip() for pattern in self.res_pattern_list: match = pattern.search(keyword_path) if match: @@ -205,10 +245,15 @@ def _get_keyword_import( splitted_keyword_path = keyword_path.split(".") minimum_length_subdivision_path_length = 2 if ( - len(splitted_keyword_path) == minimum_length_subdivision_path_length + len(splitted_keyword_path) >= minimum_length_subdivision_path_length and splitted_keyword_path[0] in self.config.library_root ): return LIBRARY_IMPORT_TYPE, splitted_keyword_path[1] + if ( + len(splitted_keyword_path) >= minimum_length_subdivision_path_length + and splitted_keyword_path[0] in self.config.resource_root + ): + return RESOURCE_IMPORT_TYPE, splitted_keyword_path[1] return UNKNOWN_IMPORT_TYPE, keyword_path def _append_compound_ia( @@ -265,10 +310,7 @@ def _create_rf_keyword_calls( group_stack[-1][0].body.append(compound_keyword_call) else: keyword_lists[tc_index].append(compound_keyword_call) - if ( - Group - and self.config.compound_keyword_logging == CompoundKeywordLogging.GROUP - ): + if Group and self.config.compound_keyword_logging == CompoundKeywordLogging.GROUP: group_stack.append((compound_keyword_call, keyword_call.indent)) return keyword_lists @@ -381,7 +423,7 @@ def to_robot_ast_test_cases( rf_test_cases: list[TestCase] = [] multiple_tests = len(rf_keyword_call_lists) > 1 for index, rf_keywords in enumerate(rf_keyword_call_lists): - phase_pattern = self.config.phasePattern + phase_pattern = self.config.phase_pattern tc_name = ( phase_pattern.format( testcase=self.uid, @@ -460,17 +502,15 @@ def _create_cbr_parameters( return cbr_parameters def _get_keyword_import_prefix(self, keyword: RFKeywordCallInformation) -> str: - for resource_regex in self.config.resource_regex: + for resource_pattern in self.res_pattern_list: if not keyword.import_prefix: continue - resource_name_match = re.search( - resource_regex, keyword.import_prefix, flags=re.IGNORECASE - ) + resource_name_match = resource_pattern.search(keyword.import_prefix) if resource_name_match: return ( self.config.fully_qualified or False - ) * f"{resource_name_match.group('resourceName').strip()}." - return "" + ) * f"{resource_name_match.group(1).strip()}." + return (self.config.fully_qualified or False) * f"{keyword.import_prefix}." def _get_keyword_indent(self, keyword: RFKeywordCallInformation) -> str: return ( @@ -704,7 +744,9 @@ def _get_resource_name(self, resource: str) -> str | None: for resource_regex in self.config.resource_regex: resource_name_match = re.search(resource_regex, resource_path_part, flags=re.IGNORECASE) if resource_name_match: - return resource_name_match.group("resourceName").strip() + return resource_name_match.group(1).strip() + if resource_path_part: + return resource_path_part.strip() return None def _get_resource_directory_path_index(self, resource: str) -> int | None: diff --git a/testbench2robotframework/testbench2robotframework.py b/testbench2robotframework/testbench2robotframework.py index 09bde21..e10cc94 100644 --- a/testbench2robotframework/testbench2robotframework.py +++ b/testbench2robotframework/testbench2robotframework.py @@ -7,11 +7,17 @@ from .log import logger, setup_logger from .testbench2rf import create_test_suites from .testsuite_write import write_test_suites -from .utils import PathResolver, extract_to_working_directory, is_zip_file +from .utils import ( + PathResolver, + extract_to_working_directory, + is_zip_file, + perform_version_check, +) -def testbench2robotframework(testbench_report: str, config: dict): - configuration = Configuration.from_dict(config) +def testbench2robotframework(testbench_report: str, config: dict | Configuration): + perform_version_check(Path(testbench_report)) + configuration = Configuration.from_dict(config) if isinstance(config, dict) else config setup_logger(configuration) logger.debug("Configuration loaded.") testbench_report = Path(testbench_report) diff --git a/testbench2robotframework/testsuite_write.py b/testbench2robotframework/testsuite_write.py index 5193340..bcd5bb3 100644 --- a/testbench2robotframework/testsuite_write.py +++ b/testbench2robotframework/testsuite_write.py @@ -17,6 +17,8 @@ def write_test_suites(test_suites: dict[str, File], config: Configuration) -> No clear_generation_directory(generation_directory) if generation_directory.suffix.lower() != ".zip": write_test_suite_files(test_suites, generation_directory) + if config.create_output_zip: + directory_to_zip(generation_directory) else: with tempfile.TemporaryDirectory(dir=Path.cwd()) as temp_dir: write_test_suite_files(test_suites, Path(temp_dir)) diff --git a/testbench2robotframework/utils.py b/testbench2robotframework/utils.py index 16bce86..13396f4 100644 --- a/testbench2robotframework/utils.py +++ b/testbench2robotframework/utils.py @@ -1,8 +1,8 @@ +import json import re import shutil import sys from pathlib import Path, PurePath -from typing import Optional from zipfile import ZipFile from testbench2robotframework.model import ( @@ -18,8 +18,56 @@ from .log import logger +ALLOWED_SERVER_VERSIONS = ["4.0"] +ERROR_COULD_NOT_READ_VERSION = ( + "Could not read TestBench report version. The report must be generated with one of the supported versions: " + + ", ".join(ALLOWED_SERVER_VERSIONS) +) +ERROR_INCOMPATIBLE_VERSION = ( + "The version of testbench2robotframework is not compatible with the TestBench report version '{server_version}'. " + f"Supported versions are: {', '.join(ALLOWED_SERVER_VERSIONS)}. " +) + + +def perform_version_check(testbench_report: Path): + try: + manifest = read_manifest_json_from_testbench_report(testbench_report) + except Exception as e: + sys.exit(ERROR_COULD_NOT_READ_VERSION) + server_version = manifest.get("serverVersions", {}).get("version", None) + if not server_version: + sys.exit(ERROR_COULD_NOT_READ_VERSION) + + if server_version not in ALLOWED_SERVER_VERSIONS: + sys.exit(ERROR_INCOMPATIBLE_VERSION.format(server_version=server_version)) + + +def read_manifest_json_from_testbench_report(testbench_report: Path) -> dict: + if testbench_report.is_dir(): + manifest = testbench_report / "manifest.json" + if not manifest.exists(): + raise FileNotFoundError("manifest.json not found") + with open(manifest, "r", encoding="utf-8") as f: + return json.load(f) + + # ZIP archive + elif testbench_report.suffix == ".zip": + with ZipFile(testbench_report) as zf: + try: + with zf.open("manifest.json") as f: + return json.load(f) + except KeyError: + raise FileNotFoundError("manifest.json not found in zip") + + # Direct file + elif testbench_report.is_file() and testbench_report.name == "manifest.json": + with open(testbench_report, "r", encoding="utf-8") as f: + return json.load(f) + else: + raise FileNotFoundError(testbench_report) + -def robot_tag_from_udf(udf: UserDefinedField) -> Optional[str]: +def robot_tag_from_udf(udf: UserDefinedField) -> str | None: if (udf.udfType == UDFType.Enumeration and udf.value) or ( udf.udfType == UDFType.String and udf.value ): @@ -115,7 +163,7 @@ def _get_padded_index(self, tse) -> str: # return resolve(tree.body) -def get_directory(json_report_path: Optional[str]) -> str: +def get_directory(json_report_path: str | None) -> str: if json_report_path is None: return "" if not Path(json_report_path).exists(): @@ -156,14 +204,14 @@ def get_tse_index(tse: TestStructureTreeNode) -> str: return tse.base.numbering.rsplit(".", 1)[-1] -def directory_to_zip(directory: Path, new_path: Optional[str] = None): +def directory_to_zip(directory: Path, new_path: str | None = None): if new_path: shutil.make_archive(str(new_path), "zip", str(directory)) else: shutil.make_archive(str(directory), "zip", str(directory)) -def get_list_item(lst, index, default: Optional[str]): +def get_list_item(lst, index, default: str | None): try: return lst[index] except IndexError: diff --git a/tests/test_data/configurations/invalid_config.json b/tests/test_data/configurations/invalid_config.json deleted file mode 100644 index ab6343d..0000000 --- a/tests/test_data/configurations/invalid_config.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "rfLibraryRoots": [ - "Interactions", - "RF-Library" - ], - "rfResourceRoots": [ - "RF-Resource" - ], - "testbalala": true, - "fullyQualified": true, - "generationDirectory": "{root}/Generated", - "createOutputZip": true, - "resourceDirectory": "{root}/Resources", - "clearGenerationDirectory": true, - "logSuiteNumbering": true, - "logCompoundInteractions": true, - "subdivisionsMapping": { - "libraries": { - "SeleniumLibrary": "SeleniumLibrary timeout=10 implicit_wait=1 run_on_failure=Capture Page Screenshot", - "SuperRemoteLibrary": "Remote http://127.0.0.1:8270 WITH NAME SuperRemoteLibrary" - }, - "resources": { - "MyKeywords": "{root}/../MyKeywords.resource", - "MyOtherKeywords": "{resourceDirectory}/subdir/MyOtherKeywords.resource" - } - }, - "forcedImport": { - "libraries": [ - "SuperRemoteLibrary", - "SeleniumLibrary" - ], - "resources": - [ - "technical_keywords.resource" - ], - "variables": [ - "myVars.py" - ] - }, - "testCaseSplitPathRegEx": "^splitting\\..*", - "loggingConfiguration": { - "console": { - "logLevel": "info" - } - } -} diff --git a/tests/test_data/configurations/valid_config.json b/tests/test_data/configurations/valid_config.json deleted file mode 100644 index 589e5c2..0000000 --- a/tests/test_data/configurations/valid_config.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "rfLibraryRoots": [ - "Interactions", - "RF-Library" - ], - "rfResourceRoots": [ - "RF-Resource" - ], - "fullyQualified": true, - "generationDirectory": "{root}/Generated", - "createOutputZip": true, - "resourceDirectory": "{root}/Resources", - "clearGenerationDirectory": true, - "logSuiteNumbering": true, - "logCompoundInteractions": true, - "subdivisionsMapping": { - "libraries": { - "SeleniumLibrary": "SeleniumLibrary timeout=10 implicit_wait=1 run_on_failure=Capture Page Screenshot", - "SuperRemoteLibrary": "Remote http://127.0.0.1:8270 WITH NAME SuperRemoteLibrary" - }, - "resources": { - "MyKeywords": "{root}/../MyKeywords.resource", - "MyOtherKeywords": "{resourceDirectory}/subdir/MyOtherKeywords.resource" - } - }, - "forcedImport": { - "libraries": [ - "SuperRemoteLibrary", - "SeleniumLibrary" - ], - "resources": - [ - "technical_keywords.resource" - ], - "variables": [ - "myVars.py" - ] - }, - "testCaseSplitPathRegEx": "^splitting\\..*", - "loggingConfiguration": { - "console": { - "logLevel": "info" - } - } -} diff --git a/tests/test_missing_files.py b/tests/test_missing_files.py deleted file mode 100644 index 12ae83f..0000000 --- a/tests/test_missing_files.py +++ /dev/null @@ -1,9 +0,0 @@ -import pytest - -from testbench2robotframework.utils import get_directory - - -def test_json_dir_does_not_exist(): - json_files_path = "invalid/file/path" - with pytest.raises(SystemExit): - get_directory(json_files_path) diff --git a/tests/test_robot_files_should_not_contain_invalid_characters.py b/tests/test_robot_files_should_not_contain_invalid_characters.py deleted file mode 100644 index a15b687..0000000 --- a/tests/test_robot_files_should_not_contain_invalid_characters.py +++ /dev/null @@ -1,11 +0,0 @@ -import pytest - -from testbench2robotframework.utils import replace_invalid_characters - -invalid_chars = ['<', '>', ':', '"', '/', '\\', '|', '?', '*'] - - -@pytest.mark.parametrize("invalid_char", invalid_chars) -def test_filename_does_not_contain_invalid_char(invalid_char): - filename = f"1.2.1 Testsuite with {invalid_char} in the name.robot" - assert replace_invalid_characters(filename).find(invalid_char) == -1 diff --git a/tests/test_zip_file_generation.py b/tests/test_zip_file_generation.py deleted file mode 100644 index be62808..0000000 --- a/tests/test_zip_file_generation.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -import shutil -from pathlib import Path - -import pytest -from robot.parsing.model.blocks import File - -from testbench2robotframework.testsuite_write import ( - clear_generation_directory, - directory_to_zip, - write_test_suite_files, -) - - -def test_previous_zip_gets_deleted(): - generation_dir = Path("./tests/test_data/Generated") - if not os.path.isdir("./tests/test_data/Generated"): - os.mkdir("./tests/test_data/Generated") - shutil.make_archive(str(generation_dir), 'zip', str(generation_dir)) - clear_generation_directory(generation_dir) - assert not os.path.exists(generation_dir) - - -def test_zip_gets_created(): - test_suites = {"iTB-TC-322-PC-12121212212": File()} - generation_dir = Path("./tests/test_data/Generated/zip_file") - write_test_suite_files(test_suites, generation_dir) - directory_to_zip(generation_dir) - assert os.path.exists("".join([str(generation_dir), ".zip"]))