"""Tests for the plugin contract registry and contract modules. Verifies that: 1. ContractRegistry singleton works correctly 2. Contracts for kommunikation, ai_assistant, and mail register and resolve 3. Contract objects expose the expected public symbols 4. Lazy loading works for unregistered plugins 5. ContractError is raised for missing contracts via require_contract """ from __future__ import annotations import importlib import pytest from app.plugins.builtins.contracts import ( ContractError, ContractRegistry, get_contract, get_contract_registry, reset_contract_registry_for_testing, ) def _reload_contracts(plugin_name: str): """Force re-import of a plugin's contracts module so it re-registers.""" module_path = f"app.plugins.builtins.{plugin_name}.contracts" mod = importlib.import_module(module_path) importlib.reload(mod) return mod @pytest.fixture(autouse=True) def _reset_registry(): """Ensure a fresh registry for each test.""" reset_contract_registry_for_testing() yield reset_contract_registry_for_testing() # ─── ContractRegistry singleton ─── class TestContractRegistry: def test_singleton_identity(self): """get_contract_registry returns the same instance.""" a = get_contract_registry() b = get_contract_registry() assert a is b def test_register_and_get(self): """register stores and get_contract retrieves.""" reg = get_contract_registry() sentinel = object() reg.register("demo", sentinel) assert reg.get_contract("demo") is sentinel def test_unregister(self): """unregister removes the contract.""" reg = get_contract_registry() sentinel = object() reg.register("demo", sentinel) reg.unregister("demo") assert reg.get_contract("demo") is None def test_get_contract_returns_none_for_unknown(self): """Unknown plugin returns None, not raises.""" reg = get_contract_registry() assert reg.get_contract("does_not_exist") is None def test_require_contract_raises_for_missing(self): """require_contract raises ContractError when missing.""" reg = get_contract_registry() with pytest.raises(ContractError): reg.require_contract("does_not_exist") def test_require_contract_returns_contract(self): """require_contract returns the contract when registered.""" reg = get_contract_registry() sentinel = object() reg.register("demo", sentinel) assert reg.require_contract("demo") is sentinel def test_list_available(self): """list_available returns sorted plugin names.""" reg = get_contract_registry() reg.register("zebra", object()) reg.register("alpha", object()) assert reg.list_available() == ["alpha", "zebra"] def test_module_level_get_contract(self): """Module-level get_contract function works.""" reg = get_contract_registry() sentinel = object() reg.register("demo", sentinel) assert get_contract("demo") is sentinel def test_reset_for_testing_clears_state(self): """reset clears all registered contracts.""" reg = get_contract_registry() reg.register("a", object()) reg.register("b", object()) assert len(reg.list_available()) == 2 reset_contract_registry_for_testing() assert reg.list_available() == [] # ─── Kommunikation contract ─── class TestKommunikationContract: @pytest.fixture(autouse=True) def _load_komm(self): """Reload kommunikation contracts so it re-registers after reset.""" _reload_contracts("kommunikation") def test_contract_registers(self): """Importing kommunikation.contracts registers it in the registry.""" contract = get_contract("kommunikation") assert contract is not None assert contract.contract_name == "kommunikation" def test_exposes_services(self): """Contract exposes service functions.""" contract = get_contract("kommunikation") assert callable(contract.parse_mentions) assert callable(contract.get_conversation) assert callable(contract.get_messages) assert callable(contract.send_message) assert callable(contract.create_plugin_room) def test_exposes_participant_registry(self): """Contract exposes participant registry types.""" contract = get_contract("kommunikation") assert callable(contract.get_participant_registry) assert contract.ParticipantHandler is not None def test_exposes_miniapp_registry(self): """Contract exposes MiniAppRegistry.""" contract = get_contract("kommunikation") assert contract.MiniAppRegistry is not None assert contract.MiniAppDef is not None def test_exposes_models(self): """Contract exposes ORM models.""" contract = get_contract("kommunikation") assert contract.CommConversation is not None assert contract.CommMessage is not None assert contract.CommParticipant is not None def test_parse_mentions_works(self): """parse_mentions actually parses @mentions.""" contract = get_contract("kommunikation") result = contract.parse_mentions("hello @ai_proactive and @system") assert result == ["ai_proactive", "system"] # ─── AI Assistant contract ─── class TestAIAssistantContract: @pytest.fixture(autouse=True) def _load_ai(self): """Reload ai_assistant contracts so it re-registers after reset.""" _reload_contracts("ai_assistant") def test_contract_registers(self): """Importing ai_assistant.contracts registers it.""" contract = get_contract("ai_assistant") assert contract is not None assert contract.contract_name == "ai_assistant" def test_exposes_tool_registry(self): """Contract exposes tool registry functions and types.""" contract = get_contract("ai_assistant") assert callable(contract.get_tool_registry) assert contract.ToolRegistry is not None assert contract.AITool is not None def test_exposes_get_default_provider(self): """Contract exposes get_default_provider.""" contract = get_contract("ai_assistant") assert callable(contract.get_default_provider) def test_tool_registry_singleton_works(self): """get_tool_registry returns a working singleton.""" contract = get_contract("ai_assistant") reg = contract.get_tool_registry() assert reg is not None reg2 = contract.get_tool_registry() assert reg is reg2 # ─── Mail contract ─── class TestMailContract: @pytest.fixture(autouse=True) def _load_mail(self): """Reload mail contracts so it re-registers after reset.""" _reload_contracts("mail") def test_contract_registers(self): """Importing mail.contracts registers it.""" contract = get_contract("mail") assert contract is not None assert contract.contract_name == "mail" def test_exposes_mail_model(self): """Contract exposes the Mail ORM model.""" contract = get_contract("mail") assert contract.Mail is not None from app.plugins.builtins.mail.models import Mail as MailModel assert contract.Mail is MailModel # ─── Lazy loading ─── class TestLazyLoading: def test_lazy_load_on_first_access(self): """get_contract triggers lazy load of contracts module.""" reg = get_contract_registry() contract = reg.get_contract("kommunikation") assert contract is not None assert contract.contract_name == "kommunikation" def test_lazy_load_missing_plugin_returns_none(self): """Lazy load of non-existent plugin returns None.""" reg = get_contract_registry() assert reg.get_contract("nonexistent_plugin_xyz") is None