Add tests for admin system
This commit is contained in:
parent
fea7430304
commit
9eaca966c8
1 changed files with 332 additions and 0 deletions
332
tests/test_admin.py
Normal file
332
tests/test_admin.py
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
"""Tests for the admin system."""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from mudlib.commands.build import cmd_demote, cmd_promote
|
||||
from mudlib.player import Player, players
|
||||
from mudlib.store import (
|
||||
create_account,
|
||||
init_db,
|
||||
load_player_data,
|
||||
set_admin,
|
||||
)
|
||||
from mudlib.zone import Zone
|
||||
from mudlib.zones import register_zone, zone_registry
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_state():
|
||||
players.clear()
|
||||
zone_registry.clear()
|
||||
yield
|
||||
players.clear()
|
||||
zone_registry.clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db(tmp_path):
|
||||
init_db(tmp_path / "test.db")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def zone():
|
||||
terrain = [["." for _ in range(10)] for _ in range(10)]
|
||||
z = Zone(name="testzone", width=10, height=10, terrain=terrain)
|
||||
register_zone("testzone", z)
|
||||
return z
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_writer():
|
||||
writer = MagicMock()
|
||||
writer.write = MagicMock()
|
||||
writer.drain = AsyncMock()
|
||||
return writer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_reader():
|
||||
return MagicMock()
|
||||
|
||||
|
||||
def make_player(name, zone, mock_writer, mock_reader, is_admin=False):
|
||||
p = Player(
|
||||
name=name,
|
||||
x=5,
|
||||
y=5,
|
||||
writer=mock_writer,
|
||||
reader=mock_reader,
|
||||
location=zone,
|
||||
is_admin=is_admin,
|
||||
)
|
||||
zone._contents.append(p)
|
||||
players[name] = p
|
||||
return p
|
||||
|
||||
|
||||
# Store layer tests
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_first_account_is_admin(db):
|
||||
"""First account created becomes admin."""
|
||||
create_account("first", "password")
|
||||
data = load_player_data("first")
|
||||
assert data is not None
|
||||
assert data["is_admin"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_second_account_not_admin(db):
|
||||
"""Second account created is not admin."""
|
||||
create_account("first", "password")
|
||||
create_account("second", "password")
|
||||
|
||||
first_data = load_player_data("first")
|
||||
second_data = load_player_data("second")
|
||||
|
||||
assert first_data is not None
|
||||
assert first_data["is_admin"] is True
|
||||
assert second_data is not None
|
||||
assert second_data["is_admin"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_admin(db):
|
||||
"""set_admin grants admin status to non-admin account."""
|
||||
create_account("first", "password")
|
||||
create_account("second", "password")
|
||||
|
||||
# Second account should not be admin initially
|
||||
data = load_player_data("second")
|
||||
assert data is not None
|
||||
assert data["is_admin"] is False
|
||||
|
||||
# Promote second account
|
||||
result = set_admin("second", True)
|
||||
assert result is True
|
||||
|
||||
# Verify admin status
|
||||
data = load_player_data("second")
|
||||
assert data is not None
|
||||
assert data["is_admin"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_admin_nonexistent(db):
|
||||
"""set_admin returns False for nonexistent account."""
|
||||
result = set_admin("nobody", True)
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_migration_promotes_earliest(db):
|
||||
"""Migration promotes earliest account when is_admin column added."""
|
||||
# Create two accounts
|
||||
create_account("alice", "password")
|
||||
create_account("bob", "password")
|
||||
|
||||
# Verify first account is admin
|
||||
alice_data = load_player_data("alice")
|
||||
bob_data = load_player_data("bob")
|
||||
|
||||
assert alice_data is not None
|
||||
assert alice_data["is_admin"] is True
|
||||
assert bob_data is not None
|
||||
assert bob_data["is_admin"] is False
|
||||
|
||||
|
||||
# Command tests
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_promote_no_args(zone, mock_writer, mock_reader, db):
|
||||
"""promote with no args shows usage."""
|
||||
player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("builder", "password")
|
||||
|
||||
await cmd_promote(player, "")
|
||||
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "Usage:" in output
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_promote_nonexistent(zone, mock_writer, mock_reader, db):
|
||||
"""promote nonexistent account shows error."""
|
||||
player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("builder", "password")
|
||||
|
||||
await cmd_promote(player, "nobody")
|
||||
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "not found" in output
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_promote_offline_player(zone, mock_writer, mock_reader, db):
|
||||
"""promote offline player updates database."""
|
||||
player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("builder", "password")
|
||||
create_account("target", "password")
|
||||
|
||||
await cmd_promote(player, "target")
|
||||
|
||||
# Verify database updated
|
||||
data = load_player_data("target")
|
||||
assert data is not None
|
||||
assert data["is_admin"] is True
|
||||
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "is now an admin" in output
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_promote_online_player(zone, mock_writer, mock_reader, db):
|
||||
"""promote online player updates both database and live state."""
|
||||
player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("builder", "password")
|
||||
create_account("target", "password")
|
||||
|
||||
# Create target as online player with separate mock_writer
|
||||
target_writer = MagicMock()
|
||||
target_writer.write = MagicMock()
|
||||
target_writer.drain = AsyncMock()
|
||||
target = make_player("target", zone, target_writer, mock_reader, is_admin=False)
|
||||
|
||||
await cmd_promote(player, "target")
|
||||
|
||||
# Verify live state updated
|
||||
assert target.is_admin is True
|
||||
|
||||
# Verify database updated
|
||||
data = load_player_data("target")
|
||||
assert data is not None
|
||||
assert data["is_admin"] is True
|
||||
|
||||
# Verify messages sent
|
||||
builder_output = "".join(
|
||||
call.args[0] for call in player.writer.write.call_args_list
|
||||
)
|
||||
assert "is now an admin" in builder_output
|
||||
|
||||
target_output = "".join(call.args[0] for call in target_writer.write.call_args_list)
|
||||
assert "granted admin status" in target_output
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_demote_self(zone, mock_writer, mock_reader, db):
|
||||
"""demote self shows error."""
|
||||
player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("builder", "password")
|
||||
|
||||
await cmd_demote(player, "builder")
|
||||
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "can't demote yourself" in output
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_demote_online_player(zone, mock_writer, mock_reader, db):
|
||||
"""demote online player updates both database and live state."""
|
||||
player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("builder", "password")
|
||||
create_account("target", "password")
|
||||
set_admin("target", True)
|
||||
|
||||
# Create target as online admin player with separate mock_writer
|
||||
target_writer = MagicMock()
|
||||
target_writer.write = MagicMock()
|
||||
target_writer.drain = AsyncMock()
|
||||
target = make_player("target", zone, target_writer, mock_reader, is_admin=True)
|
||||
|
||||
await cmd_demote(player, "target")
|
||||
|
||||
# Verify live state updated
|
||||
assert target.is_admin is False
|
||||
|
||||
# Verify database updated
|
||||
data = load_player_data("target")
|
||||
assert data is not None
|
||||
assert data["is_admin"] is False
|
||||
|
||||
# Verify messages sent
|
||||
builder_output = "".join(
|
||||
call.args[0] for call in player.writer.write.call_args_list
|
||||
)
|
||||
assert "no longer an admin" in builder_output
|
||||
|
||||
target_output = "".join(call.args[0] for call in target_writer.write.call_args_list)
|
||||
assert "revoked" in target_output
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_demote_no_args(zone, mock_writer, mock_reader, db):
|
||||
"""demote with no args shows usage."""
|
||||
player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("builder", "password")
|
||||
|
||||
await cmd_demote(player, "")
|
||||
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "Usage:" in output
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_demote_offline_player(zone, mock_writer, mock_reader, db):
|
||||
"""demote offline player updates database."""
|
||||
admin_player = make_player("admin", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("admin", "password")
|
||||
create_account("target", "password")
|
||||
set_admin("target", True)
|
||||
|
||||
await cmd_demote(admin_player, "target")
|
||||
|
||||
# Verify database updated
|
||||
data = load_player_data("target")
|
||||
assert data is not None
|
||||
assert data["is_admin"] is False
|
||||
|
||||
output = "".join(call.args[0] for call in admin_player.writer.write.call_args_list)
|
||||
assert "no longer an admin" in output
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_demote_nonexistent(zone, mock_writer, mock_reader, db):
|
||||
"""demote nonexistent account shows error."""
|
||||
player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("builder", "password")
|
||||
|
||||
await cmd_demote(player, "nobody")
|
||||
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "not found" in output
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_promote_already_admin(zone, mock_writer, mock_reader, db):
|
||||
"""promote already-admin account shows message."""
|
||||
player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("builder", "password")
|
||||
create_account("target", "password")
|
||||
set_admin("target", True)
|
||||
|
||||
await cmd_promote(player, "target")
|
||||
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "already an admin" in output
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_demote_not_admin(zone, mock_writer, mock_reader, db):
|
||||
"""demote non-admin account shows message."""
|
||||
player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
|
||||
create_account("builder", "password")
|
||||
create_account("target", "password")
|
||||
|
||||
await cmd_demote(player, "target")
|
||||
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "not an admin" in output
|
||||
Loading…
Reference in a new issue