generated from coulomb/repo-seed
Identity = stable shard-scoped handle (not a fingerprint); Placement separate; Span carries a layered provenance delta. Page model (PageShape incl. the four computational shapes). CapabilityProfile with orthogonal-core axes + validate() applying §6.5 implication rules that reject impossible profiles. Imports only provenance/. 8 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
107 lines
3.4 KiB
Python
107 lines
3.4 KiB
Python
"""Tests for the page model + capability profile (SHARD-WP-0007 T2)."""
|
|
|
|
import pytest
|
|
|
|
from shard_wiki.model import (
|
|
AccessGrant,
|
|
Addressing,
|
|
AttachmentMode,
|
|
CapabilityProfile,
|
|
ContentOpacity,
|
|
History,
|
|
Identity,
|
|
MergeModel,
|
|
NativeQuery,
|
|
OperationalEnvelope,
|
|
Page,
|
|
PageShape,
|
|
ProfileError,
|
|
Substrate,
|
|
Translation,
|
|
Verb,
|
|
WriteGranularity,
|
|
)
|
|
from shard_wiki.provenance import Liveness, ProvenanceEnvelope
|
|
|
|
|
|
def _read_only_folder_profile(**over) -> CapabilityProfile:
|
|
base = dict(
|
|
substrate=Substrate.FILES,
|
|
attachment_mode=AttachmentMode.FILE_STORE,
|
|
write_granularity=WriteGranularity.NONE,
|
|
content_opacity=ContentOpacity.TRANSPARENT,
|
|
operational_envelope=OperationalEnvelope.LOCAL_UNBOUNDED,
|
|
access_grant=AccessGrant.OPEN,
|
|
liveness=Liveness.STATIC,
|
|
history=History.NONE,
|
|
merge_model=MergeModel.NONE,
|
|
addressing=Addressing.PATH,
|
|
native_query=NativeQuery.NONE,
|
|
translation=Translation.NATIVE,
|
|
supported_verbs=frozenset({Verb.READ}),
|
|
)
|
|
base.update(over)
|
|
return CapabilityProfile(**base)
|
|
|
|
|
|
def test_identity_is_stable_across_content_and_value_equal():
|
|
a = Identity("shardA", "Home")
|
|
b = Identity("shardA", "Home")
|
|
assert a == b and hash(a) == hash(b)
|
|
assert str(a) == "shardA:Home"
|
|
# Identity does not depend on content: a Page edit reuses the same identity.
|
|
env = ProvenanceEnvelope(source_shard="shardA")
|
|
p1 = Page(a, "first body", env)
|
|
p2 = Page(a, "edited body", env)
|
|
assert p1.identity == p2.identity # stable handle, not a fingerprint
|
|
|
|
|
|
def test_read_only_folder_profile_validates():
|
|
assert _read_only_folder_profile().validate().supports(Verb.READ)
|
|
|
|
|
|
def test_missing_read_is_rejected():
|
|
with pytest.raises(ProfileError, match="READ"):
|
|
_read_only_folder_profile(supported_verbs=frozenset()).validate()
|
|
|
|
|
|
def test_git_is_store_requires_git_substrate_and_history():
|
|
with pytest.raises(ProfileError, match="git-is-store"):
|
|
_read_only_folder_profile(attachment_mode=AttachmentMode.GIT_IS_STORE).validate()
|
|
# consistent git-is-store profile validates
|
|
_read_only_folder_profile(
|
|
attachment_mode=AttachmentMode.GIT_IS_STORE,
|
|
substrate=Substrate.GIT,
|
|
history=History.GIT_NATIVE,
|
|
).validate()
|
|
|
|
|
|
def test_encrypted_forbids_native_query():
|
|
with pytest.raises(ProfileError, match="encrypted"):
|
|
_read_only_folder_profile(
|
|
content_opacity=ContentOpacity.ENCRYPTED, native_query=NativeQuery.DB_QUERY
|
|
).validate()
|
|
|
|
|
|
def test_write_verb_requires_write_granularity():
|
|
with pytest.raises(ProfileError, match="read-only consistency"):
|
|
_read_only_folder_profile(supported_verbs=frozenset({Verb.READ, Verb.WRITE})).validate()
|
|
# writable profile is consistent
|
|
_read_only_folder_profile(
|
|
write_granularity=WriteGranularity.PER_PAGE,
|
|
supported_verbs=frozenset({Verb.READ, Verb.WRITE}),
|
|
).validate()
|
|
|
|
|
|
def test_native_crdt_merge_requires_crdt_log_and_realtime():
|
|
with pytest.raises(ProfileError, match="native-crdt"):
|
|
_read_only_folder_profile(
|
|
merge_model=MergeModel.NATIVE_CRDT,
|
|
supported_verbs=frozenset({Verb.READ, Verb.MERGE}),
|
|
).validate()
|
|
|
|
|
|
def test_page_defaults_to_prose():
|
|
p = Page(Identity("s", "k"), "x", ProvenanceEnvelope(source_shard="s"))
|
|
assert p.shape is PageShape.PROSE
|