test(backend): add pytest baseline (B0.8)
Unit (SQLite, pure logic): - test_templating.py: Jinja2 sandbox, regex_extract, strict-undefined, sandbox blocks attribute-access escape, output blob 10 MB cap. - test_password.py: bcrypt hash + verify, empty / malformed handling. - test_soc_token.py: 256-bit url-safe token + bcrypt verification. - test_rbac_matrix.py: F11 invariants (lead ⊇ operator, SOC restricted to detection + report-read, audit_read & ttp_promote lead-only). - test_connector_factory.py: register / build / double-register-rejected, TaskStatus terminal helper, Mythic mapping vs empty Home mapping. - test_audit_hash.py: SHA-256 chain helper is deterministic and reacts to prev_hash / metadata changes. Integration scaffold (testcontainers Postgres): - tests/integration/conftest.py spins up postgres:16-alpine, monkeypatches MIMIC_DATABASE_URL, creates a Flask app + db.create_all. - test_healthz.py: end-to-end smoke through the Flask test client. 38 unit tests pass; ruff clean.
This commit is contained in:
80
backend/tests/unit/test_templating.py
Normal file
80
backend/tests/unit/test_templating.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Jinja2 sandbox + regex_extract tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from mimic.templating.filters import regex_extract
|
||||
from mimic.templating.sandbox import (
|
||||
CleanupRenderer,
|
||||
RenderError,
|
||||
StepOutputs,
|
||||
render_cleanup,
|
||||
)
|
||||
|
||||
|
||||
class TestRegexExtract:
|
||||
def test_returns_capture_group(self) -> None:
|
||||
assert regex_extract("hello world", r"hello (\w+)") == "world"
|
||||
|
||||
def test_default_when_no_match(self) -> None:
|
||||
assert regex_extract("hello", r"foo(\d+)", default="N/A") == "N/A"
|
||||
|
||||
def test_none_input_returns_default(self) -> None:
|
||||
assert regex_extract(None, r"x", default="empty") == "empty"
|
||||
|
||||
def test_supports_group_zero(self) -> None:
|
||||
assert regex_extract("abc123", r"\w+\d+", group=0) == "abc123"
|
||||
|
||||
|
||||
class TestCleanupRenderer:
|
||||
def setup_method(self) -> None:
|
||||
self.renderer = CleanupRenderer()
|
||||
|
||||
def test_render_params(self) -> None:
|
||||
out = self.renderer.render(
|
||||
"echo {{ params.target }}",
|
||||
params={"target": "WIN-01"},
|
||||
)
|
||||
assert out == "echo WIN-01"
|
||||
|
||||
def test_render_outputs_text(self) -> None:
|
||||
out = self.renderer.render(
|
||||
'echo "{{ outputs.text }}"',
|
||||
outputs=StepOutputs(text="captured"),
|
||||
)
|
||||
assert out == 'echo "captured"'
|
||||
|
||||
def test_regex_extract_filter(self) -> None:
|
||||
out = self.renderer.render(
|
||||
r"{{ outputs.text | regex_extract('pid=(\\d+)') }}",
|
||||
outputs=StepOutputs(text="status: pid=4242 user=svc"),
|
||||
)
|
||||
assert out == "4242"
|
||||
|
||||
def test_strict_undefined_raises(self) -> None:
|
||||
with pytest.raises(RenderError):
|
||||
self.renderer.render("{{ params.does_not_exist }}", params={})
|
||||
|
||||
def test_sandbox_forbids_attribute_access(self) -> None:
|
||||
with pytest.raises(RenderError):
|
||||
self.renderer.render(
|
||||
"{{ ().__class__.__bases__[0].__subclasses__() }}",
|
||||
params={},
|
||||
)
|
||||
|
||||
def test_module_singleton_round_trip(self) -> None:
|
||||
out = render_cleanup("hello {{ params.x }}", params={"x": "there"})
|
||||
assert out == "hello there"
|
||||
|
||||
|
||||
class TestStepOutputsBlob:
|
||||
def test_blob_returns_empty_when_no_path(self) -> None:
|
||||
out = StepOutputs(text="x")
|
||||
assert out.blob() == ""
|
||||
|
||||
def test_blob_caps_size(self, tmp_path) -> None:
|
||||
blob = tmp_path / "evidence.bin"
|
||||
blob.write_bytes(b"A" * 1024)
|
||||
out = StepOutputs(blob_path=blob, blob_max_bytes=10)
|
||||
assert out.blob() == "A" * 10
|
||||
Reference in New Issue
Block a user