"""Tests for capability-profile-derived-from-extensions (SHARD-WP-0014 T4, E-5).""" import pytest from shard_wiki.engine import ( Extension, ExtensionRuntime, Hook, ProfileContribution, derive_profile, engine_base_profile, ) from shard_wiki.model import ( Addressing, ContentOpacity, NativeQuery, ProfileError, Verb, ) class StructExt(Extension): id = "ext.struct" def hooks(self): return { Hook.ON_PROFILE: lambda payload, ctx: ProfileContribution( verbs_add=frozenset({Verb.STRUCTURED_PAYLOAD}) ) } class AddrExt(Extension): id = "ext.addr" def hooks(self): return { Hook.ON_PROFILE: lambda payload, ctx: ProfileContribution( addressing=Addressing.SPAN, verbs_add=frozenset({Verb.TRANSCLUDE_SOURCE}) ) } class EncryptExt(Extension): id = "ext.encrypt" def hooks(self): return {Hook.ON_PROFILE: lambda p, c: ProfileContribution(content_opacity=ContentOpacity.ENCRYPTED)} class QueryExt(Extension): id = "ext.query" def hooks(self): return {Hook.ON_PROFILE: lambda p, c: ProfileContribution(native_query=NativeQuery.DB_QUERY)} def _active(*exts, ids=None): rt = ExtensionRuntime() for e in exts: rt.register(e) return rt.activate(ids if ids is not None else {e.id for e in exts}) def test_base_profile_is_valid_kernel_minimum(): p = engine_base_profile() assert p.supports(Verb.READ) and p.supports(Verb.WRITE) assert not p.supports(Verb.STRUCTURED_PAYLOAD) assert p.addressing is Addressing.PATH def test_no_extensions_yields_base(): rt = ExtensionRuntime() active = rt.activate(set()) assert derive_profile(active) == engine_base_profile() def test_activating_extension_raises_the_profile(): active = _active(StructExt(), AddrExt()) p = derive_profile(active) assert p.supports(Verb.STRUCTURED_PAYLOAD) # from ext.struct assert p.supports(Verb.TRANSCLUDE_SOURCE) # from ext.addr assert p.addressing is Addressing.SPAN # raised by ext.addr assert p.supports(Verb.READ) # base preserved def test_profile_changes_with_active_set(): only_struct = derive_profile(_active(StructExt(), AddrExt(), ids={"ext.struct"})) both = derive_profile(_active(StructExt(), AddrExt())) assert not only_struct.supports(Verb.TRANSCLUDE_SOURCE) assert both.supports(Verb.TRANSCLUDE_SOURCE) # E-5: profile reflects what's active def test_composition_cannot_yield_an_impossible_profile(): # encrypted opacity + native query violates ยง6.5 implication rules -> derive must reject. active = _active(EncryptExt(), QueryExt()) with pytest.raises(ProfileError): derive_profile(active)