refactor: complete server-first item schema wiring and plugin contract checks
This commit is contained in:
32
server/tests/test_item_plugin_contract.py
Normal file
32
server/tests/test_item_plugin_contract.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from app.items.registry import ITEM_PLUGINS
|
||||
|
||||
|
||||
def test_item_plugins_expose_expected_contract() -> None:
|
||||
for plugin in ITEM_PLUGINS:
|
||||
module = plugin.module
|
||||
assert isinstance(plugin.type, str) and plugin.type
|
||||
assert isinstance(plugin.order, int)
|
||||
assert callable(getattr(module, "validate_update", None))
|
||||
assert callable(getattr(module, "use_item", None))
|
||||
assert isinstance(getattr(module, "LABEL", ""), str)
|
||||
assert isinstance(getattr(module, "TOOLTIP", ""), str)
|
||||
assert isinstance(getattr(module, "EDITABLE_PROPERTIES", ()), tuple)
|
||||
assert isinstance(getattr(module, "CAPABILITIES", ()), tuple)
|
||||
assert isinstance(getattr(module, "DEFAULT_PARAMS", {}), dict)
|
||||
assert isinstance(getattr(module, "PROPERTY_METADATA", {}), dict)
|
||||
|
||||
|
||||
def test_item_plugin_folders_have_required_files() -> None:
|
||||
base_dir = Path(__file__).resolve().parents[1] / "app" / "items" / "types"
|
||||
for plugin in ITEM_PLUGINS:
|
||||
type_dir = base_dir / plugin.type
|
||||
assert type_dir.is_dir()
|
||||
assert (type_dir / "definition.py").is_file()
|
||||
assert (type_dir / "validator.py").is_file()
|
||||
assert (type_dir / "actions.py").is_file()
|
||||
assert (type_dir / "module.py").is_file()
|
||||
assert (type_dir / "plugin.py").is_file()
|
||||
@@ -33,15 +33,16 @@ def test_ui_definitions_are_complete_for_all_item_types() -> None:
|
||||
assert isinstance(entry.get("type"), str)
|
||||
assert isinstance(entry.get("label"), str)
|
||||
assert isinstance(entry.get("editableProperties"), list)
|
||||
assert isinstance(entry.get("capabilities"), list)
|
||||
assert isinstance(entry.get("propertyMetadata"), dict)
|
||||
assert isinstance(entry.get("propertyOptions"), dict)
|
||||
assert isinstance(entry.get("globalProperties"), dict)
|
||||
|
||||
editable_properties = entry["editableProperties"]
|
||||
capabilities = entry["capabilities"]
|
||||
property_metadata = entry["propertyMetadata"]
|
||||
property_options = entry["propertyOptions"]
|
||||
global_properties = entry["globalProperties"]
|
||||
|
||||
assert capabilities
|
||||
assert required_global_keys.issubset(set(global_properties.keys()))
|
||||
for property_key in editable_properties:
|
||||
if property_key == "title":
|
||||
@@ -50,7 +51,7 @@ def test_ui_definitions_are_complete_for_all_item_types() -> None:
|
||||
metadata = property_metadata[property_key]
|
||||
assert isinstance(metadata, dict)
|
||||
if metadata.get("valueType") == "list":
|
||||
options = property_options.get(property_key)
|
||||
options = metadata.get("options")
|
||||
assert isinstance(options, list)
|
||||
assert options
|
||||
|
||||
|
||||
@@ -83,3 +83,24 @@ async def test_broadcast_fanout_is_concurrent(monkeypatch: pytest.MonkeyPatch) -
|
||||
assert ws1 in send_started_at
|
||||
assert ws2 in send_started_at
|
||||
assert abs(send_started_at[ws1] - send_started_at[ws2]) < 0.02
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_item_add_rejects_unknown_type(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
server = SignalingServer("127.0.0.1", 8765, None, None)
|
||||
ws = _fake_ws()
|
||||
client = ClientConnection(websocket=ws, id="u1", nickname="tester", x=5, y=6)
|
||||
server.clients[ws] = client
|
||||
|
||||
send_payloads: list[object] = []
|
||||
|
||||
async def fake_send(websocket: ServerConnection, packet: object) -> None:
|
||||
send_payloads.append(packet)
|
||||
|
||||
monkeypatch.setattr(server, "_send", fake_send)
|
||||
|
||||
await server._handle_message(client, json.dumps({"type": "item_add", "itemType": "not_a_type"}))
|
||||
|
||||
assert send_payloads
|
||||
assert send_payloads[-1].ok is False
|
||||
assert "unknown item type" in send_payloads[-1].message.lower()
|
||||
|
||||
Reference in New Issue
Block a user