code.pablomurad.com

prami-pet

Community virtual pet that lives as a Mastodon account

tests/test_actions.py raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import pytest

from prami import cli, engine, memory
from prami import repository as repo
from prami.actions.registry import REGISTRY, build_registry
from prami.config import Config, config as app_config
from prami.db import session_scope
from prami.parser import parse


# --- Registry: alias resolution -------------------------------------------------

def test_aliases_resolve_to_canonical_names():
    assert REGISTRY.canonical("food") == "feed"
    assert REGISTRY.canonical("pat") == "pet"
    assert REGISTRY.canonical("nick") == "nickname"
    assert REGISTRY.canonical("?") == "help"
    assert REGISTRY.canonical("unmapped") is None


def test_phrase_aliases_resolve():
    phrases = REGISTRY.phrase_map()
    assert phrases.get("call me") == "nickname"
    assert phrases.get("name me") == "nickname"


def test_resolve_falls_back_to_unknown():
    assert REGISTRY.resolve("feed").name == "feed"
    assert REGISTRY.resolve("zzz").name == "unknown"


def test_a_fresh_registry_has_every_command():
    registry = build_registry()
    for name in ("status", "feed", "play", "pet", "clean", "sleep", "wake",
                 "nickname", "teach", "event", "vote", "admin", "help"):
        assert name in registry.names()


# --- Disabled commands ----------------------------------------------------------

def test_teach_is_config_gated():
    cfg = Config()
    cfg.enable_teach_command = False
    assert REGISTRY.is_enabled("teach", cfg) is False
    cfg.enable_teach_command = True
    assert REGISTRY.is_enabled("teach", cfg) is True


def test_disabled_command_does_not_execute(session, pet, user, config):
    config.enable_teach_command = False
    result = engine.handle(session, pet, user, "teach", config, arg="Prami fears printers")
    assert result.accepted is False
    assert memory.by_status(session, "pending") == []


# --- Admin-only actions ---------------------------------------------------------

def test_admin_action_is_admin_only():
    assert REGISTRY.resolve("admin").admin_only is True
    assert REGISTRY.resolve("feed").admin_only is False


def test_nonadmin_cannot_run_admin_action(session, pet, user, config):
    config.admin_accounts = []
    assert engine.handle(session, pet, user, "admin", config, arg="pause") is None


def test_admin_action_runs_for_admin(session, pet, user, config):
    config.admin_accounts = [user.acct]
    result = engine.handle(session, pet, user, "admin", config, arg="status")
    assert result is not None and "paused=" in result.text


# --- CLI and Mastodon share the same core path ----------------------------------

def test_parsed_alias_resolves_to_same_action():
    parsed = parse("<p>@prami food</p>")
    assert REGISTRY.resolve(parsed.name) is REGISTRY.resolve("feed")


def test_engine_dispatches_through_registry(session, pet, user, config, monkeypatch):
    seen = []
    original = REGISTRY.resolve
    monkeypatch.setattr(REGISTRY, "resolve", lambda name: seen.append(name) or original(name))
    engine.handle(session, pet, user, "feed", config)
    assert "feed" in seen


@pytest.fixture
def cli_db(tmp_path, monkeypatch):
    monkeypatch.setattr(app_config, "database_url", f"sqlite:///{tmp_path / 'cli.db'}")
    cli.main(["reset", "--yes"])


def test_cli_runs_through_engine_handle(cli_db, monkeypatch):
    seen = []
    original = engine.handle
    monkeypatch.setattr(engine, "handle", lambda *a, **k: seen.append(a[3]) or original(*a, **k))
    cli.main(["feed", "--user", "@alice@example.com"])
    assert "feed" in seen


def test_cli_and_mastodon_paths_reach_same_state(cli_db):
    cli.main(["feed", "--user", "@cli@x.com"])
    with session_scope() as s:
        cli_hunger = repo.get_pet(s).hunger

    cli.main(["reset", "--yes"])
    parsed = parse("<p>@prami food</p>")  # what the Mastodon poller feeds the engine
    with session_scope() as s:
        pet = repo.get_pet(s)
        user = repo.get_or_create_user(s, "masto@x.com")
        engine.handle(s, pet, user, parsed.name, app_config, arg=parsed.arg)
        masto_hunger = pet.hunger

    assert cli_hunger == masto_hunger == 5