264 lines
10 KiB
Python
264 lines
10 KiB
Python
# #region TestPolicyResolutionService [C:2] [TYPE Module]
|
|
# @SEMANTICS: clean-release, policy-resolution, trusted-snapshots, contracts
|
|
# @PURPOSE: Verify trusted policy snapshot resolution contract and error guards.
|
|
# @LAYER Tests
|
|
# @RELATION DEPENDS_ON -> [EXT:frontend:policy_resolution_service]
|
|
# @RELATION DEPENDS_ON -> [EXT:frontend:repository]
|
|
# @RELATION DEPENDS_ON -> [clean_release_exceptions]
|
|
# @INVARIANT: Resolution uses only ConfigManager active IDs and rejects runtime override attempts.
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from types import SimpleNamespace
|
|
|
|
from src.models.clean_release import CleanPolicySnapshot, SourceRegistrySnapshot
|
|
from src.services.clean_release.exceptions import PolicyResolutionError
|
|
from src.services.clean_release.policy_resolution_service import (
|
|
resolve_trusted_policy_snapshots,
|
|
)
|
|
from src.services.clean_release.repository import CleanReleaseRepository
|
|
|
|
|
|
# #region _config_manager [C:2] [TYPE Function]
|
|
# @RELATION BINDS_TO -> [EXT:frontend:TestPolicyResolutionService]
|
|
# @PURPOSE: Build deterministic ConfigManager-like stub for tests.
|
|
# @INVARIANT: Only settings.clean_release.active_policy_id and active_registry_id are populated; any other settings field access raises AttributeError.
|
|
# @PRE: policy_id and registry_id may be None or non-empty strings.
|
|
# @POST: Returns object exposing get_config().settings.clean_release active IDs.
|
|
def _config_manager(policy_id, registry_id):
|
|
clean_release = SimpleNamespace(
|
|
active_policy_id=policy_id, active_registry_id=registry_id
|
|
)
|
|
settings = SimpleNamespace(clean_release=clean_release)
|
|
config = SimpleNamespace(settings=settings)
|
|
return SimpleNamespace(get_config=lambda: config)
|
|
|
|
|
|
# #endregion _config_manager
|
|
|
|
|
|
# #region test_resolve_trusted_policy_snapshots_missing_profile [C:2] [TYPE Function]
|
|
# @RELATION BINDS_TO -> [EXT:frontend:TestPolicyResolutionService]
|
|
# @PURPOSE: Ensure resolution fails when trusted profile is not configured.
|
|
# @PRE: active_policy_id is None.
|
|
# @POST: Raises PolicyResolutionError with missing trusted profile reason.
|
|
def test_resolve_trusted_policy_snapshots_missing_profile():
|
|
repository = CleanReleaseRepository()
|
|
config_manager = _config_manager(policy_id=None, registry_id="registry-1")
|
|
|
|
with pytest.raises(PolicyResolutionError, match="missing trusted profile"):
|
|
resolve_trusted_policy_snapshots(
|
|
config_manager=config_manager,
|
|
repository=repository,
|
|
)
|
|
|
|
|
|
# #endregion test_resolve_trusted_policy_snapshots_missing_profile
|
|
|
|
|
|
# #region test_resolve_trusted_policy_snapshots_missing_registry [C:2] [TYPE Function]
|
|
# @RELATION BINDS_TO -> [EXT:frontend:TestPolicyResolutionService]
|
|
# @PURPOSE: Ensure resolution fails when trusted registry is not configured.
|
|
# @PRE: active_registry_id is None and active_policy_id is set.
|
|
# @POST: Raises PolicyResolutionError with missing trusted registry reason.
|
|
def test_resolve_trusted_policy_snapshots_missing_registry():
|
|
repository = CleanReleaseRepository()
|
|
config_manager = _config_manager(policy_id="policy-1", registry_id=None)
|
|
|
|
with pytest.raises(PolicyResolutionError, match="missing trusted registry"):
|
|
resolve_trusted_policy_snapshots(
|
|
config_manager=config_manager,
|
|
repository=repository,
|
|
)
|
|
|
|
|
|
# #endregion test_resolve_trusted_policy_snapshots_missing_registry
|
|
|
|
|
|
# #region test_resolve_trusted_policy_snapshots_rejects_override_attempt [C:2] [TYPE Function]
|
|
# @RELATION BINDS_TO -> [EXT:frontend:TestPolicyResolutionService]
|
|
# @PURPOSE: Ensure runtime override attempt is rejected even if snapshots exist.
|
|
# @PRE: valid trusted snapshots exist in repository and override is provided.
|
|
# @POST: Raises PolicyResolutionError with override forbidden reason.
|
|
def test_resolve_trusted_policy_snapshots_rejects_override_attempt():
|
|
repository = CleanReleaseRepository()
|
|
repository.save_policy(
|
|
CleanPolicySnapshot(
|
|
id="policy-1",
|
|
policy_id="baseline",
|
|
policy_version="1.0.0",
|
|
content_json={"rules": []},
|
|
registry_snapshot_id="registry-1",
|
|
immutable=True,
|
|
)
|
|
)
|
|
repository.save_registry(
|
|
SourceRegistrySnapshot(
|
|
id="registry-1",
|
|
registry_id="trusted",
|
|
registry_version="1.0.0",
|
|
allowed_hosts=["internal.local"],
|
|
allowed_schemes=["https"],
|
|
allowed_source_types=["repo"],
|
|
immutable=True,
|
|
)
|
|
)
|
|
|
|
config_manager = _config_manager(policy_id="policy-1", registry_id="registry-1")
|
|
|
|
with pytest.raises(PolicyResolutionError, match="override attempt is forbidden"):
|
|
resolve_trusted_policy_snapshots(
|
|
config_manager=config_manager,
|
|
repository=repository,
|
|
policy_id_override="policy-override",
|
|
)
|
|
|
|
|
|
# #endregion test_resolve_trusted_policy_snapshots_rejects_override_attempt
|
|
|
|
|
|
# #region test_missing_clean_release_settings [C:2] [TYPE Function]
|
|
def test_missing_clean_release_settings():
|
|
"""clean_release_settings missing → PolicyResolutionError (line 44)."""
|
|
repository = CleanReleaseRepository()
|
|
# settings has no clean_release attribute
|
|
config = SimpleNamespace(settings=SimpleNamespace())
|
|
config_manager = SimpleNamespace(get_config=lambda: config)
|
|
|
|
with pytest.raises(PolicyResolutionError, match="clean_release settings are missing"):
|
|
resolve_trusted_policy_snapshots(
|
|
config_manager=config_manager,
|
|
repository=repository,
|
|
)
|
|
# #endregion test_missing_clean_release_settings
|
|
|
|
|
|
# #region test_policy_not_found [C:2] [TYPE Function]
|
|
def test_policy_not_found():
|
|
"""get_policy returns None → PolicyResolutionError (line 60)."""
|
|
repository = CleanReleaseRepository()
|
|
config_manager = _config_manager(policy_id="policy-missing", registry_id="registry-1")
|
|
|
|
with pytest.raises(PolicyResolutionError, match="trusted policy snapshot.*not found"):
|
|
resolve_trusted_policy_snapshots(
|
|
config_manager=config_manager,
|
|
repository=repository,
|
|
)
|
|
# #endregion test_policy_not_found
|
|
|
|
|
|
# #region test_registry_not_found [C:2] [TYPE Function]
|
|
def test_registry_not_found():
|
|
"""get_registry returns None → PolicyResolutionError (line 66)."""
|
|
repository = CleanReleaseRepository()
|
|
repository.save_policy(
|
|
CleanPolicySnapshot(
|
|
id="policy-1", policy_id="policy-1", policy_version="1.0.0",
|
|
content_json={"rules": []},
|
|
registry_snapshot_id="registry-missing",
|
|
immutable=True,
|
|
)
|
|
)
|
|
config_manager = _config_manager(policy_id="policy-1", registry_id="registry-missing")
|
|
|
|
with pytest.raises(PolicyResolutionError, match="trusted registry snapshot.*not found"):
|
|
resolve_trusted_policy_snapshots(
|
|
config_manager=config_manager,
|
|
repository=repository,
|
|
)
|
|
# #endregion test_registry_not_found
|
|
|
|
|
|
# #region test_policy_not_immutable [C:2] [TYPE Function]
|
|
def test_policy_not_immutable():
|
|
"""Policy snapshot not immutable → PolicyResolutionError (line 71)."""
|
|
repository = CleanReleaseRepository()
|
|
repository.save_policy(
|
|
CleanPolicySnapshot(
|
|
id="policy-1", policy_id="policy-1", policy_version="1.0.0",
|
|
content_json={"rules": []},
|
|
registry_snapshot_id="registry-1",
|
|
immutable=False, # not immutable
|
|
)
|
|
)
|
|
repository.save_registry(
|
|
SourceRegistrySnapshot(
|
|
id="registry-1", registry_id="registry-1", registry_version="1.0.0",
|
|
allowed_hosts=["internal.local"], allowed_schemes=["https"],
|
|
allowed_source_types=["repo"], immutable=True,
|
|
)
|
|
)
|
|
config_manager = _config_manager(policy_id="policy-1", registry_id="registry-1")
|
|
|
|
with pytest.raises(PolicyResolutionError, match="policy snapshot must be immutable"):
|
|
resolve_trusted_policy_snapshots(
|
|
config_manager=config_manager,
|
|
repository=repository,
|
|
)
|
|
# #endregion test_policy_not_immutable
|
|
|
|
|
|
# #region test_registry_not_immutable [C:2] [TYPE Function]
|
|
def test_registry_not_immutable():
|
|
"""Registry snapshot not immutable → PolicyResolutionError (line 73)."""
|
|
repository = CleanReleaseRepository()
|
|
repository.save_policy(
|
|
CleanPolicySnapshot(
|
|
id="policy-1", policy_id="policy-1", policy_version="1.0.0",
|
|
content_json={"rules": []},
|
|
registry_snapshot_id="registry-1",
|
|
immutable=True,
|
|
)
|
|
)
|
|
repository.save_registry(
|
|
SourceRegistrySnapshot(
|
|
id="registry-1", registry_id="registry-1", registry_version="1.0.0",
|
|
allowed_hosts=["internal.local"], allowed_schemes=["https"],
|
|
allowed_source_types=["repo"],
|
|
immutable=False, # not immutable
|
|
)
|
|
)
|
|
config_manager = _config_manager(policy_id="policy-1", registry_id="registry-1")
|
|
|
|
with pytest.raises(PolicyResolutionError, match="registry snapshot must be immutable"):
|
|
resolve_trusted_policy_snapshots(
|
|
config_manager=config_manager,
|
|
repository=repository,
|
|
)
|
|
# #endregion test_registry_not_immutable
|
|
|
|
|
|
# #region test_full_success_path [C:2] [TYPE Function]
|
|
def test_full_success_path():
|
|
"""All valid inputs → returns (policy, registry) tuple (line 75)."""
|
|
repository = CleanReleaseRepository()
|
|
policy = CleanPolicySnapshot(
|
|
id="policy-1", policy_id="policy-1", policy_version="1.0.0",
|
|
content_json={"rules": ["allow-all"]},
|
|
registry_snapshot_id="registry-1",
|
|
immutable=True,
|
|
)
|
|
registry = SourceRegistrySnapshot(
|
|
id="registry-1", registry_id="registry-1", registry_version="1.0.0",
|
|
allowed_hosts=["internal.local"], allowed_schemes=["https"],
|
|
allowed_source_types=["repo"], immutable=True,
|
|
)
|
|
repository.save_policy(policy)
|
|
repository.save_registry(registry)
|
|
|
|
config_manager = _config_manager(policy_id="policy-1", registry_id="registry-1")
|
|
|
|
policy_result, registry_result = resolve_trusted_policy_snapshots(
|
|
config_manager=config_manager,
|
|
repository=repository,
|
|
)
|
|
assert policy_result.id == "policy-1"
|
|
assert policy_result.immutable is True
|
|
assert registry_result.id == "registry-1"
|
|
assert registry_result.immutable is True
|
|
# #endregion test_full_success_path
|
|
|
|
|
|
# #endregion TestPolicyResolutionService
|