mud/tests/test_store.py
Jared Miller 485302fab3
Add store module with SQLite account persistence
Implements account management with password hashing (pbkdf2_hmac with SHA256)
and constant-time comparison. Includes player state serialization for position
and inventory persistence across sessions.
2026-02-07 21:42:07 -05:00

179 lines
4.8 KiB
Python

"""Tests for the store (persistence) module."""
import os
import tempfile
from pathlib import Path
import pytest
from mudlib.player import Player
from mudlib.store import (
account_exists,
authenticate,
create_account,
init_db,
load_player_data,
save_player,
)
@pytest.fixture
def temp_db():
"""Create a temporary database for testing."""
with tempfile.NamedTemporaryFile(delete=False, suffix=".db") as f:
db_path = f.name
init_db(db_path)
yield db_path
# Cleanup
os.unlink(db_path)
def test_init_db_creates_file(temp_db):
"""init_db creates the database file."""
assert Path(temp_db).exists()
def test_create_account_success(temp_db):
"""create_account creates a new account."""
assert create_account("Alice", "password123")
assert account_exists("Alice")
def test_create_account_case_insensitive(temp_db):
"""Account names are case-insensitive."""
create_account("Bob", "password123")
assert account_exists("bob")
assert account_exists("BOB")
assert account_exists("Bob")
def test_create_account_duplicate_fails(temp_db):
"""create_account fails if name is already taken."""
assert create_account("Charlie", "password123")
assert not create_account("Charlie", "different_password")
assert not create_account("charlie", "different_password") # case insensitive
def test_authenticate_success(temp_db):
"""authenticate returns True for correct password."""
create_account("Dave", "correct_password")
assert authenticate("Dave", "correct_password")
def test_authenticate_case_insensitive_name(temp_db):
"""authenticate works with case-insensitive names."""
create_account("Eve", "password123")
assert authenticate("eve", "password123")
assert authenticate("EVE", "password123")
def test_authenticate_wrong_password(temp_db):
"""authenticate returns False for wrong password."""
create_account("Frank", "correct_password")
assert not authenticate("Frank", "wrong_password")
def test_authenticate_nonexistent_account(temp_db):
"""authenticate returns False for nonexistent account."""
assert not authenticate("Ghost", "any_password")
def test_save_and_load_player_data(temp_db):
"""save_player and load_player_data persist player state."""
# Create account first
create_account("Grace", "password123")
# Create a player with non-default values
player = Player(
name="Grace",
x=42,
y=17,
pl=85.5,
stamina=60.0,
max_stamina=120.0,
flying=True,
)
# Save and load
save_player(player)
data = load_player_data("Grace")
assert data is not None
assert data["x"] == 42
assert data["y"] == 17
assert data["pl"] == 85.5
assert data["stamina"] == 60.0
assert data["max_stamina"] == 120.0
assert data["flying"] is True
def test_load_player_data_case_insensitive(temp_db):
"""load_player_data works with case-insensitive names."""
create_account("Henry", "password123")
player = Player(name="Henry", x=10, y=20)
save_player(player)
data_lower = load_player_data("henry")
data_upper = load_player_data("HENRY")
assert data_lower is not None
assert data_upper is not None
assert data_lower["x"] == 10
assert data_upper["x"] == 10
def test_load_player_data_nonexistent(temp_db):
"""load_player_data returns None for nonexistent account."""
assert load_player_data("Nobody") is None
def test_save_player_updates_existing(temp_db):
"""save_player updates existing player data."""
create_account("Iris", "password123")
# First save
player = Player(name="Iris", x=10, y=20, pl=100.0)
save_player(player)
# Update and save again
player.x = 50
player.y = 60
player.pl = 75.0
save_player(player)
# Load and verify
data = load_player_data("Iris")
assert data is not None
assert data["x"] == 50
assert data["y"] == 60
assert data["pl"] == 75.0
def test_default_values(temp_db):
"""New accounts have default values."""
create_account("Jack", "password123")
data = load_player_data("Jack")
assert data is not None
assert data["x"] == 0
assert data["y"] == 0
assert data["pl"] == 100.0
assert data["stamina"] == 100.0
assert data["max_stamina"] == 100.0
assert data["flying"] is False
def test_password_hashing_different_salts(temp_db):
"""Different accounts with same password have different hashes."""
create_account("Kate", "same_password")
create_account("Leo", "same_password")
# Both should authenticate
assert authenticate("Kate", "same_password")
assert authenticate("Leo", "same_password")
# This just verifies the API works correctly - we can't easily check
# the hashes are different without exposing internal details