generated from coulomb/repo-seed
CMIS compliance fixture integration and optional OpenCMIS TCK harness boundary
This commit is contained in:
@@ -120,6 +120,22 @@ Relationship listings and change logs now apply the same asset visibility gates
|
||||
as object reads. This prevents indirect leakage of confidential or restricted
|
||||
asset IDs through relationship targets or audit-backed change entries.
|
||||
|
||||
## Fixture And Optional TCK Integration
|
||||
|
||||
CMIS fixtures now act as active compatibility contracts:
|
||||
|
||||
- `examples/cmis/capability-fixtures.json` defines profile expectations and
|
||||
capability groups,
|
||||
- `tests/cmis/test_cmis_fixture_integration.py` compares those expectations to
|
||||
implemented profiles and access-point shapes,
|
||||
- `tests/cmis/opencmis-tck/tck-subset-map.json` maps fixture capability groups
|
||||
to selected OpenCMIS TCK groups,
|
||||
- `tests/cmis/opencmis-tck/tck-result-template.json` captures optional TCK
|
||||
result summaries and known capability gaps.
|
||||
|
||||
The default Python suite validates the fixture/TCK mapping without requiring
|
||||
Java or Maven. Actual OpenCMIS TCK execution remains opt-in.
|
||||
|
||||
Route-level tests are present but skip when the optional FastAPI/httpx service
|
||||
dependencies are not installed. Runtime-level Browser Binding tests cover the
|
||||
same behavior in the default Python test suite.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"binding": "browser",
|
||||
"mutations": false,
|
||||
"visibility": ["public", "internal"],
|
||||
"deny": ["confidential"],
|
||||
"deny": ["confidential", "restricted"],
|
||||
"expected_tck_posture": "repository/type/read/navigation/query/content-read subset"
|
||||
},
|
||||
"governed-authoring": {
|
||||
@@ -20,14 +20,14 @@
|
||||
"binding": "browser",
|
||||
"mutations": true,
|
||||
"visibility": ["public", "internal"],
|
||||
"deny": ["confidential"],
|
||||
"deny": ["confidential", "restricted"],
|
||||
"expected_tck_posture": "readonly subset plus selected create/update/delete checks"
|
||||
},
|
||||
"admin-export": {
|
||||
"description": "Service-account export profile for broad governance inspection.",
|
||||
"binding": "browser",
|
||||
"mutations": false,
|
||||
"visibility": ["public", "internal", "confidential"],
|
||||
"visibility": ["public", "internal", "confidential", "restricted"],
|
||||
"deny": [],
|
||||
"expected_tck_posture": "internal contract tests, not general client compatibility"
|
||||
},
|
||||
@@ -36,7 +36,7 @@
|
||||
"binding": "browser",
|
||||
"mutations": true,
|
||||
"visibility": ["public", "internal"],
|
||||
"deny": ["confidential"],
|
||||
"deny": ["confidential", "restricted"],
|
||||
"expected_tck_posture": "selected OpenCMIS TCK Browser Binding subset"
|
||||
}
|
||||
},
|
||||
@@ -200,4 +200,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,3 +26,15 @@ The first target is a selected Browser Binding subset:
|
||||
Mutation groups should only be enabled for `governed-authoring` or
|
||||
`compat-tck` profiles after the policy and audit gates are implemented.
|
||||
|
||||
Harness files:
|
||||
|
||||
- `tck-subset-map.json` maps engine capability groups to selected OpenCMIS TCK
|
||||
groups and known gaps.
|
||||
- `tck-result-template.json` is the compact result capture format for optional
|
||||
TCK runs. Results should be stored outside the default repo history unless
|
||||
they document an intentional compatibility baseline.
|
||||
|
||||
Default `pytest` does not execute Java/Maven. It validates the map and result
|
||||
template so the optional harness does not drift away from the implemented
|
||||
profiles and fixtures.
|
||||
|
||||
|
||||
28
tests/cmis/opencmis-tck/tck-result-template.json
Normal file
28
tests/cmis/opencmis-tck/tck-result-template.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run": {
|
||||
"tool": "Apache Chemistry OpenCMIS TCK",
|
||||
"tool_version": "1.1.0",
|
||||
"profile": "compat-tck",
|
||||
"repository_id": "kontextual-compat-tck",
|
||||
"browser_url": "http://127.0.0.1:8000/cmis/compat-tck/browser",
|
||||
"started_at": null,
|
||||
"finished_at": null
|
||||
},
|
||||
"summary": {
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"known_gap": 0
|
||||
},
|
||||
"groups": [
|
||||
{
|
||||
"capability_group": "repository-type",
|
||||
"tck_group": "RepositoryInfoTestGroup",
|
||||
"status": "not_run",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"capability_gaps": []
|
||||
}
|
||||
|
||||
66
tests/cmis/opencmis-tck/tck-subset-map.json
Normal file
66
tests/cmis/opencmis-tck/tck-subset-map.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"tck": {
|
||||
"name": "Apache Chemistry OpenCMIS TCK",
|
||||
"version": "1.1.0",
|
||||
"artifact": "org.apache.chemistry.opencmis:chemistry-opencmis-test-tck:1.1.0",
|
||||
"execution": "optional"
|
||||
},
|
||||
"target": {
|
||||
"profile": "compat-tck",
|
||||
"repository_id": "kontextual-compat-tck",
|
||||
"browser_url": "http://127.0.0.1:8000/cmis/compat-tck/browser"
|
||||
},
|
||||
"groups": [
|
||||
{
|
||||
"capability_group": "repository-type",
|
||||
"tck_groups": ["RepositoryInfoTestGroup", "TypesTestGroup"],
|
||||
"expected": "selected-pass"
|
||||
},
|
||||
{
|
||||
"capability_group": "navigation",
|
||||
"tck_groups": ["NavigationTestGroup"],
|
||||
"expected": "selected-pass"
|
||||
},
|
||||
{
|
||||
"capability_group": "object-content",
|
||||
"tck_groups": ["ObjectServiceTestGroup", "ContentStreamTestGroup"],
|
||||
"expected": "selected-pass"
|
||||
},
|
||||
{
|
||||
"capability_group": "versioning",
|
||||
"tck_groups": ["VersioningTestGroup"],
|
||||
"expected": "partial-pass",
|
||||
"known_gaps": ["private_working_copy"]
|
||||
},
|
||||
{
|
||||
"capability_group": "discovery-query",
|
||||
"tck_groups": ["QueryTestGroup"],
|
||||
"expected": "partial-pass",
|
||||
"known_gaps": ["full_cmis_sql_joins"]
|
||||
},
|
||||
{
|
||||
"capability_group": "relationships",
|
||||
"tck_groups": ["RelationshipTestGroup"],
|
||||
"expected": "selected-pass"
|
||||
},
|
||||
{
|
||||
"capability_group": "acl-policy",
|
||||
"tck_groups": ["AclTestGroup", "PolicyTestGroup"],
|
||||
"expected": "partial-pass",
|
||||
"known_gaps": ["apply_policy", "remove_policy"]
|
||||
},
|
||||
{
|
||||
"capability_group": "change-log",
|
||||
"tck_groups": ["ChangeLogTestGroup"],
|
||||
"expected": "selected-pass"
|
||||
},
|
||||
{
|
||||
"capability_group": "retention-renditions-bulk",
|
||||
"tck_groups": ["RenditionsTestGroup", "BulkUpdateTestGroup"],
|
||||
"expected": "skip",
|
||||
"known_gaps": ["retention_hold_mutation", "bulk_update_properties", "rendition_streams"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
122
tests/cmis/test_cmis_fixture_integration.py
Normal file
122
tests/cmis/test_cmis_fixture_integration.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from kontextual_engine import (
|
||||
Actor,
|
||||
ActorType,
|
||||
CMISAccessPoint,
|
||||
CMISAccessProfile,
|
||||
CMISAction,
|
||||
OperationContext,
|
||||
)
|
||||
|
||||
|
||||
pytestmark = pytest.mark.cmis
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
FIXTURE_PATH = ROOT / "examples" / "cmis" / "capability-fixtures.json"
|
||||
TCK_MAP_PATH = ROOT / "tests" / "cmis" / "opencmis-tck" / "tck-subset-map.json"
|
||||
TCK_RESULT_TEMPLATE = ROOT / "tests" / "cmis" / "opencmis-tck" / "tck-result-template.json"
|
||||
|
||||
ACTION_MAP = {
|
||||
"create_document": CMISAction.CREATE_DOCUMENT,
|
||||
"update_properties": CMISAction.UPDATE_PROPERTIES,
|
||||
"delete_object": CMISAction.DELETE_OBJECT,
|
||||
"set_content_stream": CMISAction.SET_CONTENT_STREAM,
|
||||
}
|
||||
|
||||
|
||||
def _catalog() -> dict:
|
||||
return json.loads(FIXTURE_PATH.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _tck_map() -> dict:
|
||||
return json.loads(TCK_MAP_PATH.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _profiles() -> dict[str, CMISAccessProfile]:
|
||||
return {
|
||||
profile.name: profile
|
||||
for profile in (
|
||||
CMISAccessProfile.readonly_browser(),
|
||||
CMISAccessProfile.governed_authoring(),
|
||||
CMISAccessProfile.admin_export(),
|
||||
CMISAccessProfile.compat_tck(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def _context(actor_type: ActorType = ActorType.HUMAN) -> OperationContext:
|
||||
return OperationContext.create(
|
||||
Actor.create(actor_type, actor_id=f"fixture-{actor_type.value}"),
|
||||
correlation_id="corr-cmis-fixture-integration",
|
||||
)
|
||||
|
||||
|
||||
def test_fixture_profiles_match_implemented_access_profiles() -> None:
|
||||
catalog = _catalog()
|
||||
implemented = _profiles()
|
||||
|
||||
assert set(catalog["profiles"]) == set(implemented)
|
||||
for profile_name, fixture in catalog["profiles"].items():
|
||||
profile = implemented[profile_name]
|
||||
assert profile.binding.value == fixture["binding"]
|
||||
assert profile.allow_mutations is fixture["mutations"]
|
||||
assert [item.value for item in profile.visible_sensitivities] == fixture["visibility"]
|
||||
assert [item.value for item in profile.denied_sensitivities] == fixture["deny"]
|
||||
|
||||
|
||||
def test_fixture_mutation_expectations_match_profile_decisions() -> None:
|
||||
catalog = _catalog()
|
||||
implemented = _profiles()
|
||||
|
||||
for profile_name, expectations in catalog["profile_expectations"].items():
|
||||
profile = implemented[profile_name]
|
||||
context = _context(
|
||||
ActorType.SERVICE_ACCOUNT if profile.required_actor_types else ActorType.HUMAN
|
||||
)
|
||||
for action_name in expectations.get("must_authorize_actions", []):
|
||||
assert profile.decide_action(ACTION_MAP[action_name], context).allowed is True
|
||||
for action_name in expectations.get("must_reject_actions", []):
|
||||
assert profile.decide_action(ACTION_MAP[action_name], context).allowed is False
|
||||
|
||||
|
||||
def test_every_fixture_profile_has_a_browser_access_point_shape() -> None:
|
||||
for profile in _profiles().values():
|
||||
access_point = CMISAccessPoint(
|
||||
access_point_id=profile.name,
|
||||
repository_id=f"kontextual-{profile.name}",
|
||||
profile=profile,
|
||||
base_path=f"/cmis/{profile.name}/browser",
|
||||
)
|
||||
serialized = access_point.to_dict()
|
||||
|
||||
assert serialized["base_path"] == f"/cmis/{profile.name}/browser"
|
||||
assert serialized["repository_id"] == f"kontextual-{profile.name}"
|
||||
assert serialized["profile"]["binding"] == "browser"
|
||||
|
||||
|
||||
def test_optional_opencmis_tck_map_covers_fixture_capability_groups() -> None:
|
||||
catalog = _catalog()
|
||||
tck_map = _tck_map()
|
||||
fixture_groups = {group["id"] for group in catalog["capability_groups"]}
|
||||
mapped_groups = {group["capability_group"] for group in tck_map["groups"]}
|
||||
|
||||
assert tck_map["tck"]["execution"] == "optional"
|
||||
assert tck_map["target"]["profile"] == "compat-tck"
|
||||
assert mapped_groups == fixture_groups
|
||||
assert all(group["tck_groups"] for group in tck_map["groups"])
|
||||
|
||||
|
||||
def test_optional_tck_result_template_can_capture_gap_mapping() -> None:
|
||||
template = json.loads(TCK_RESULT_TEMPLATE.read_text(encoding="utf-8"))
|
||||
|
||||
assert template["run"]["profile"] == "compat-tck"
|
||||
assert template["summary"] == {"passed": 0, "failed": 0, "skipped": 0, "known_gap": 0}
|
||||
assert "capability_gaps" in template
|
||||
assert template["groups"][0]["status"] == "not_run"
|
||||
|
||||
@@ -46,6 +46,8 @@ suite.
|
||||
- `tests/cmis/test_cmis_domain_mapper.py`
|
||||
- `tests/cmis/test_cmis_runtime_browser_binding.py`
|
||||
- `tests/cmis/test_cmis_browser_binding_api.py`
|
||||
- `tests/cmis/test_cmis_fixture_integration.py`
|
||||
- `tests/cmis/opencmis-tck/tck-subset-map.json`
|
||||
|
||||
## Architecture Constraint
|
||||
|
||||
@@ -141,7 +143,7 @@ Acceptance:
|
||||
|
||||
```task
|
||||
id: KONT-WP-0012-T006
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "2f1e9075-395e-4ed0-9abd-ed7c4ecd774d"
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user