"""Tests for core/policy_engine.py (REQ-308, v1.25). Protocol conformance, registry selection, NullEngine fallback, unknown-engine KeyError, and the NullEngine-satisfies-Protocol assertion (G-Q8a — proves the swap boundary is real without implementing OPA). """ import json import os import sys from pathlib import Path from unittest import mock import pytest sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) import core.policy_engine as pe class TestPolicyEngineProtocol: def test_null_engine_satisfies_protocol(self): # G-Q8a: NullEngine satisfies the PolicyEngine Protocol — proves # the swap boundary is real (a second engine implements it). eng = pe.NullEngine() assert isinstance(eng, pe.PolicyEngine) def test_null_engine_is_configured_false(self): assert pe.NullEngine().is_configured() is False def test_null_engine_evaluate_returns_skipped(self): out = pe.NullEngine().evaluate({}, Path("/tmp"), "cid-123") assert len(out) == 1 pcr = out[0] assert pcr["ruleId"] == "NULL_ENGINE_INACTIVE" assert pcr["result"] == "skipped" assert pcr["engine"] == "kyverno" assert pcr["contractId"] == "cid-123" def test_null_engine_severity_is_info(self): out = pe.NullEngine().evaluate({}, Path("/tmp"), "cid") assert out[0]["severity"] == "info" class TestRegistry: def test_register_and_get(self, tmp_path, monkeypatch): # Register a stub engine and verify get_engine() returns it. class StubEngine: name = "stub" def is_configured(self) -> bool: return True def evaluate(self, payload, policy_dir, contract_id): return [{"contractId": contract_id, "engine": "kyverno", "ruleId": "STUB", "result": "pass", "severity": "info", "message": "", "evaluatedAt": "t", "resourceRef": "", "evidence": {}}] pe._REGISTRY.clear() pe.register("stub", StubEngine) monkeypatch.setattr(pe, "_load_config_policy", lambda: {"engine": "stub"}) eng = pe.get_engine() assert eng.name == "stub" pe._REGISTRY.clear() pe._autoload_kyverno_json() def test_unknown_engine_raises_keyerror(self, monkeypatch): pe._REGISTRY.clear() monkeypatch.setattr(pe, "_load_config_policy", lambda: {"engine": "nonexistent"}) with pytest.raises(KeyError, match="Unknown policy engine"): pe.get_engine() pe._autoload_kyverno_json() def test_null_engine_fallback_when_policy_key_absent(self, monkeypatch): # G-Q4: policy key absent → NullEngine (distinct from kj-not-configured). monkeypatch.setattr(pe, "_load_config_policy", lambda: None) eng = pe.get_engine() assert isinstance(eng, pe.NullEngine) assert eng.is_configured() is False def test_kyverno_json_registered_via_autoload(self): # The autoload should register kyverno-json if the adapter file exists. pe._autoload_kyverno_json() assert "kyverno-json" in pe._REGISTRY or len(pe._REGISTRY) == 0 class TestConfigPolicyLoad: def test_load_config_policy_returns_dict(self): out = pe._load_config_policy() if out is not None: assert "engine" in out assert out["engine"] == "kyverno-json" def test_get_policy_root_is_path(self): root = pe.get_policy_root() assert isinstance(root, Path) assert root.name == "policies" or str(root).endswith("policies") class TestKjNotConfiguredPath: """G-Q4: when policy key is present but kj is absent, the engine returns KJ_ENGINE_NOT_CONFIGURED (distinct from NullEngine's NULL_ENGINE_INACTIVE).""" def test_kj_not_configured_returns_distinct_ruleid(self, monkeypatch): # Force the registry to return KyvernoJsonEngine, then mock # `which kj` to return None. pe._autoload_kyverno_json() if "kyverno-json" not in pe._REGISTRY: pytest.skip("kyverno-json adapter not loadable in this env") monkeypatch.setattr(pe, "_load_config_policy", lambda: {"engine": "kyverno-json"}) eng = pe.get_engine() # Mock is_configured → False with mock.patch.object(eng, "is_configured", return_value=False): out = eng.evaluate({}, Path("/tmp"), "cid-456") assert len(out) == 1 assert out[0]["ruleId"] == "KJ_ENGINE_NOT_CONFIGURED" assert out[0]["result"] == "skipped" assert out[0]["contractId"] == "cid-456" # Distinct from NullEngine assert out[0]["ruleId"] != "NULL_ENGINE_INACTIVE"