Initial commit
This commit is contained in:
0
server/tests/__init__.py
Normal file
0
server/tests/__init__.py
Normal file
24
server/tests/test_config.py
Normal file
24
server/tests/test_config.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.config import load_config
|
||||
|
||||
|
||||
def test_load_config_defaults_when_path_none() -> None:
|
||||
cfg = load_config(None)
|
||||
assert cfg.server.bind_ip == "127.0.0.1"
|
||||
assert cfg.network.allow_insecure_ws is True
|
||||
assert cfg.storage.state_file == "runtime/items.json"
|
||||
|
||||
|
||||
def test_load_config_requires_tls_when_insecure_disabled(tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "config.toml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
[network]
|
||||
allow_insecure_ws = false
|
||||
""".strip()
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
load_config(config_path)
|
||||
35
server/tests/test_item_persistence.py
Normal file
35
server/tests/test_item_persistence.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
from websockets.asyncio.server import ServerConnection
|
||||
|
||||
from app.client import ClientConnection
|
||||
from app.item_service import ItemService
|
||||
|
||||
|
||||
def _fake_ws() -> ServerConnection:
|
||||
return cast(ServerConnection, object())
|
||||
|
||||
|
||||
def test_item_persistence_omits_global_type_properties(tmp_path: Path) -> None:
|
||||
state_file = tmp_path / "items.json"
|
||||
service = ItemService(state_file=state_file)
|
||||
client = ClientConnection(websocket=_fake_ws(), id="u1", x=3, y=4)
|
||||
|
||||
item = service.default_item(client, "dice")
|
||||
service.add_item(item)
|
||||
service.save_state()
|
||||
|
||||
saved = json.loads(state_file.read_text(encoding="utf-8"))
|
||||
assert isinstance(saved, list)
|
||||
assert len(saved) == 1
|
||||
assert "capabilities" not in saved[0]
|
||||
assert "useSound" not in saved[0]
|
||||
|
||||
reloaded = ItemService(state_file=state_file)
|
||||
loaded_item = reloaded.items[item.id]
|
||||
assert loaded_item.useSound == "sounds/roll.ogg"
|
||||
assert "usable" in loaded_item.capabilities
|
||||
18
server/tests/test_models.py
Normal file
18
server/tests/test_models.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from pydantic import ValidationError, TypeAdapter
|
||||
|
||||
from app.models import ClientPacket
|
||||
|
||||
|
||||
def test_update_position_validates() -> None:
|
||||
adapter = TypeAdapter(ClientPacket)
|
||||
packet = adapter.validate_python({"type": "update_position", "x": 10, "y": 12})
|
||||
assert packet.type == "update_position"
|
||||
|
||||
|
||||
def test_unknown_type_rejected() -> None:
|
||||
adapter = TypeAdapter(ClientPacket)
|
||||
try:
|
||||
adapter.validate_python({"type": "unknown"})
|
||||
except ValidationError:
|
||||
return
|
||||
assert False, "validation should fail"
|
||||
28
server/tests/test_nickname_uniqueness.py
Normal file
28
server/tests/test_nickname_uniqueness.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import cast
|
||||
|
||||
from websockets.asyncio.server import ServerConnection
|
||||
|
||||
from app.server import ClientConnection, SignalingServer
|
||||
|
||||
|
||||
def _fake_ws() -> ServerConnection:
|
||||
return cast(ServerConnection, object())
|
||||
|
||||
|
||||
def test_nickname_taken_is_case_insensitive() -> None:
|
||||
server = SignalingServer("127.0.0.1", 8765, None, None)
|
||||
first_ws = _fake_ws()
|
||||
second_ws = _fake_ws()
|
||||
server.clients[first_ws] = ClientConnection(websocket=first_ws, id="1", nickname="Jage")
|
||||
server.clients[second_ws] = ClientConnection(websocket=second_ws, id="2", nickname="Alice")
|
||||
|
||||
assert server._is_nickname_taken("jage", exclude_client_id="2")
|
||||
assert server._is_nickname_taken("JAGE", exclude_client_id="2")
|
||||
assert not server._is_nickname_taken("jage", exclude_client_id="1")
|
||||
|
||||
|
||||
def test_nickname_key_uses_casefold() -> None:
|
||||
server = SignalingServer("127.0.0.1", 8765, None, None)
|
||||
assert server._nickname_key("Jage") == server._nickname_key("jage")
|
||||
73
server/tests/test_nickname_updates.py
Normal file
73
server/tests/test_nickname_updates.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from websockets.asyncio.server import ServerConnection
|
||||
|
||||
from app.models import BroadcastChatMessagePacket, BroadcastNicknamePacket, NicknameResultPacket
|
||||
from app.server import ClientConnection, SignalingServer
|
||||
|
||||
|
||||
def _fake_ws() -> ServerConnection:
|
||||
return cast(ServerConnection, object())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_same_nickname_same_case_is_noop(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
server = SignalingServer("127.0.0.1", 8765, None, None)
|
||||
ws = _fake_ws()
|
||||
client = ClientConnection(websocket=ws, id="1", nickname="Jage")
|
||||
server.clients[ws] = client
|
||||
|
||||
sent_packets: list[object] = []
|
||||
broadcast_packets: list[object] = []
|
||||
|
||||
async def fake_send(websocket: ServerConnection, packet: object) -> None:
|
||||
sent_packets.append(packet)
|
||||
|
||||
async def fake_broadcast(packet: object, exclude: ServerConnection | None = None) -> None:
|
||||
broadcast_packets.append(packet)
|
||||
|
||||
monkeypatch.setattr(server, "_send", fake_send)
|
||||
monkeypatch.setattr(server, "_broadcast", fake_broadcast)
|
||||
|
||||
await server._handle_message(client, json.dumps({"type": "update_nickname", "nickname": "Jage"}))
|
||||
|
||||
assert client.nickname == "Jage"
|
||||
assert broadcast_packets == []
|
||||
assert any(
|
||||
isinstance(packet, NicknameResultPacket) and packet.accepted and packet.effectiveNickname == "Jage"
|
||||
for packet in sent_packets
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_case_only_change_is_allowed_and_broadcast(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
server = SignalingServer("127.0.0.1", 8765, None, None)
|
||||
ws = _fake_ws()
|
||||
client = ClientConnection(websocket=ws, id="1", nickname="jage")
|
||||
server.clients[ws] = client
|
||||
|
||||
sent_packets: list[object] = []
|
||||
broadcast_packets: list[object] = []
|
||||
|
||||
async def fake_send(websocket: ServerConnection, packet: object) -> None:
|
||||
sent_packets.append(packet)
|
||||
|
||||
async def fake_broadcast(packet: object, exclude: ServerConnection | None = None) -> None:
|
||||
broadcast_packets.append(packet)
|
||||
|
||||
monkeypatch.setattr(server, "_send", fake_send)
|
||||
monkeypatch.setattr(server, "_broadcast", fake_broadcast)
|
||||
|
||||
await server._handle_message(client, json.dumps({"type": "update_nickname", "nickname": "Jage"}))
|
||||
|
||||
assert client.nickname == "Jage"
|
||||
assert any(
|
||||
isinstance(packet, NicknameResultPacket) and packet.accepted and packet.effectiveNickname == "Jage"
|
||||
for packet in sent_packets
|
||||
)
|
||||
assert any(isinstance(packet, BroadcastNicknamePacket) for packet in broadcast_packets)
|
||||
assert any(isinstance(packet, BroadcastChatMessagePacket) for packet in broadcast_packets)
|
||||
Reference in New Issue
Block a user