vault88 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vault88-0.1.0/PKG-INFO +168 -0
- vault88-0.1.0/README.md +149 -0
- vault88-0.1.0/pyproject.toml +66 -0
- vault88-0.1.0/setup.cfg +4 -0
- vault88-0.1.0/tests/test_args.py +65 -0
- vault88-0.1.0/tests/test_console.py +160 -0
- vault88-0.1.0/tests/test_environment.py +82 -0
- vault88-0.1.0/tests/test_junit_reporter.py +281 -0
- vault88-0.1.0/tests/test_loader.py +266 -0
- vault88-0.1.0/tests/test_loaders.py +56 -0
- vault88-0.1.0/tests/test_log_archive_reporter.py +165 -0
- vault88-0.1.0/tests/test_logger.py +91 -0
- vault88-0.1.0/tests/test_main.py +207 -0
- vault88-0.1.0/tests/test_positive_path.py +155 -0
- vault88-0.1.0/tests/test_runner_edge_cases.py +134 -0
- vault88-0.1.0/tests/test_ssh.py +211 -0
- vault88-0.1.0/tests/test_test_construct.py +188 -0
- vault88-0.1.0/tests/test_webdriver.py +95 -0
- vault88-0.1.0/vault88/__about__.py +8 -0
- vault88-0.1.0/vault88/__init__.py +31 -0
- vault88-0.1.0/vault88/__main__.py +5 -0
- vault88-0.1.0/vault88/args.py +38 -0
- vault88-0.1.0/vault88/constructs/__init__.py +10 -0
- vault88-0.1.0/vault88/constructs/environment.py +70 -0
- vault88-0.1.0/vault88/constructs/result.py +28 -0
- vault88-0.1.0/vault88/constructs/stage.py +14 -0
- vault88-0.1.0/vault88/constructs/test.py +137 -0
- vault88-0.1.0/vault88/core/__init__.py +9 -0
- vault88-0.1.0/vault88/core/environment_loader.py +15 -0
- vault88-0.1.0/vault88/core/environment_loaders/__init__.py +4 -0
- vault88-0.1.0/vault88/core/environment_loaders/api_loader.py +19 -0
- vault88-0.1.0/vault88/core/environment_loaders/json_file_loader.py +18 -0
- vault88-0.1.0/vault88/core/loader.py +169 -0
- vault88-0.1.0/vault88/core/logger.py +174 -0
- vault88-0.1.0/vault88/core/reporter.py +25 -0
- vault88-0.1.0/vault88/core/reporters/__init__.py +0 -0
- vault88-0.1.0/vault88/core/reporters/junit_reporter.py +67 -0
- vault88-0.1.0/vault88/core/reporters/log_archive_reporter.py +29 -0
- vault88-0.1.0/vault88/core/runner.py +88 -0
- vault88-0.1.0/vault88/main.py +166 -0
- vault88-0.1.0/vault88/utils/__init__.py +9 -0
- vault88-0.1.0/vault88/utils/console.py +193 -0
- vault88-0.1.0/vault88/utils/ssh.py +88 -0
- vault88-0.1.0/vault88/utils/webdriver.py +45 -0
- vault88-0.1.0/vault88.egg-info/PKG-INFO +168 -0
- vault88-0.1.0/vault88.egg-info/SOURCES.txt +48 -0
- vault88-0.1.0/vault88.egg-info/dependency_links.txt +1 -0
- vault88-0.1.0/vault88.egg-info/entry_points.txt +2 -0
- vault88-0.1.0/vault88.egg-info/requires.txt +3 -0
- vault88-0.1.0/vault88.egg-info/top_level.txt +1 -0
vault88-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vault88
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python automated test framework
|
|
5
|
+
Author-email: Brad Murdoch <brad.murdoch@me.ca>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Documentation, https://gitlab.glider-eng.com/python-packages/vault88/-/wikis/home
|
|
8
|
+
Project-URL: Issues, https://gitlab.glider-eng.com/python-packages/vault88/-/work_items
|
|
9
|
+
Project-URL: Source, https://gitlab.glider-eng.com/python-packages/vault88
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: junit-xml==1.9
|
|
17
|
+
Requires-Dist: paramiko<4,>=3.0
|
|
18
|
+
Requires-Dist: selenium<5,>=4.0
|
|
19
|
+
|
|
20
|
+
# vault88
|
|
21
|
+
|
|
22
|
+
[](https://gitlab.glider-eng.com/python-packages/vault88/-/pipelines)
|
|
23
|
+
[](https://gitlab.glider-eng.com/python-packages/vault88/-/commits/main)
|
|
24
|
+
|
|
25
|
+
**vault88** is a Python automated test framework designed for structured hardware and software validation in CI/CD pipelines. Tests are written as Python classes, organised into stages, and results can be published through configurable reporters.
|
|
26
|
+
|
|
27
|
+
## Architecture
|
|
28
|
+
|
|
29
|
+
| Component | Role |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `Loader` | Discovers test classes from files or directories; supports tag-based include/exclude filtering |
|
|
32
|
+
| `Logger` | Centralised logging with rotating file handler and optional verbose (debug) output |
|
|
33
|
+
| `Runner` | Instantiates and executes a test class through its full lifecycle: `selfCheck` → `setup` → `run` → `teardown` |
|
|
34
|
+
| `Reporter` | Pluggable reporters loaded from the config file; built-in reporters include JUnit XML and log archive |
|
|
35
|
+
|
|
36
|
+
Test results are grouped into named **stages** within each test, and each result records an action, expected value, actual value, outcome, timestamp, and optional requirement references.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install vault88
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv add vault88
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Writing a test
|
|
51
|
+
|
|
52
|
+
Subclass `Test` and implement the four lifecycle methods:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from vault88 import Test
|
|
56
|
+
|
|
57
|
+
class MyTest(Test):
|
|
58
|
+
name = "My Test"
|
|
59
|
+
tags = ["smoke"]
|
|
60
|
+
|
|
61
|
+
def selfCheck(self) -> bool:
|
|
62
|
+
# Return False to skip the test entirely
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
def setup(self) -> bool:
|
|
66
|
+
# Prepare resources; return False to abort
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
def run(self) -> None:
|
|
70
|
+
self.newStage("Basic checks")
|
|
71
|
+
self.assertEqual("Verify result", expected=42, actual=compute())
|
|
72
|
+
|
|
73
|
+
def teardown(self) -> None:
|
|
74
|
+
pass
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Available assertion helpers: `assertEqual`, `assertNotEqual`, `assertTrue`, `assertFalse`, `assertGreaterThan`, `assertLessThan`, `assertGreaterThanOrEqual`, `assertLessThanOrEqual`, `assertIn`, `assertNotIn`, `assertIsNone`, `assertIsNotNone`.
|
|
78
|
+
|
|
79
|
+
## Running tests
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
vault88 <testpath> [options]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
| Option | Description |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `testpath` | Path to a test file or directory |
|
|
88
|
+
| `-t TAG [TAG ...]` | Only run tests that have **at least one** of these tags |
|
|
89
|
+
| `-nt TAG [TAG ...]` | Exclude tests that have any of these tags |
|
|
90
|
+
| `-r` | Recursively search directories for tests |
|
|
91
|
+
| `-l FILE` | Log file path (default: `./test_execution.log`) |
|
|
92
|
+
| `-v` | Verbose / debug logging |
|
|
93
|
+
| `--env PATH_OR_URL` | Load an environment from a JSON file or HTTP(S) URL |
|
|
94
|
+
| `--junit` | Generate a `testresults.xml` JUnit report in the current directory |
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
Reporters are configured in `/etc/vault88.conf` (INI format). Each `[REPORTER<n>]` section loads one reporter:
|
|
99
|
+
|
|
100
|
+
```ini
|
|
101
|
+
[REPORTER1]
|
|
102
|
+
module = vault88.core.reporters.junit_reporter
|
|
103
|
+
class = JUnitReporter
|
|
104
|
+
enabled = true
|
|
105
|
+
suite_name = My Project Tests
|
|
106
|
+
|
|
107
|
+
[REPORTER2]
|
|
108
|
+
module = vault88.core.reporters.log_archive_reporter
|
|
109
|
+
class = LogArchiveReporter
|
|
110
|
+
enabled = true
|
|
111
|
+
output_dir = ./logs
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
Dependencies are managed with [uv](https://docs.astral.sh/uv/). Install the project with test dependencies:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
uv sync
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Running the test suite
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
uv run pytest
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Run with coverage:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
uv run pytest --cov=vault88 --cov-report=term-missing
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Run tests in parallel:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
uv run pytest -n auto
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Linting with ruff
|
|
141
|
+
|
|
142
|
+
Check for issues:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv run ruff check .
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Auto-fix fixable issues:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
uv run ruff check --fix .
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Check formatting:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
uv run ruff format --check .
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Apply formatting:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
uv run ruff format .
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
vault88-0.1.0/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# vault88
|
|
2
|
+
|
|
3
|
+
[](https://gitlab.glider-eng.com/python-packages/vault88/-/pipelines)
|
|
4
|
+
[](https://gitlab.glider-eng.com/python-packages/vault88/-/commits/main)
|
|
5
|
+
|
|
6
|
+
**vault88** is a Python automated test framework designed for structured hardware and software validation in CI/CD pipelines. Tests are written as Python classes, organised into stages, and results can be published through configurable reporters.
|
|
7
|
+
|
|
8
|
+
## Architecture
|
|
9
|
+
|
|
10
|
+
| Component | Role |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `Loader` | Discovers test classes from files or directories; supports tag-based include/exclude filtering |
|
|
13
|
+
| `Logger` | Centralised logging with rotating file handler and optional verbose (debug) output |
|
|
14
|
+
| `Runner` | Instantiates and executes a test class through its full lifecycle: `selfCheck` → `setup` → `run` → `teardown` |
|
|
15
|
+
| `Reporter` | Pluggable reporters loaded from the config file; built-in reporters include JUnit XML and log archive |
|
|
16
|
+
|
|
17
|
+
Test results are grouped into named **stages** within each test, and each result records an action, expected value, actual value, outcome, timestamp, and optional requirement references.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install vault88
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv add vault88
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Writing a test
|
|
32
|
+
|
|
33
|
+
Subclass `Test` and implement the four lifecycle methods:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from vault88 import Test
|
|
37
|
+
|
|
38
|
+
class MyTest(Test):
|
|
39
|
+
name = "My Test"
|
|
40
|
+
tags = ["smoke"]
|
|
41
|
+
|
|
42
|
+
def selfCheck(self) -> bool:
|
|
43
|
+
# Return False to skip the test entirely
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
def setup(self) -> bool:
|
|
47
|
+
# Prepare resources; return False to abort
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
def run(self) -> None:
|
|
51
|
+
self.newStage("Basic checks")
|
|
52
|
+
self.assertEqual("Verify result", expected=42, actual=compute())
|
|
53
|
+
|
|
54
|
+
def teardown(self) -> None:
|
|
55
|
+
pass
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Available assertion helpers: `assertEqual`, `assertNotEqual`, `assertTrue`, `assertFalse`, `assertGreaterThan`, `assertLessThan`, `assertGreaterThanOrEqual`, `assertLessThanOrEqual`, `assertIn`, `assertNotIn`, `assertIsNone`, `assertIsNotNone`.
|
|
59
|
+
|
|
60
|
+
## Running tests
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
vault88 <testpath> [options]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
| Option | Description |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `testpath` | Path to a test file or directory |
|
|
69
|
+
| `-t TAG [TAG ...]` | Only run tests that have **at least one** of these tags |
|
|
70
|
+
| `-nt TAG [TAG ...]` | Exclude tests that have any of these tags |
|
|
71
|
+
| `-r` | Recursively search directories for tests |
|
|
72
|
+
| `-l FILE` | Log file path (default: `./test_execution.log`) |
|
|
73
|
+
| `-v` | Verbose / debug logging |
|
|
74
|
+
| `--env PATH_OR_URL` | Load an environment from a JSON file or HTTP(S) URL |
|
|
75
|
+
| `--junit` | Generate a `testresults.xml` JUnit report in the current directory |
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
Reporters are configured in `/etc/vault88.conf` (INI format). Each `[REPORTER<n>]` section loads one reporter:
|
|
80
|
+
|
|
81
|
+
```ini
|
|
82
|
+
[REPORTER1]
|
|
83
|
+
module = vault88.core.reporters.junit_reporter
|
|
84
|
+
class = JUnitReporter
|
|
85
|
+
enabled = true
|
|
86
|
+
suite_name = My Project Tests
|
|
87
|
+
|
|
88
|
+
[REPORTER2]
|
|
89
|
+
module = vault88.core.reporters.log_archive_reporter
|
|
90
|
+
class = LogArchiveReporter
|
|
91
|
+
enabled = true
|
|
92
|
+
output_dir = ./logs
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
Dependencies are managed with [uv](https://docs.astral.sh/uv/). Install the project with test dependencies:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv sync
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Running the test suite
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uv run pytest
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Run with coverage:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv run pytest --cov=vault88 --cov-report=term-missing
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Run tests in parallel:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
uv run pytest -n auto
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Linting with ruff
|
|
122
|
+
|
|
123
|
+
Check for issues:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
uv run ruff check .
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Auto-fix fixable issues:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
uv run ruff check --fix .
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Check formatting:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
uv run ruff format --check .
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Apply formatting:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
uv run ruff format .
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vault88"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Brad Murdoch", email = "brad.murdoch@me.ca" }
|
|
10
|
+
]
|
|
11
|
+
description = "Python automated test framework"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.12"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Programming Language :: Python",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"License :: OSI Approved :: MIT License"
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"junit-xml==1.9",
|
|
23
|
+
"paramiko>=3.0,<4",
|
|
24
|
+
"selenium>=4.0,<5",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Documentation = "https://gitlab.glider-eng.com/python-packages/vault88/-/wikis/home"
|
|
29
|
+
Issues = "https://gitlab.glider-eng.com/python-packages/vault88/-/work_items"
|
|
30
|
+
Source = "https://gitlab.glider-eng.com/python-packages/vault88"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
vault88 = "vault88.main:main"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
test = [
|
|
37
|
+
"coverage[toml]>=7.13.4,<8",
|
|
38
|
+
"pytest>=9.0.2,<10",
|
|
39
|
+
"pytest-cov>=7.0.0,<8",
|
|
40
|
+
"pytest-xdist>=3.8.0,<4",
|
|
41
|
+
"ruff>=0.15.2,<1"
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.uv]
|
|
45
|
+
default-groups = ["test"]
|
|
46
|
+
|
|
47
|
+
[[tool.uv.index]]
|
|
48
|
+
name = "nexus"
|
|
49
|
+
url = "https://nexus.glider-eng.com/repository/pypi_hosted/simple/"
|
|
50
|
+
publish-url = "https://nexus.glider-eng.com/repository/pypi_hosted/"
|
|
51
|
+
explicit = true
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
|
|
56
|
+
[tool.coverage.run]
|
|
57
|
+
branch = true
|
|
58
|
+
parallel = true
|
|
59
|
+
omit = []
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
line-length = 100
|
|
63
|
+
target-version = "py312"
|
|
64
|
+
|
|
65
|
+
[tool.ruff.lint]
|
|
66
|
+
select = ["E", "F", "I"]
|
vault88-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Tests for CLI argument parsing"""
|
|
2
|
+
|
|
3
|
+
from vault88.args import parseArgs
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestParseArgs:
|
|
7
|
+
def test_minimal_required_arg(self, monkeypatch):
|
|
8
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/"])
|
|
9
|
+
args = parseArgs()
|
|
10
|
+
assert args.testpath == "tests/"
|
|
11
|
+
|
|
12
|
+
def test_tags_flag(self, monkeypatch):
|
|
13
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/", "-t", "smoke", "fast"])
|
|
14
|
+
args = parseArgs()
|
|
15
|
+
assert args.tags == ["smoke", "fast"]
|
|
16
|
+
|
|
17
|
+
def test_nottags_flag(self, monkeypatch):
|
|
18
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/", "-nt", "slow"])
|
|
19
|
+
args = parseArgs()
|
|
20
|
+
assert args.nottags == ["slow"]
|
|
21
|
+
|
|
22
|
+
def test_recursive_flag(self, monkeypatch):
|
|
23
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/", "-r"])
|
|
24
|
+
args = parseArgs()
|
|
25
|
+
assert args.recursive is True
|
|
26
|
+
|
|
27
|
+
def test_recursive_default_is_false(self, monkeypatch):
|
|
28
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/"])
|
|
29
|
+
args = parseArgs()
|
|
30
|
+
assert args.recursive is False
|
|
31
|
+
|
|
32
|
+
def test_log_flag(self, monkeypatch):
|
|
33
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/", "-l", "/tmp/run.log"])
|
|
34
|
+
args = parseArgs()
|
|
35
|
+
assert args.log == "/tmp/run.log"
|
|
36
|
+
|
|
37
|
+
def test_log_default(self, monkeypatch):
|
|
38
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/"])
|
|
39
|
+
args = parseArgs()
|
|
40
|
+
assert args.log == "./test_execution.log"
|
|
41
|
+
|
|
42
|
+
def test_junit_flag(self, monkeypatch):
|
|
43
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/", "--junit"])
|
|
44
|
+
args = parseArgs()
|
|
45
|
+
assert args.junit is True
|
|
46
|
+
|
|
47
|
+
def test_verbose_flag(self, monkeypatch):
|
|
48
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/", "-v"])
|
|
49
|
+
args = parseArgs()
|
|
50
|
+
assert args.verbose is True
|
|
51
|
+
|
|
52
|
+
def test_env_flag(self, monkeypatch):
|
|
53
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/", "--env", "http://env.local/api"])
|
|
54
|
+
args = parseArgs()
|
|
55
|
+
assert args.env == "http://env.local/api"
|
|
56
|
+
|
|
57
|
+
def test_tags_default_is_none(self, monkeypatch):
|
|
58
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/"])
|
|
59
|
+
args = parseArgs()
|
|
60
|
+
assert args.tags is None
|
|
61
|
+
|
|
62
|
+
def test_env_default_is_none(self, monkeypatch):
|
|
63
|
+
monkeypatch.setattr("sys.argv", ["vault88", "tests/"])
|
|
64
|
+
args = parseArgs()
|
|
65
|
+
assert args.env is None
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Tests for console.py truncation and overflow guards"""
|
|
2
|
+
|
|
3
|
+
from vault88.constructs.result import Result, ResultOutcome
|
|
4
|
+
from vault88.utils.console import (
|
|
5
|
+
_box_line,
|
|
6
|
+
_visible_truncate,
|
|
7
|
+
result_box_lines,
|
|
8
|
+
strip_ansi,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
_GREEN = "\033[32m"
|
|
12
|
+
_RESET = "\033[0m"
|
|
13
|
+
_WIDTH = 80
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _result(
|
|
17
|
+
action="act", expected="exp", actual="act", outcome=ResultOutcome.PASSED, requirements=None
|
|
18
|
+
):
|
|
19
|
+
return Result(action, expected, actual, outcome, requirements or [])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _display_width(line: str) -> int:
|
|
23
|
+
return len(strip_ansi(line))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestVisibleTruncate:
|
|
27
|
+
def test_zero_width_returns_empty(self):
|
|
28
|
+
assert _visible_truncate("hello", 0) == ""
|
|
29
|
+
|
|
30
|
+
def test_negative_width_returns_empty(self):
|
|
31
|
+
assert _visible_truncate("hello", -5) == ""
|
|
32
|
+
|
|
33
|
+
def test_text_shorter_than_max_unchanged(self):
|
|
34
|
+
assert _visible_truncate("hi", 10) == "hi"
|
|
35
|
+
|
|
36
|
+
def test_text_exactly_at_max_unchanged(self):
|
|
37
|
+
assert _visible_truncate("hello", 5) == "hello"
|
|
38
|
+
|
|
39
|
+
def test_plain_text_truncated_with_ellipsis(self):
|
|
40
|
+
result = _visible_truncate("abcdef", 4)
|
|
41
|
+
assert result == "abc…"
|
|
42
|
+
|
|
43
|
+
def test_truncated_visible_length_equals_max_width(self):
|
|
44
|
+
result = _visible_truncate("abcdefghij", 6)
|
|
45
|
+
assert len(strip_ansi(result)) == 6
|
|
46
|
+
|
|
47
|
+
def test_ansi_codes_not_counted_toward_width(self):
|
|
48
|
+
colored = f"{_GREEN}hello{_RESET}"
|
|
49
|
+
result = _visible_truncate(colored, 5)
|
|
50
|
+
assert strip_ansi(result) == "hello"
|
|
51
|
+
|
|
52
|
+
def test_ansi_codes_preserved_before_cut_point(self):
|
|
53
|
+
colored = f"{_GREEN}abcdef{_RESET}"
|
|
54
|
+
result = _visible_truncate(colored, 4)
|
|
55
|
+
assert strip_ansi(result) == "abc…"
|
|
56
|
+
assert _GREEN in result
|
|
57
|
+
|
|
58
|
+
def test_ansi_colored_truncated_visible_length_matches_max_width(self):
|
|
59
|
+
colored = f"{_GREEN}abcdefghij{_RESET}"
|
|
60
|
+
result = _visible_truncate(colored, 6)
|
|
61
|
+
assert len(strip_ansi(result)) == 6
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TestBoxLine:
|
|
65
|
+
def test_short_content_pads_to_box_width(self):
|
|
66
|
+
assert _display_width(_box_line("hi", 20)) == 20
|
|
67
|
+
|
|
68
|
+
def test_empty_content_pads_to_box_width(self):
|
|
69
|
+
assert _display_width(_box_line("", 20)) == 20
|
|
70
|
+
|
|
71
|
+
def test_content_fills_inner_exactly(self):
|
|
72
|
+
inner = 20 - 4
|
|
73
|
+
assert _display_width(_box_line("x" * inner, 20)) == 20
|
|
74
|
+
|
|
75
|
+
def test_content_exceeding_inner_truncated_to_box_width(self):
|
|
76
|
+
assert _display_width(_box_line("x" * 200, 20)) == 20
|
|
77
|
+
|
|
78
|
+
def test_truncated_line_contains_ellipsis(self):
|
|
79
|
+
assert "…" in _box_line("x" * 200, 20)
|
|
80
|
+
|
|
81
|
+
def test_truncated_line_has_right_border(self):
|
|
82
|
+
assert _box_line("x" * 200, 20).endswith("│")
|
|
83
|
+
|
|
84
|
+
def test_all_lines_start_with_left_border(self):
|
|
85
|
+
for content in ("", "short", "x" * 200):
|
|
86
|
+
assert _box_line(content, 40).startswith("│")
|
|
87
|
+
|
|
88
|
+
def test_ansi_content_exceeding_inner_truncated_to_box_width(self):
|
|
89
|
+
content = f"{_GREEN}" + "x" * 100 + f"{_RESET}"
|
|
90
|
+
assert _display_width(_box_line(content, 20)) == 20
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TestResultBoxLinesOverflow:
|
|
94
|
+
def _badge_in(self, lines, badge="[PASS]"):
|
|
95
|
+
return any(badge in strip_ansi(line) for line in lines)
|
|
96
|
+
|
|
97
|
+
def _all_widths(self, lines):
|
|
98
|
+
return [_display_width(line) for line in lines]
|
|
99
|
+
|
|
100
|
+
def test_normal_result_all_lines_fit_exactly(self):
|
|
101
|
+
lines = result_box_lines(_result(), _WIDTH)
|
|
102
|
+
assert all(w == _WIDTH for w in self._all_widths(lines))
|
|
103
|
+
|
|
104
|
+
def test_pass_badge_present(self):
|
|
105
|
+
lines = result_box_lines(_result(outcome=ResultOutcome.PASSED), _WIDTH)
|
|
106
|
+
assert self._badge_in(lines, "[PASS]")
|
|
107
|
+
|
|
108
|
+
def test_fail_badge_present(self):
|
|
109
|
+
lines = result_box_lines(_result(outcome=ResultOutcome.FAILED), _WIDTH)
|
|
110
|
+
assert self._badge_in(lines, "[FAIL]")
|
|
111
|
+
|
|
112
|
+
def test_long_action_all_lines_fit(self):
|
|
113
|
+
lines = result_box_lines(_result(action="A" * 300), _WIDTH)
|
|
114
|
+
assert all(w == _WIDTH for w in self._all_widths(lines))
|
|
115
|
+
|
|
116
|
+
def test_long_expected_all_lines_fit(self):
|
|
117
|
+
lines = result_box_lines(_result(expected="E" * 300), _WIDTH)
|
|
118
|
+
assert all(w == _WIDTH for w in self._all_widths(lines))
|
|
119
|
+
|
|
120
|
+
def test_long_actual_all_lines_fit(self):
|
|
121
|
+
lines = result_box_lines(_result(actual="A" * 300), _WIDTH)
|
|
122
|
+
assert all(w == _WIDTH for w in self._all_widths(lines))
|
|
123
|
+
|
|
124
|
+
def test_long_requirements_all_lines_fit(self):
|
|
125
|
+
reqs = ["REQ-" + str(i) for i in range(50)]
|
|
126
|
+
lines = result_box_lines(_result(requirements=reqs), _WIDTH)
|
|
127
|
+
assert all(w == _WIDTH for w in self._all_widths(lines))
|
|
128
|
+
|
|
129
|
+
def test_badge_present_with_all_fields_long(self):
|
|
130
|
+
r = _result(
|
|
131
|
+
action="ACT" * 100,
|
|
132
|
+
expected="EXP" * 100,
|
|
133
|
+
actual="ACT" * 100,
|
|
134
|
+
requirements=["REQ-" + str(i) for i in range(30)],
|
|
135
|
+
)
|
|
136
|
+
lines = result_box_lines(r, _WIDTH)
|
|
137
|
+
assert self._badge_in(lines, "[PASS]")
|
|
138
|
+
assert all(w == _WIDTH for w in self._all_widths(lines))
|
|
139
|
+
|
|
140
|
+
def test_narrow_width_all_lines_fit(self):
|
|
141
|
+
lines = result_box_lines(_result(), 20)
|
|
142
|
+
assert all(w == 20 for w in self._all_widths(lines))
|
|
143
|
+
|
|
144
|
+
def test_narrow_width_badge_present(self):
|
|
145
|
+
lines = result_box_lines(_result(outcome=ResultOutcome.PASSED), 20)
|
|
146
|
+
assert self._badge_in(lines, "[PASS]")
|
|
147
|
+
|
|
148
|
+
def test_last_field_line_too_long_badge_still_present(self):
|
|
149
|
+
# Actual is the last field with no reqs; its last wrapped chunk will be
|
|
150
|
+
# artificially wide enough to trigger the badge-overflow guard.
|
|
151
|
+
r = _result(actual="X" * 300)
|
|
152
|
+
lines = result_box_lines(r, _WIDTH)
|
|
153
|
+
assert self._badge_in(lines, "[PASS]")
|
|
154
|
+
assert all(w == _WIDTH for w in self._all_widths(lines))
|
|
155
|
+
|
|
156
|
+
def test_long_reqs_last_line_badge_still_present(self):
|
|
157
|
+
r = _result(requirements=["REQ-" + "X" * 60])
|
|
158
|
+
lines = result_box_lines(r, _WIDTH)
|
|
159
|
+
assert self._badge_in(lines, "[PASS]")
|
|
160
|
+
assert all(w == _WIDTH for w in self._all_widths(lines))
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Tests for environment dataclasses and their from_dict constructors"""
|
|
2
|
+
|
|
3
|
+
from vault88.constructs.environment import Credential, Environment, Host, Service
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestCredential:
|
|
7
|
+
def test_username_only(self):
|
|
8
|
+
c = Credential.from_dict({"username": "alice"})
|
|
9
|
+
assert c.username == "alice"
|
|
10
|
+
assert c.password is None
|
|
11
|
+
assert c.key_path is None
|
|
12
|
+
|
|
13
|
+
def test_with_password_and_key_path(self):
|
|
14
|
+
c = Credential.from_dict({"username": "bob", "password": "s3cr3t", "key_path": "/id_rsa"})
|
|
15
|
+
assert c.password == "s3cr3t"
|
|
16
|
+
assert c.key_path == "/id_rsa"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestService:
|
|
20
|
+
def test_minimal(self):
|
|
21
|
+
s = Service.from_dict({"name": "http"})
|
|
22
|
+
assert s.name == "http"
|
|
23
|
+
assert s.port is None
|
|
24
|
+
assert s.url is None
|
|
25
|
+
assert s.credential is None
|
|
26
|
+
|
|
27
|
+
def test_with_port_url_and_credential(self):
|
|
28
|
+
s = Service.from_dict(
|
|
29
|
+
{
|
|
30
|
+
"name": "api",
|
|
31
|
+
"port": 8080,
|
|
32
|
+
"url": "http://localhost:8080",
|
|
33
|
+
"credential": {"username": "admin"},
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
assert s.port == 8080
|
|
37
|
+
assert s.url == "http://localhost:8080"
|
|
38
|
+
assert s.credential is not None
|
|
39
|
+
assert s.credential.username == "admin"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestHost:
|
|
43
|
+
def test_minimal(self):
|
|
44
|
+
h = Host.from_dict({"name": "server", "address": "10.0.0.1"})
|
|
45
|
+
assert h.name == "server"
|
|
46
|
+
assert h.address == "10.0.0.1"
|
|
47
|
+
assert h.services == []
|
|
48
|
+
assert h.credential is None
|
|
49
|
+
|
|
50
|
+
def test_with_services_and_credential(self):
|
|
51
|
+
h = Host.from_dict(
|
|
52
|
+
{
|
|
53
|
+
"name": "server",
|
|
54
|
+
"address": "10.0.0.1",
|
|
55
|
+
"services": [{"name": "ssh", "port": 22}],
|
|
56
|
+
"credential": {"username": "root"},
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
assert len(h.services) == 1
|
|
60
|
+
assert h.services[0].name == "ssh"
|
|
61
|
+
assert h.credential.username == "root"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TestEnvironment:
|
|
65
|
+
def test_minimal(self):
|
|
66
|
+
e = Environment.from_dict({"name": "staging"})
|
|
67
|
+
assert e.name == "staging"
|
|
68
|
+
assert e.hosts == []
|
|
69
|
+
|
|
70
|
+
def test_with_hosts(self):
|
|
71
|
+
e = Environment.from_dict(
|
|
72
|
+
{
|
|
73
|
+
"name": "prod",
|
|
74
|
+
"hosts": [
|
|
75
|
+
{"name": "web1", "address": "192.168.0.1"},
|
|
76
|
+
{"name": "db1", "address": "192.168.0.2"},
|
|
77
|
+
],
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
assert len(e.hosts) == 2
|
|
81
|
+
assert e.hosts[0].name == "web1"
|
|
82
|
+
assert e.hosts[1].name == "db1"
|