Files
kontextual-engine/tests/test_asset_registry.py

183 lines
6.1 KiB
Python

from pathlib import Path
import pytest
from kontextual_engine import (
Actor,
ActorType,
AssetRegistryService,
AssetRepresentation,
AuthorizationError,
Classification,
InMemoryAssetRegistryRepository,
LifecycleState,
MetadataRecord,
OperationContext,
PolicyDecision,
RepresentationKind,
Sensitivity,
SourceReference,
SQLiteAssetRegistryRepository,
ValidationError,
)
def test_asset_registry_service_creates_assets_with_versions_and_audit() -> None:
repo = InMemoryAssetRegistryRepository()
service = AssetRegistryService(repo)
context = operation_context()
source_ref = SourceReference(
source_system="repo",
path="docs/intent.md",
checksum="sha256:source",
)
representation = AssetRepresentation.from_content(
"asset-intent",
RepresentationKind.SOURCE,
"text/markdown",
"# Intent\n",
storage_ref="object://intent-source",
source_ref_id=source_ref.id,
)
metadata = MetadataRecord(
"topic",
"architecture",
provenance={"producer": "human"},
confirmed=True,
)
result = service.create_asset(
"Intent",
Classification(
asset_type="document",
sensitivity=Sensitivity.INTERNAL,
owner="Platform Knowledge",
),
context,
asset_id="asset-intent",
source_refs=[source_ref],
representations=[representation],
metadata_records=[metadata],
)
assert result.asset.id == "asset-intent"
assert result.asset.current_version_id == result.version.version_id
assert result.version.sequence == 1
assert result.audit_event.outcome.value == "success"
assert result.policy_decision.allowed is True
assert repo.get_asset("asset-intent").source_refs[0].path == "docs/intent.md"
assert repo.list_representations(asset_id="asset-intent")[0].storage_ref == "object://intent-source"
assert repo.list_metadata_records("asset-intent")[0].confirmed is True
assert repo.list_audit_events(target="asset:asset-intent")[0].operation == "asset.create"
def test_asset_registry_lifecycle_policy_denial_fails_closed_and_audits() -> None:
repo = InMemoryAssetRegistryRepository()
service = AssetRegistryService(repo, policy_gateway=DenyLifecyclePolicy())
context = operation_context()
created = service.create_asset(
"Governed Asset",
Classification(asset_type="document", sensitivity=Sensitivity.CONFIDENTIAL),
context,
asset_id="asset-governed",
)
with pytest.raises(AuthorizationError) as exc_info:
service.transition_lifecycle(created.asset.id, LifecycleState.RETIRED, context)
events = repo.list_audit_events(target="asset:asset-governed")
assert exc_info.value.details["correlation_id"] == "corr-test"
assert exc_info.value.details["policy_decision"]["effect"] == "fail_closed"
assert [event.outcome.value for event in events] == ["success", "denied"]
assert repo.get_asset("asset-governed").lifecycle == LifecycleState.ACTIVE
def test_sqlite_asset_registry_survives_reinstantiation(tmp_path: Path) -> None:
db_path = tmp_path / "registry.sqlite"
repo = SQLiteAssetRegistryRepository(db_path)
service = AssetRegistryService(repo)
context = operation_context()
source_ref = SourceReference(source_system="repo", path="README.md", checksum="sha256:readme")
source = AssetRepresentation.from_content(
"asset-readme",
RepresentationKind.SOURCE,
"text/markdown",
"# Readme\n",
storage_ref="object://readme-source",
)
created = service.create_asset(
"Readme",
Classification(asset_type="document", sensitivity=Sensitivity.PUBLIC),
context,
asset_id="asset-readme",
source_refs=[source_ref],
representations=[source],
)
service.add_metadata_record(
created.asset.id,
MetadataRecord("owner", "Platform Knowledge", confirmed=True),
context,
)
service.request_delete(created.asset.id, context)
reloaded = SQLiteAssetRegistryRepository(db_path)
asset = reloaded.get_asset("asset-readme")
assert asset.lifecycle == LifecycleState.DELETE_REQUESTED
assert asset.source_refs[0].path == "README.md"
assert [item.kind for item in reloaded.list_representations(asset_id=asset.id)] == [
RepresentationKind.SOURCE
]
assert [item.key for item in reloaded.list_metadata_records(asset.id)] == ["owner"]
assert [version.sequence for version in reloaded.list_versions(asset.id)] == [1, 2, 3]
assert [event.operation for event in reloaded.list_audit_events(target="asset:asset-readme")] == [
"asset.create",
"asset.metadata.add",
"asset.lifecycle.transition",
]
def test_sqlite_registry_enforces_representation_asset_reference(tmp_path: Path) -> None:
repo = SQLiteAssetRegistryRepository(tmp_path / "registry.sqlite")
representation = AssetRepresentation.from_content(
"missing-asset",
RepresentationKind.NORMALIZED,
"text/plain",
"normalized",
)
with pytest.raises(ValidationError, match="unknown asset"):
repo.save_representation(representation)
def operation_context() -> OperationContext:
actor = Actor.create(
ActorType.HUMAN,
actor_id="user-test",
display_name="Test User",
groups=["engineering"],
)
return OperationContext.create(actor, correlation_id="corr-test")
class DenyLifecyclePolicy:
def authorize(
self,
context: OperationContext,
action: str,
resource: str,
*,
resource_metadata: dict[str, str] | None = None,
) -> PolicyDecision:
if action == "asset.lifecycle.transition":
return PolicyDecision.fail_closed(
context.actor.id,
action,
resource,
reason="lifecycle transitions require review",
context={"resource_metadata": resource_metadata or {}},
)
return PolicyDecision.allow(context.actor.id, action, resource)