generated from coulomb/repo-seed
CMIS layer into an honest CMIS 1.1
This commit is contained in:
@@ -20,7 +20,8 @@
|
||||
{
|
||||
"capability_group": "navigation",
|
||||
"tck_groups": ["NavigationTestGroup"],
|
||||
"expected": "selected-pass"
|
||||
"expected": "partial-pass",
|
||||
"known_gaps": ["get_descendants", "get_folder_tree", "multifiling", "unfiling"]
|
||||
},
|
||||
{
|
||||
"capability_group": "object-content",
|
||||
@@ -30,14 +31,14 @@
|
||||
{
|
||||
"capability_group": "versioning",
|
||||
"tck_groups": ["VersioningTestGroup"],
|
||||
"expected": "partial-pass",
|
||||
"known_gaps": ["private_working_copy"]
|
||||
"expected": "skip",
|
||||
"known_gaps": ["versioning_services", "private_working_copy", "all_versions_search"]
|
||||
},
|
||||
{
|
||||
"capability_group": "discovery-query",
|
||||
"tck_groups": ["QueryTestGroup"],
|
||||
"expected": "partial-pass",
|
||||
"known_gaps": ["full_cmis_sql_joins"]
|
||||
"known_gaps": ["full_cmis_sql_joins", "order_by"]
|
||||
},
|
||||
{
|
||||
"capability_group": "relationships",
|
||||
@@ -48,7 +49,7 @@
|
||||
"capability_group": "acl-policy",
|
||||
"tck_groups": ["AclTestGroup", "PolicyTestGroup"],
|
||||
"expected": "partial-pass",
|
||||
"known_gaps": ["apply_policy", "remove_policy"]
|
||||
"known_gaps": ["apply_acl", "apply_policy", "remove_policy"]
|
||||
},
|
||||
{
|
||||
"capability_group": "change-log",
|
||||
@@ -63,4 +64,3 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ def test_governed_authoring_profile_allows_selected_write_actions() -> None:
|
||||
assert profile.decide_action(CMISAction.CREATE_DOCUMENT, context).allowed is True
|
||||
assert profile.decide_action(CMISAction.SET_CONTENT_STREAM, context).allowed is True
|
||||
assert profile.decide_action(CMISAction.BULK_UPDATE_PROPERTIES, context).allowed is False
|
||||
assert profile.decide_action(CMISAction.APPLY_ACL, context).reason == "cmis_operation_not_implemented"
|
||||
|
||||
|
||||
def test_profiles_hide_denied_sensitivities_without_partial_exposure() -> None:
|
||||
@@ -148,4 +149,3 @@ def test_disabled_access_point_denies_all_actions_and_visibility() -> None:
|
||||
|
||||
assert access_point.decide_action(CMISAction.GET_OBJECT, context).reason == "cmis_access_point_disabled"
|
||||
assert access_point.exposes_asset(_asset("asset-public", "public"), context) is False
|
||||
|
||||
|
||||
@@ -100,6 +100,8 @@ def test_cmis_repository_info_and_type_definitions(cmis_client) -> None:
|
||||
assert repository["repository_id"] == "kontextual-readonly-browser"
|
||||
assert repository["cmis_version_supported"] == "1.1"
|
||||
assert repository["capabilities"]["capability_query"] == "metadataonly"
|
||||
assert repository["capabilities"]["capability_get_descendants"] is False
|
||||
assert repository["unsupported_features"]["multifiling"]["status"] == "projection_only"
|
||||
assert {item["base_type_id"] for item in types["items"]} >= {
|
||||
"cmis:document",
|
||||
"cmis:folder",
|
||||
@@ -208,3 +210,14 @@ def test_cmis_readonly_route_rejects_mutation(cmis_client) -> None:
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_cmis_rejects_unsupported_standard_property_update_with_diagnostics(cmis_client) -> None:
|
||||
response = cmis_client.post(
|
||||
"/cmis/governed-authoring/browser/object/cmis:asset:asset-source/properties",
|
||||
json={"properties": {"cmis:name": "Renamed"}},
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
assert response.json()["detail"]["details"]["property"] == "cmis:name"
|
||||
assert response.json()["detail"]["details"]["supported"] == ["kontextual:metadata:<key>"]
|
||||
|
||||
145
tests/cmis/test_cmis_compliance_flags.py
Normal file
145
tests/cmis/test_cmis_compliance_flags.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from kontextual_engine import (
|
||||
Actor,
|
||||
ActorType,
|
||||
CMISAccessPoint,
|
||||
CMISAccessProfile,
|
||||
CMISAction,
|
||||
CMISBaseType,
|
||||
CMISCapability,
|
||||
CMISDomainMapper,
|
||||
Classification,
|
||||
KnowledgeAsset,
|
||||
LifecycleState,
|
||||
OperationContext,
|
||||
)
|
||||
|
||||
|
||||
EXPECTED_CAPABILITY_KEYS = {
|
||||
"capability_content_stream_updatability",
|
||||
"capability_changes",
|
||||
"capability_renditions",
|
||||
"capability_get_descendants",
|
||||
"capability_get_folder_tree",
|
||||
"capability_order_by",
|
||||
"capability_multifiling",
|
||||
"capability_unfiling",
|
||||
"capability_version_specific_filing",
|
||||
"capability_pwc_searchable",
|
||||
"capability_pwc_updatable",
|
||||
"capability_all_versions_searchable",
|
||||
"capability_query",
|
||||
"capability_join",
|
||||
"capability_acl",
|
||||
"capability_new_type_settable_attributes",
|
||||
}
|
||||
|
||||
|
||||
def _context() -> OperationContext:
|
||||
return OperationContext.create(
|
||||
Actor.create(ActorType.HUMAN, actor_id="actor-cmis-compliance"),
|
||||
correlation_id="corr-cmis-compliance",
|
||||
)
|
||||
|
||||
|
||||
def _mapper(profile: CMISAccessProfile | None = None) -> CMISDomainMapper:
|
||||
return CMISDomainMapper(
|
||||
CMISAccessPoint(
|
||||
access_point_id="cmis-compliance",
|
||||
repository_id="kontextual-compliance",
|
||||
profile=profile or CMISAccessProfile.readonly_browser(),
|
||||
base_path="/cmis/compliance/browser",
|
||||
metadata={"repository_name": "Kontextual Compliance Repository"},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_repository_info_uses_complete_conservative_cmis_11_capability_flags() -> None:
|
||||
repository = _mapper().repository_info()
|
||||
capabilities = repository["capabilities"]
|
||||
|
||||
assert EXPECTED_CAPABILITY_KEYS <= set(capabilities)
|
||||
assert capabilities["capability_content_stream_updatability"] == "none"
|
||||
assert capabilities["capability_changes"] == "objectidsonly"
|
||||
assert capabilities["capability_renditions"] == "none"
|
||||
assert capabilities["capability_get_descendants"] is False
|
||||
assert capabilities["capability_get_folder_tree"] is False
|
||||
assert capabilities["capability_order_by"] == "none"
|
||||
assert capabilities["capability_multifiling"] is False
|
||||
assert capabilities["capability_unfiling"] is False
|
||||
assert capabilities["capability_version_specific_filing"] is False
|
||||
assert capabilities["capability_pwc_searchable"] is False
|
||||
assert capabilities["capability_pwc_updatable"] is False
|
||||
assert capabilities["capability_all_versions_searchable"] is False
|
||||
assert capabilities["capability_query"] == "metadataonly"
|
||||
assert capabilities["capability_join"] == "none"
|
||||
assert capabilities["capability_acl"] == "discover"
|
||||
assert set(capabilities["capability_new_type_settable_attributes"].values()) == {False}
|
||||
|
||||
|
||||
def test_authoring_profile_only_advertises_supported_content_write_semantics() -> None:
|
||||
repository = _mapper(CMISAccessProfile.governed_authoring()).repository_info()
|
||||
capabilities = repository["capabilities"]
|
||||
|
||||
assert capabilities["capability_content_stream_updatability"] == "anytime"
|
||||
assert capabilities["capability_multifiling"] is False
|
||||
assert capabilities["capability_renditions"] == "none"
|
||||
|
||||
|
||||
def test_repository_info_exposes_nonstandard_projection_feature_and_unsupported_catalog() -> None:
|
||||
repository = _mapper().repository_info()
|
||||
feature_ids = {feature["id"] for feature in repository["repository_features"]}
|
||||
unsupported = repository["unsupported_features"]
|
||||
|
||||
assert "urn:kontextual:cmis:feature:projection-parentage" in feature_ids
|
||||
assert unsupported["multifiling"]["status"] == "projection_only"
|
||||
assert unsupported["multifiling"]["standard_flag"] == "capability_multifiling"
|
||||
assert unsupported["get_descendants"]["standard_flag"] == "capability_get_descendants"
|
||||
assert unsupported["rendition_streams"]["standard_flag"] == "capability_renditions"
|
||||
assert repository["compliance"]["posture"] == "declared-browser-binding-subset"
|
||||
|
||||
|
||||
def test_type_definitions_do_not_claim_unimplemented_cmis_services() -> None:
|
||||
types = {definition["base_type_id"]: definition for definition in _mapper().type_definitions()}
|
||||
document = types[CMISBaseType.DOCUMENT.value]
|
||||
folder = types[CMISBaseType.FOLDER.value]
|
||||
relationship = types[CMISBaseType.RELATIONSHIP.value]
|
||||
policy = types[CMISBaseType.POLICY.value]
|
||||
|
||||
assert document["versionable"] is False
|
||||
assert document["controllable_acl"] is True
|
||||
assert document["queryable"] is True
|
||||
assert document["creatable"] is False
|
||||
assert "cmis:contentStreamLength" in document["property_definitions"]
|
||||
assert folder["controllable_acl"] is False
|
||||
assert folder["queryable"] is False
|
||||
assert relationship["creatable"] is False
|
||||
assert relationship["queryable"] is False
|
||||
assert policy["controllable_policy"] is False
|
||||
|
||||
|
||||
def test_profile_denies_unimplemented_mutations_even_when_related_capability_exists() -> None:
|
||||
profile = CMISAccessProfile(
|
||||
name="acl-manager-shape",
|
||||
capabilities=(CMISCapability.ACL,),
|
||||
allow_mutations=True,
|
||||
)
|
||||
decision = profile.decide_action(CMISAction.APPLY_ACL, _context())
|
||||
|
||||
assert decision.allowed is False
|
||||
assert decision.reason == "cmis_operation_not_implemented"
|
||||
|
||||
|
||||
def test_deleted_assets_are_not_exposed_as_live_cmis_objects() -> None:
|
||||
profile = CMISAccessProfile.readonly_browser()
|
||||
asset = KnowledgeAsset.create(
|
||||
"Deleted",
|
||||
Classification(asset_type="document", sensitivity="internal"),
|
||||
asset_id="asset-deleted",
|
||||
).transition_lifecycle(LifecycleState.DELETE_REQUESTED)
|
||||
|
||||
decision = profile.decide_asset_visibility(asset, _context())
|
||||
|
||||
assert decision.allowed is False
|
||||
assert decision.reason == "cmis_lifecycle_not_visible"
|
||||
@@ -73,7 +73,8 @@ def test_mapper_exposes_repository_info_capabilities_and_base_type_definitions()
|
||||
assert repository["cmis_version_supported"] == "1.1"
|
||||
assert repository["binding"] == "browser"
|
||||
assert repository["capabilities"]["capability_query"] == "metadataonly"
|
||||
assert repository["capabilities"]["capability_multifiling"] is True
|
||||
assert repository["capabilities"]["capability_multifiling"] is False
|
||||
assert repository["unsupported_features"]["multifiling"]["status"] == "projection_only"
|
||||
|
||||
assert set(types) == {
|
||||
"cmis:document",
|
||||
@@ -122,6 +123,8 @@ def test_mapper_projects_asset_to_cmis_document_envelope() -> None:
|
||||
assert serialized["properties"]["cmis:objectTypeId"] == "kontextual:document"
|
||||
assert serialized["properties"]["kontextual:metadata:status"] == "accepted"
|
||||
assert serialized["content_stream"]["mime_type"] == "text/markdown"
|
||||
assert serialized["properties"]["cmis:contentStreamMimeType"] == "text/markdown"
|
||||
assert serialized["properties"]["cmis:contentStreamId"] == "repr-source"
|
||||
assert serialized["version"]["cmis:versionLabel"] == "3"
|
||||
assert serialized["relationships"] == ["cmis:relationship:rel-derived"]
|
||||
assert CMISAction.GET_CONTENT_STREAM.value in serialized["allowable_actions"]
|
||||
|
||||
@@ -91,7 +91,9 @@ def test_runtime_cmis_browser_repository_types_children_and_object(cmis_runtime)
|
||||
|
||||
assert access_points["count"] == 4
|
||||
assert repository["repository_id"] == "kontextual-readonly-browser"
|
||||
assert repository["capabilities"]["capability_get_descendants"] is True
|
||||
assert repository["capabilities"]["capability_get_descendants"] is False
|
||||
assert repository["capabilities"]["capability_get_folder_tree"] is False
|
||||
assert repository["unsupported_features"]["get_descendants"]["status"] == "unsupported"
|
||||
assert {item["base_type_id"] for item in types["items"]} >= {"cmis:document", "cmis:folder"}
|
||||
root_paths = {item["path"] for item in children["objects"]}
|
||||
topic_object_ids = {item["object_id"] for item in topic_children["objects"]}
|
||||
@@ -183,6 +185,23 @@ def test_runtime_cmis_governed_authoring_allows_selected_mutations(cmis_runtime)
|
||||
assert stream_bytes.representation.storage_ref.startswith("blob://memory/")
|
||||
assert deleted["deleted"] is False
|
||||
assert deleted["lifecycle"] == "delete_requested"
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
runtime.cmis_object("governed-authoring", "cmis:asset:asset-authored", context)
|
||||
assert "CMIS object not found" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_runtime_cmis_rejects_unsupported_standard_property_updates(cmis_runtime) -> None:
|
||||
runtime, context = cmis_runtime
|
||||
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
runtime.cmis_update_properties(
|
||||
"governed-authoring",
|
||||
"cmis:asset:asset-runtime-source",
|
||||
{"properties": {"cmis:name": "Renamed"}},
|
||||
context,
|
||||
)
|
||||
|
||||
assert "Unsupported CMIS property update" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_runtime_cmis_readonly_profile_rejects_mutations(cmis_runtime) -> None:
|
||||
|
||||
Reference in New Issue
Block a user