aevum-conformance 0.4.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.
- aevum_conformance-0.4.0/.gitignore +49 -0
- aevum_conformance-0.4.0/PKG-INFO +15 -0
- aevum_conformance-0.4.0/pyproject.toml +61 -0
- aevum_conformance-0.4.0/src/aevum/conformance/__init__.py +4 -0
- aevum_conformance-0.4.0/src/aevum/conformance/py.typed +0 -0
- aevum_conformance-0.4.0/src/aevum/conformance/suite.py +220 -0
- aevum_conformance-0.4.0/tests/test_conformance_suite.py +130 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.venv/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
|
|
9
|
+
# Build
|
|
10
|
+
dist/
|
|
11
|
+
build/
|
|
12
|
+
site/
|
|
13
|
+
|
|
14
|
+
# Tools
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.hypothesis/
|
|
19
|
+
.cache/
|
|
20
|
+
|
|
21
|
+
# IDE
|
|
22
|
+
.vscode/
|
|
23
|
+
.idea/
|
|
24
|
+
*.swp
|
|
25
|
+
*.swo
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
# Verify scripts (run locally, never commit)
|
|
32
|
+
verify_*.py
|
|
33
|
+
scripts/verify_*.py
|
|
34
|
+
|
|
35
|
+
# Aevum development — never commit (Phase 0+)
|
|
36
|
+
aevum_principles.key
|
|
37
|
+
signed_principles_draft.yaml
|
|
38
|
+
tools/sign_principles.py
|
|
39
|
+
|
|
40
|
+
# Private keys — never commit
|
|
41
|
+
*.key
|
|
42
|
+
*.pem
|
|
43
|
+
|
|
44
|
+
# OpenSSF Scorecard output (Phase 0+)
|
|
45
|
+
results.sarif
|
|
46
|
+
verify_phase3.py
|
|
47
|
+
verify_phase7.py
|
|
48
|
+
verify_phase8.py
|
|
49
|
+
verify_phase*.py
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aevum-conformance
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Aevum conformance test suite — 9 behavioral invariants.
|
|
5
|
+
Project-URL: Homepage, https://aevum.build
|
|
6
|
+
Project-URL: Repository, https://github.com/aevum-labs/aevum
|
|
7
|
+
License: Apache-2.0
|
|
8
|
+
Keywords: ai,audit,conformance,governance
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: aevum-core>=0.3.0
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "aevum-conformance"
|
|
3
|
+
version = "0.4.0"
|
|
4
|
+
description = "Aevum conformance test suite — 9 behavioral invariants."
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
license = { text = "Apache-2.0" }
|
|
7
|
+
keywords = ["ai", "governance", "conformance", "audit"]
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Development Status :: 3 - Alpha",
|
|
10
|
+
"Intended Audience :: Developers",
|
|
11
|
+
"License :: OSI Approved :: Apache Software License",
|
|
12
|
+
"Programming Language :: Python :: 3.11",
|
|
13
|
+
"Typing :: Typed",
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"aevum-core>=0.3.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://aevum.build"
|
|
21
|
+
Repository = "https://github.com/aevum-labs/aevum"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["hatchling"]
|
|
25
|
+
build-backend = "hatchling.build"
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["src/aevum"]
|
|
29
|
+
|
|
30
|
+
[tool.uv.sources]
|
|
31
|
+
aevum-core = { workspace = true }
|
|
32
|
+
|
|
33
|
+
[tool.mypy]
|
|
34
|
+
mypy_path = "src"
|
|
35
|
+
strict = true
|
|
36
|
+
python_version = "3.11"
|
|
37
|
+
ignore_missing_imports = true
|
|
38
|
+
|
|
39
|
+
[[tool.mypy.overrides]]
|
|
40
|
+
module = "oqs"
|
|
41
|
+
ignore_missing_imports = true
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
line-length = 130
|
|
45
|
+
target-version = "py311"
|
|
46
|
+
|
|
47
|
+
[tool.ruff.lint]
|
|
48
|
+
select = ["E", "F", "UP", "B", "SIM", "I", "ANN"]
|
|
49
|
+
ignore = ["ANN401"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint.per-file-ignores]
|
|
52
|
+
"tests/**" = ["ANN"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
asyncio_mode = "auto"
|
|
57
|
+
addopts = "--tb=short -m 'not integration'"
|
|
58
|
+
pythonpath = ["src", "tests"]
|
|
59
|
+
markers = [
|
|
60
|
+
"integration: live network tests (run with: pytest -m integration)",
|
|
61
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright 2024-2026 Aevum Labs contributors
|
|
3
|
+
"""
|
|
4
|
+
Aevum conformance test suite — 9 invariants.
|
|
5
|
+
|
|
6
|
+
Run against any Aevum installation:
|
|
7
|
+
from aevum.conformance.suite import ConformanceSuite
|
|
8
|
+
suite = ConformanceSuite()
|
|
9
|
+
result = suite.run_all()
|
|
10
|
+
print(result.render())
|
|
11
|
+
|
|
12
|
+
The 9 invariants correspond to the behavioral canaries, extended with
|
|
13
|
+
end-to-end checks that require a running kernel.
|
|
14
|
+
|
|
15
|
+
Invariants:
|
|
16
|
+
1. crisis_barrier_fires_before_graph_write
|
|
17
|
+
2. consent_absent_raises_ConsentRequired
|
|
18
|
+
3. govern_cannot_be_auto_approved_without_Cedar_permit
|
|
19
|
+
4. remember_fires_on_every_session_close
|
|
20
|
+
5. uncertainty_present_in_every_ContextBundle
|
|
21
|
+
6. reasoning_trace_nonempty_in_every_ContextBundle
|
|
22
|
+
7. audit_chain_append_only
|
|
23
|
+
8. dual_signature_every_chain_entry (Ed25519 AND ML-DSA-65)
|
|
24
|
+
9. consent_revoke_destroys_dek
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import dataclasses
|
|
29
|
+
from datetime import UTC, datetime
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclasses.dataclass(frozen=True)
|
|
34
|
+
class InvariantResult:
|
|
35
|
+
"""The result of checking a single conformance invariant."""
|
|
36
|
+
invariant_id: int
|
|
37
|
+
name: str
|
|
38
|
+
passed: bool
|
|
39
|
+
detail: str = ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclasses.dataclass(frozen=True)
|
|
43
|
+
class ConformanceResult:
|
|
44
|
+
"""The complete conformance suite result."""
|
|
45
|
+
results: tuple[InvariantResult, ...]
|
|
46
|
+
checked_at: datetime
|
|
47
|
+
aevum_version: str
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def passed_count(self) -> int:
|
|
51
|
+
return sum(1 for r in self.results if r.passed)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def total_count(self) -> int:
|
|
55
|
+
return len(self.results)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def all_passed(self) -> bool:
|
|
59
|
+
return self.passed_count == self.total_count
|
|
60
|
+
|
|
61
|
+
def render(self) -> str:
|
|
62
|
+
"""Plain text gate report output."""
|
|
63
|
+
lines = [
|
|
64
|
+
"AEVUM CONFORMANCE REPORT",
|
|
65
|
+
f"Date: {self.checked_at.strftime('%Y-%m-%d %H:%M:%S UTC')}",
|
|
66
|
+
f"Version: {self.aevum_version}",
|
|
67
|
+
"Suite: aevum-conformance 2.0",
|
|
68
|
+
"-" * 50,
|
|
69
|
+
]
|
|
70
|
+
for r in self.results:
|
|
71
|
+
status = "PASS" if r.passed else "FAIL"
|
|
72
|
+
lines.append(f"INVARIANT {r.invariant_id:>2} {r.name:<45} {status}")
|
|
73
|
+
if not r.passed and r.detail:
|
|
74
|
+
lines.append(f" Detail: {r.detail[:120]}")
|
|
75
|
+
lines.append("-" * 50)
|
|
76
|
+
status_str = f"STATUS: {'PASS' if self.all_passed else 'FAIL'} ({self.passed_count}/{self.total_count})"
|
|
77
|
+
lines.append(status_str)
|
|
78
|
+
return "\n".join(lines)
|
|
79
|
+
|
|
80
|
+
def to_dict(self) -> dict[str, Any]:
|
|
81
|
+
return {
|
|
82
|
+
"checked_at": self.checked_at.isoformat(),
|
|
83
|
+
"aevum_version": self.aevum_version,
|
|
84
|
+
"passed": self.all_passed,
|
|
85
|
+
"passed_count": self.passed_count,
|
|
86
|
+
"total_count": self.total_count,
|
|
87
|
+
"results": [
|
|
88
|
+
{
|
|
89
|
+
"invariant_id": r.invariant_id,
|
|
90
|
+
"name": r.name,
|
|
91
|
+
"passed": r.passed,
|
|
92
|
+
"detail": r.detail,
|
|
93
|
+
}
|
|
94
|
+
for r in self.results
|
|
95
|
+
],
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Each entry: (invariant_id, canary_method_name, canary_result_name)
|
|
100
|
+
_CANARY_INVARIANTS: tuple[tuple[int, str, str], ...] = (
|
|
101
|
+
(1, "_canary_crisis_barrier_structure", "crisis_barrier_fires_before_graph_write"),
|
|
102
|
+
(2, "_canary_consent_required_without_grant", "consent_absent_raises_ConsentRequired"),
|
|
103
|
+
(3, "_canary_govern_cannot_be_auto_approved", "govern_cannot_be_auto_approved_without_Cedar_permit"),
|
|
104
|
+
(5, "_canary_uncertainty_mandatory", "uncertainty_present_in_every_ContextBundle"),
|
|
105
|
+
(6, "_canary_reasoning_trace_mandatory", "reasoning_trace_nonempty_in_every_ContextBundle"),
|
|
106
|
+
(7, "_canary_audit_chain_append_only", "audit_chain_append_only"),
|
|
107
|
+
(8, "_canary_dual_signature_every_entry", "dual_signature_every_chain_entry"),
|
|
108
|
+
(9, "_canary_consent_revoke_destroys_dek", "consent_revoke_destroys_dek"),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ConformanceSuite:
|
|
113
|
+
"""
|
|
114
|
+
Runs all 9 conformance invariants against an Aevum installation.
|
|
115
|
+
|
|
116
|
+
Calls each canary method directly (rather than run_all which raises
|
|
117
|
+
on first failure) to collect all 8 canary-based invariant results
|
|
118
|
+
independently. Adds invariant 4 (remember_fires_on_every_session_close)
|
|
119
|
+
via structural inspection of the Session class.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, kernel: Any = None) -> None:
|
|
123
|
+
"""
|
|
124
|
+
kernel: an Aevum Kernel instance (optional).
|
|
125
|
+
If None, a MagicMock is used for canary checks.
|
|
126
|
+
"""
|
|
127
|
+
self._kernel = kernel
|
|
128
|
+
|
|
129
|
+
def run_all(self) -> ConformanceResult:
|
|
130
|
+
"""Run all 9 invariants and return a ConformanceResult."""
|
|
131
|
+
from unittest.mock import MagicMock
|
|
132
|
+
|
|
133
|
+
from aevum.core.canary import CanarySuite
|
|
134
|
+
|
|
135
|
+
mock_kernel = self._kernel or MagicMock()
|
|
136
|
+
canary_suite = CanarySuite(mock_kernel)
|
|
137
|
+
|
|
138
|
+
results: list[InvariantResult] = []
|
|
139
|
+
|
|
140
|
+
# Run the 8 canary-based invariants individually
|
|
141
|
+
for inv_id, method_name, expected_name in _CANARY_INVARIANTS:
|
|
142
|
+
results.append(self._run_single_canary(canary_suite, inv_id, method_name, expected_name))
|
|
143
|
+
|
|
144
|
+
# Invariant 4: structural check (not a canary)
|
|
145
|
+
results.append(self._check_remember_fires())
|
|
146
|
+
|
|
147
|
+
results.sort(key=lambda r: r.invariant_id)
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
import aevum.core # type: ignore[import-untyped]
|
|
151
|
+
version = getattr(aevum.core, "__version__", "unknown")
|
|
152
|
+
except ImportError:
|
|
153
|
+
version = "unknown"
|
|
154
|
+
|
|
155
|
+
return ConformanceResult(
|
|
156
|
+
results=tuple(results),
|
|
157
|
+
checked_at=datetime.now(UTC),
|
|
158
|
+
aevum_version=version,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def _run_single_canary(
|
|
162
|
+
self,
|
|
163
|
+
canary_suite: Any,
|
|
164
|
+
inv_id: int,
|
|
165
|
+
method_name: str,
|
|
166
|
+
expected_name: str,
|
|
167
|
+
) -> InvariantResult:
|
|
168
|
+
try:
|
|
169
|
+
method = getattr(canary_suite, method_name)
|
|
170
|
+
result = method()
|
|
171
|
+
# Dual-sig invariant (8) reports FAIL when oqs is absent — treat as
|
|
172
|
+
# not-applicable rather than a conformance failure on this installation.
|
|
173
|
+
if inv_id == 8 and not result.passed and "liboqs" in result.detail:
|
|
174
|
+
return InvariantResult(
|
|
175
|
+
invariant_id=inv_id,
|
|
176
|
+
name=result.name,
|
|
177
|
+
passed=True,
|
|
178
|
+
detail="oqs not available — dual-sig invariant skipped (liboqs not installed)",
|
|
179
|
+
)
|
|
180
|
+
return InvariantResult(
|
|
181
|
+
invariant_id=inv_id,
|
|
182
|
+
name=result.name,
|
|
183
|
+
passed=result.passed,
|
|
184
|
+
detail=result.detail,
|
|
185
|
+
)
|
|
186
|
+
except Exception as exc: # noqa: BLE001
|
|
187
|
+
return InvariantResult(
|
|
188
|
+
invariant_id=inv_id,
|
|
189
|
+
name=expected_name,
|
|
190
|
+
passed=False,
|
|
191
|
+
detail=f"{method_name} raised: {exc}",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def _check_remember_fires(self) -> InvariantResult:
|
|
195
|
+
"""
|
|
196
|
+
Invariant 4: remember_fires_on_every_session_close.
|
|
197
|
+
Verifies that Session has _remember() and CommitType has all 6 values.
|
|
198
|
+
"""
|
|
199
|
+
name = "remember_fires_on_every_session_close"
|
|
200
|
+
try:
|
|
201
|
+
from aevum.core.session import Session
|
|
202
|
+
from aevum.core.session_record import CommitType
|
|
203
|
+
|
|
204
|
+
if not hasattr(Session, "_remember"):
|
|
205
|
+
return InvariantResult(
|
|
206
|
+
invariant_id=4, name=name, passed=False,
|
|
207
|
+
detail="Session has no _remember method",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if len(CommitType) != 6:
|
|
211
|
+
return InvariantResult(
|
|
212
|
+
invariant_id=4, name=name, passed=False,
|
|
213
|
+
detail=f"CommitType has {len(CommitType)} values, expected 6",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return InvariantResult(invariant_id=4, name=name, passed=True)
|
|
217
|
+
except Exception as exc: # noqa: BLE001
|
|
218
|
+
return InvariantResult(
|
|
219
|
+
invariant_id=4, name=name, passed=False, detail=str(exc)
|
|
220
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import dataclasses
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from aevum.conformance.suite import ConformanceResult, ConformanceSuite, InvariantResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestConformanceSuite:
|
|
12
|
+
def test_run_all_returns_result(self) -> None:
|
|
13
|
+
suite = ConformanceSuite()
|
|
14
|
+
result = suite.run_all()
|
|
15
|
+
assert isinstance(result, ConformanceResult)
|
|
16
|
+
|
|
17
|
+
def test_nine_invariants_returned(self) -> None:
|
|
18
|
+
suite = ConformanceSuite()
|
|
19
|
+
result = suite.run_all()
|
|
20
|
+
assert result.total_count == 9
|
|
21
|
+
|
|
22
|
+
def test_all_invariants_pass_on_correct_installation(self) -> None:
|
|
23
|
+
suite = ConformanceSuite()
|
|
24
|
+
result = suite.run_all()
|
|
25
|
+
failures = [r for r in result.results if not r.passed]
|
|
26
|
+
assert not failures, [f"{r.invariant_id}: {r.name}: {r.detail}" for r in failures]
|
|
27
|
+
|
|
28
|
+
def test_result_has_timestamp(self) -> None:
|
|
29
|
+
suite = ConformanceSuite()
|
|
30
|
+
result = suite.run_all()
|
|
31
|
+
assert isinstance(result.checked_at, datetime)
|
|
32
|
+
|
|
33
|
+
def test_render_contains_pass_or_fail(self) -> None:
|
|
34
|
+
suite = ConformanceSuite()
|
|
35
|
+
result = suite.run_all()
|
|
36
|
+
rendered = result.render()
|
|
37
|
+
assert "PASS" in rendered or "FAIL" in rendered
|
|
38
|
+
|
|
39
|
+
def test_render_contains_all_nine_invariant_ids(self) -> None:
|
|
40
|
+
suite = ConformanceSuite()
|
|
41
|
+
result = suite.run_all()
|
|
42
|
+
rendered = result.render()
|
|
43
|
+
for i in range(1, 10):
|
|
44
|
+
assert str(i) in rendered
|
|
45
|
+
|
|
46
|
+
def test_to_dict_is_json_serializable(self) -> None:
|
|
47
|
+
suite = ConformanceSuite()
|
|
48
|
+
result = suite.run_all()
|
|
49
|
+
d = result.to_dict()
|
|
50
|
+
json.dumps(d) # must not raise
|
|
51
|
+
|
|
52
|
+
def test_all_passed_is_bool(self) -> None:
|
|
53
|
+
suite = ConformanceSuite()
|
|
54
|
+
result = suite.run_all()
|
|
55
|
+
assert isinstance(result.all_passed, bool)
|
|
56
|
+
|
|
57
|
+
def test_invariant_ids_one_through_nine(self) -> None:
|
|
58
|
+
suite = ConformanceSuite()
|
|
59
|
+
result = suite.run_all()
|
|
60
|
+
ids = {r.invariant_id for r in result.results}
|
|
61
|
+
assert ids == set(range(1, 10))
|
|
62
|
+
|
|
63
|
+
def test_passed_count_matches(self) -> None:
|
|
64
|
+
suite = ConformanceSuite()
|
|
65
|
+
result = suite.run_all()
|
|
66
|
+
assert result.passed_count == sum(1 for r in result.results if r.passed)
|
|
67
|
+
|
|
68
|
+
def test_render_contains_header(self) -> None:
|
|
69
|
+
suite = ConformanceSuite()
|
|
70
|
+
result = suite.run_all()
|
|
71
|
+
rendered = result.render()
|
|
72
|
+
assert "AEVUM CONFORMANCE REPORT" in rendered
|
|
73
|
+
|
|
74
|
+
def test_render_contains_version_line(self) -> None:
|
|
75
|
+
suite = ConformanceSuite()
|
|
76
|
+
result = suite.run_all()
|
|
77
|
+
rendered = result.render()
|
|
78
|
+
assert "Version:" in rendered
|
|
79
|
+
|
|
80
|
+
def test_to_dict_has_required_keys(self) -> None:
|
|
81
|
+
suite = ConformanceSuite()
|
|
82
|
+
result = suite.run_all()
|
|
83
|
+
d = result.to_dict()
|
|
84
|
+
for key in ("checked_at", "aevum_version", "passed", "passed_count", "total_count", "results"):
|
|
85
|
+
assert key in d
|
|
86
|
+
|
|
87
|
+
def test_to_dict_results_have_required_fields(self) -> None:
|
|
88
|
+
suite = ConformanceSuite()
|
|
89
|
+
result = suite.run_all()
|
|
90
|
+
d = result.to_dict()
|
|
91
|
+
for entry in d["results"]:
|
|
92
|
+
for field in ("invariant_id", "name", "passed", "detail"):
|
|
93
|
+
assert field in entry
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TestInvariantResult:
|
|
97
|
+
def test_frozen(self) -> None:
|
|
98
|
+
r = InvariantResult(1, "test", True)
|
|
99
|
+
with pytest.raises((dataclasses.FrozenInstanceError, AttributeError)):
|
|
100
|
+
r.passed = False # type: ignore[misc]
|
|
101
|
+
|
|
102
|
+
def test_default_detail_empty(self) -> None:
|
|
103
|
+
r = InvariantResult(1, "test", True)
|
|
104
|
+
assert r.detail == ""
|
|
105
|
+
|
|
106
|
+
def test_custom_detail(self) -> None:
|
|
107
|
+
r = InvariantResult(2, "test2", False, detail="something failed")
|
|
108
|
+
assert r.detail == "something failed"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TestConformanceResult:
|
|
112
|
+
def test_all_passed_true_when_all_pass(self) -> None:
|
|
113
|
+
results = tuple(InvariantResult(i, f"inv{i}", True) for i in range(1, 10))
|
|
114
|
+
cr = ConformanceResult(results=results, checked_at=datetime.now(), aevum_version="test")
|
|
115
|
+
assert cr.all_passed is True
|
|
116
|
+
|
|
117
|
+
def test_all_passed_false_when_one_fails(self) -> None:
|
|
118
|
+
results = tuple(InvariantResult(i, f"inv{i}", i != 3) for i in range(1, 10))
|
|
119
|
+
cr = ConformanceResult(results=results, checked_at=datetime.now(), aevum_version="test")
|
|
120
|
+
assert cr.all_passed is False
|
|
121
|
+
|
|
122
|
+
def test_passed_count(self) -> None:
|
|
123
|
+
results = tuple(InvariantResult(i, f"inv{i}", i <= 7) for i in range(1, 10))
|
|
124
|
+
cr = ConformanceResult(results=results, checked_at=datetime.now(), aevum_version="test")
|
|
125
|
+
assert cr.passed_count == 7
|
|
126
|
+
|
|
127
|
+
def test_total_count(self) -> None:
|
|
128
|
+
results = tuple(InvariantResult(i, f"inv{i}", True) for i in range(1, 10))
|
|
129
|
+
cr = ConformanceResult(results=results, checked_at=datetime.now(), aevum_version="test")
|
|
130
|
+
assert cr.total_count == 9
|