Give the tests one setup instead of thirty-five

Every test module carried the same eight-line prologue redirecting the
database to a temp file. Only the first one to be imported ever took
effect: `app.database` reads `AIDND_DB_PATH` at import and builds `engine`
from it once, so by the time the second module ran the engine already
existed. The other 34 copies created a temp file that nothing opened and
nothing deleted, and leaked one per module per run.

`conftest.py` now does it once, which is early enough because pytest
imports conftest before any test module. It also deletes the file when the
run ends. The tests still share one database, exactly as they already did:
each `client` fixture calls `create_all` on setup and `drop_all` on
teardown, so no test sees another test's rows.

`tests/fakes.py` holds the one `ScriptedProvider`. Nine modules each had a
copy, and the copies had drifted into four feature sets, so a test that
needed to raise a provider error had to be written in one of the files
whose copy supported that. The shared one is the superset. The two
`FakeProvider` copies were the same class with a fixed reply, so they use
it too. `test_chat.py` keeps its own, which implements `chat` rather than
`generate` and records what it was constructed with.

An autouse fixture resets the fake's class state between tests, so a stale
reply list can no longer reach the next test.

435 lines out of the suite. 549 tests pass. Verified live by sabotage:
breaking the shared fake fails 13 tests across four modules.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Dix4oGV3njgWRdu7P9t6r
This commit is contained in:
parththakkar106
2026-08-29 00:47:46 +05:30
co-authored by Claude Opus 5
parent b1772c6e21
commit 32cd7c1077
37 changed files with 123 additions and 460 deletions
+57
View File
@@ -0,0 +1,57 @@
"""Shared setup for the test suite.
pytest imports this file before it imports any test module, which is the only
reason the database redirection below works. `app.database` reads
`AIDND_DB_PATH` at import and builds `engine` from it once, so the variable has
to be set before the first `from app...` line anywhere in the suite.
Every test module used to carry its own copy of that redirection. Only the first
one to be imported ever took effect, because the engine already existed by the
time the second one ran. The other copies created a temp file that nothing
opened and nothing deleted. One copy here does the job, and it cleans up after
itself.
The tests share one database. That is not new: they already did. Each `client`
fixture calls `Base.metadata.create_all` on setup and `drop_all` on teardown, so
no test sees another test's rows.
"""
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
# A real `AIDND_DATABASE_URL` or `DATABASE_URL` in the developer's shell points
# at Postgres, and `app.database` prefers either over the SQLite path above.
# Clear both, so running the suite never touches a server database.
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest # noqa: E402 Import order is load-bearing; see above.
from fakes import ScriptedProvider # noqa: E402
@pytest.fixture(autouse=True)
def reset_scripted_provider():
"""Clears the fake provider's state between tests.
`ScriptedProvider` keeps its replies and its call count on the class, because
the code under test constructs the provider itself and a test cannot reach
the instance. Class state outlives a test, so reset it here rather than
trusting every fixture to remember.
"""
ScriptedProvider.replies = []
ScriptedProvider.calls = 0
ScriptedProvider.prompts = []
yield
def pytest_sessionfinish(session, exitstatus):
"""Deletes the temporary database once the run ends."""
try:
os.unlink(_tmp.name)
except OSError:
# The file is already gone, or Windows still holds a handle on it. It is
# in the temp directory either way, so leaving it costs nothing.
pass
+41
View File
@@ -0,0 +1,41 @@
"""Stand-ins for the parts of the app a test must not really call.
Import these rather than writing another copy. Nine test modules each carried
their own `ScriptedProvider`, and the copies had drifted into four different
feature sets, so a test that needed to raise a provider error had to be written
in one of the files whose copy supported it.
"""
class ScriptedProvider:
"""Streams canned replies in place of `OpenAICompatibleProvider`.
Set `replies` to the texts the model returns, one per call. The last entry
repeats once the list runs out, so a test that plays more turns than it
scripted still gets text. To drive the provider-error path, put an
`Exception` in the list. It is raised rather than streamed.
State lives on the class, not on the instance, because the turn engine
constructs the provider itself and a test never sees the object. The autouse
`reset_scripted_provider` fixture in `conftest.py` clears it between tests.
`prompts` records every assembled `(system, story)` pair, which is what a
test asserts on to check what the model was shown.
"""
last_usage = None
replies: list = []
calls = 0
prompts: list = []
def __init__(self, *a, **k):
pass
async def generate(self, parts, *, temperature, max_tokens):
index = min(ScriptedProvider.calls, len(ScriptedProvider.replies) - 1)
ScriptedProvider.calls += 1
ScriptedProvider.prompts.append((parts.system, parts.story))
reply = ScriptedProvider.replies[index]
if isinstance(reply, Exception):
raise reply
yield ("text", reply)
-9
View File
@@ -9,15 +9,6 @@ accounts on a schedule, and a log that deletes itself is not a log.
python -m pytest tests/test_accesslog.py -v python -m pytest tests/test_accesslog.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
-9
View File
@@ -12,15 +12,6 @@ to be scrolling. An anchor means the same thing before and after.
python -m pytest tests/test_action_paging.py -v python -m pytest tests/test_action_paging.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
-9
View File
@@ -9,15 +9,6 @@ the dashboard, and cannot inflate what it reports beyond hitting the page.
python -m pytest tests/test_analytics.py -v python -m pytest tests/test_analytics.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
from datetime import timedelta from datetime import timedelta
import pytest import pytest
+2 -26
View File
@@ -8,15 +8,6 @@ story, and the arrangement costs neither an extra prompt nor an extra turn.
python -m pytest tests/test_attempt_siblings.py -v python -m pytest tests/test_attempt_siblings.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -26,9 +17,10 @@ from app import attempts, auth, limits, models, tree
from app.context import cursors, history from app.context import cursors, history
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}} SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}}
GOLD_SCRIPT = """ GOLD_SCRIPT = """
@@ -40,22 +32,6 @@ modifier(text);
""" """
class ScriptedProvider:
last_usage = None
replies: list = []
calls = 0
prompts: list = []
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
index = min(ScriptedProvider.calls, len(ScriptedProvider.replies) - 1)
ScriptedProvider.calls += 1
ScriptedProvider.prompts.append((parts.system, parts.story))
yield ("text", ScriptedProvider.replies[index])
@pytest.fixture() @pytest.fixture()
def client(monkeypatch): def client(monkeypatch):
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
-9
View File
@@ -21,15 +21,6 @@ nodes may appear on C.
python -m pytest tests/test_branch_clause.py -v python -m pytest tests/test_branch_clause.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
+2 -26
View File
@@ -12,15 +12,6 @@ that makes borrowing possible lives in `lineage`.
python -m pytest tests/test_branch_forking.py -v python -m pytest tests/test_branch_forking.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -29,9 +20,10 @@ from app import auth, limits, models
from app.context import cursors, lineage from app.context import cursors, lineage
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
# `hp` moves freely. `mana` has a cooldown of 2 turns, so an incorrect # `hp` moves freely. `mana` has a cooldown of 2 turns, so an incorrect
# advance shows up as a change the referee should have rejected. # advance shows up as a change the referee should have rejected.
SCHEMA = { SCHEMA = {
@@ -50,22 +42,6 @@ modifier(text);
""" """
class ScriptedProvider:
last_usage = None
replies: list = []
calls = 0
prompts: list = []
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
index = min(ScriptedProvider.calls, len(ScriptedProvider.replies) - 1)
ScriptedProvider.calls += 1
ScriptedProvider.prompts.append((parts.system, parts.story))
yield ("text", ScriptedProvider.replies[index])
@pytest.fixture() @pytest.fixture()
def client(monkeypatch): def client(monkeypatch):
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
+1 -23
View File
@@ -19,15 +19,6 @@ Two rules carry most of this file:
python -m pytest tests/test_branch_management.py -v python -m pytest tests/test_branch_management.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -37,22 +28,9 @@ from app import auth, limits, models, schemas
from app.context import cursors, lineage from app.context import cursors, lineage
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
class ScriptedProvider:
last_usage = None
replies: list = []
calls = 0
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
index = min(ScriptedProvider.calls, len(ScriptedProvider.replies) - 1)
ScriptedProvider.calls += 1
yield ("text", ScriptedProvider.replies[index])
@pytest.fixture() @pytest.fixture()
+2 -24
View File
@@ -23,15 +23,6 @@ importer a file that does disagree with itself.
python -m pytest tests/test_bundle_v2.py -v python -m pytest tests/test_bundle_v2.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -40,9 +31,10 @@ from app import auth, bundle, limits, models
from app.context import lineage from app.context import lineage
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}} SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}}
# Ten gold a turn, so the stored gold total tells how many turns the # Ten gold a turn, so the stored gold total tells how many turns the
@@ -58,20 +50,6 @@ modifier(text);
OPENING = "You enter a cave." OPENING = "You enter a cave."
class ScriptedProvider:
last_usage = None
replies: list = []
calls = 0
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
index = min(ScriptedProvider.calls, len(ScriptedProvider.replies) - 1)
ScriptedProvider.calls += 1
yield ("text", ScriptedProvider.replies[index])
@pytest.fixture() @pytest.fixture()
def client(monkeypatch): def client(monkeypatch):
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
-9
View File
@@ -6,15 +6,6 @@ page.
python -m pytest tests/test_chat.py -v python -m pytest tests/test_chat.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
-9
View File
@@ -17,15 +17,6 @@ Two kinds of guard live here, and both are needed:
python -m pytest tests/test_egress.py -v python -m pytest tests/test_egress.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import json import json
import random import random
-7
View File
@@ -7,15 +7,8 @@ for bit, and that the migration reaches every row, however many there are.
python -m pytest tests/test_embedding_blob.py -v python -m pytest tests/test_embedding_blob.py -v
""" """
import os
import struct import struct
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import json import json
import random import random
@@ -19,15 +19,6 @@ garbage instead.
python -m pytest tests/test_embedding_model_switch.py -v python -m pytest tests/test_embedding_model_switch.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import asyncio import asyncio
import pytest import pytest
-7
View File
@@ -6,15 +6,8 @@ deleted.
python -m pytest tests/test_guest_cleanup.py -v python -m pytest tests/test_guest_cleanup.py -v
""" """
import os
import tempfile
from datetime import timedelta from datetime import timedelta
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from sqlalchemy import create_engine, event from sqlalchemy import create_engine, event
-9
View File
@@ -16,15 +16,6 @@ Two things must hold, and both are easy to break by accident:
python -m pytest tests/test_history_window.py -v python -m pytest tests/test_history_window.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from sqlalchemy import event from sqlalchemy import event
-7
View File
@@ -16,15 +16,8 @@ Two things are easy to break here:
python -m pytest tests/test_length_hint.py -v python -m pytest tests/test_length_hint.py -v
""" """
import os
import re import re
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
-9
View File
@@ -19,15 +19,6 @@ exactly as `test_branch_clause.py` builds it.
python -m pytest tests/test_memory_nodes.py -v python -m pytest tests/test_memory_nodes.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import asyncio import asyncio
import pytest import pytest
-9
View File
@@ -14,15 +14,6 @@ failure that no error message would report:
python -m pytest tests/test_memory_retrieval.py -v python -m pytest tests/test_memory_retrieval.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
-7
View File
@@ -23,14 +23,7 @@ happened.
python -m pytest tests/test_memory_settling.py -v python -m pytest tests/test_memory_settling.py -v
""" """
import asyncio import asyncio
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
-9
View File
@@ -2,15 +2,6 @@
python -m pytest tests/test_netguard.py -v python -m pytest tests/test_netguard.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from app import auth, netguard from app import auth, netguard
-6
View File
@@ -25,13 +25,7 @@ than assumed.
python -m pytest tests/test_prompt_caching.py -v python -m pytest tests/test_prompt_caching.py -v
""" """
import os import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
@@ -9,15 +9,6 @@ also has an email-keyed throttle that no IP trick can weaken.
python -m pytest tests/test_ratelimit_hardening.py -v python -m pytest tests/test_ratelimit_hardening.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from app import auth, limits from app import auth, limits
+3 -30
View File
@@ -4,15 +4,6 @@ switchable, restoring the world/script state that attempt produced.
python -m pytest tests/test_retry_variants.py -v python -m pytest tests/test_retry_variants.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -20,9 +11,11 @@ from fastapi.testclient import TestClient
from app import auth, limits, models from app import auth, limits, models
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts, ProviderError from app.providers import ProviderError
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}} SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}}
# Each turn spends 10 gold, so a double-applied or un-rolled-back attempt shows. # Each turn spends 10 gold, so a double-applied or un-rolled-back attempt shows.
@@ -35,26 +28,6 @@ modifier(text);
""" """
class ScriptedProvider:
"""Streams the next canned reply each call, so successive retries differ."""
last_usage = None
replies: list = []
calls = 0
prompts: list = [] # every assembled (system, story) pair, for context assertions
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
index = min(ScriptedProvider.calls, len(ScriptedProvider.replies) - 1)
ScriptedProvider.calls += 1
ScriptedProvider.prompts.append((parts.system, parts.story))
reply = ScriptedProvider.replies[index]
if isinstance(reply, Exception):
raise reply
yield ("text", reply)
@pytest.fixture() @pytest.fixture()
def client(monkeypatch): def client(monkeypatch):
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
-7
View File
@@ -9,14 +9,7 @@ player's last line.
python -m pytest tests/test_scenario_art.py -v python -m pytest tests/test_scenario_art.py -v
""" """
import base64 import base64
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
-9
View File
@@ -3,15 +3,6 @@ story cards and stat schema back down over a running adventure's copy.
python -m pytest tests/test_scenario_refresh.py -v python -m pytest tests/test_scenario_refresh.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
-7
View File
@@ -8,14 +8,7 @@ deletes seeded rows no file claims any more.
python -m pytest tests/test_seed_sweep.py -v python -m pytest tests/test_seed_sweep.py -v
""" """
import json import json
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
@@ -17,16 +17,9 @@ Three things must hold, and only the first is obvious:
python -m pytest tests/test_snapshot_compression.py -v python -m pytest tests/test_snapshot_compression.py -v
""" """
import json import json
import os
import random import random
import tempfile
import zlib import zlib
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from sqlalchemy import text from sqlalchemy import text
-7
View File
@@ -8,14 +8,7 @@ empty account would.
python -m pytest tests/test_starter_adventure.py -v python -m pytest tests/test_starter_adventure.py -v
""" """
import json import json
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from sqlalchemy import create_engine, event from sqlalchemy import create_engine, event
-7
View File
@@ -11,17 +11,10 @@ only in their outcome.
Run from the backend dir: python -m pytest tests/test_state_revert.py -v Run from the backend dir: python -m pytest tests/test_state_revert.py -v
""" """
import os
import tempfile
# Point the app at a throwaway SQLite file before importing anything that # Point the app at a throwaway SQLite file before importing anything that
# binds the engine at import time. `app.database` reads `AIDND_DB_PATH` # binds the engine at import time. `app.database` reads `AIDND_DB_PATH`
# on import. # on import.
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import HTTPException from fastapi import HTTPException
+2 -31
View File
@@ -17,15 +17,6 @@ those subphases, the change is wrong, not the test. SP4 is the first
subphase allowed to move it, and only for the variant-count semantics subphase allowed to move it, and only for the variant-count semantics
called out in plan/14. called out in plan/14.
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -33,9 +24,10 @@ from fastapi.testclient import TestClient
from app import auth, limits, models from app import auth, limits, models
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
# A world-state schema, so the RPG layer is exercised rather than skipped. # A world-state schema, so the RPG layer is exercised rather than skipped.
SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}} SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}}
@@ -52,27 +44,6 @@ modifier(text);
OPENING = "You enter a cave." OPENING = "You enter a cave."
class ScriptedProvider:
"""Streams the next canned reply each call, so successive turns differ."""
last_usage = None
replies: list = []
calls = 0
prompts: list = [] # every assembled (system, story) pair
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
index = min(ScriptedProvider.calls, len(ScriptedProvider.replies) - 1)
ScriptedProvider.calls += 1
ScriptedProvider.prompts.append((parts.system, parts.story))
reply = ScriptedProvider.replies[index]
if isinstance(reply, Exception):
raise reply
yield ("text", reply)
def _make_world(monkeypatch, *, seeded_actions: int = 0): def _make_world(monkeypatch, *, seeded_actions: int = 0):
"""Create a user, a scenario, and an adventure, with `seeded_actions` """Create a user, a scenario, and an adventure, with `seeded_actions`
extra story actions written straight to the database. Paging tests need extra story actions written straight to the database. Paging tests need
+1 -23
View File
@@ -20,15 +20,6 @@ be silently broken.
python -m pytest tests/test_take_edit.py -v python -m pytest tests/test_take_edit.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -36,22 +27,9 @@ from fastapi.testclient import TestClient
from app import auth, limits, models from app import auth, limits, models
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
class ScriptedProvider:
last_usage = None
replies: list = []
calls = 0
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
index = min(ScriptedProvider.calls, len(ScriptedProvider.replies) - 1)
ScriptedProvider.calls += 1
yield ("text", ScriptedProvider.replies[index])
@pytest.fixture() @pytest.fixture()
+1 -21
View File
@@ -20,14 +20,7 @@ they mean.
python -m pytest tests/test_take_parentage.py -v python -m pytest tests/test_take_parentage.py -v
""" """
import json import json
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
@@ -36,22 +29,9 @@ from fastapi.testclient import TestClient
from app import attempts, auth, limits, models from app import attempts, auth, limits, models
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
class ScriptedProvider:
last_usage = None
replies: list = []
calls = 0
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
index = min(ScriptedProvider.calls, len(ScriptedProvider.replies) - 1)
ScriptedProvider.calls += 1
yield ("text", ScriptedProvider.replies[index])
@pytest.fixture() @pytest.fixture()
+2 -24
View File
@@ -15,15 +15,6 @@ the line it was on.
python -m pytest tests/test_take_state.py -v python -m pytest tests/test_take_state.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -31,9 +22,10 @@ from fastapi.testclient import TestClient
from app import auth, limits, models from app import auth, limits, models
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}} SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}}
# Ten gold a turn, every turn. A number that only ever increases makes a # Ten gold a turn, every turn. A number that only ever increases makes a
@@ -48,20 +40,6 @@ modifier(text);
""" """
class ScriptedProvider:
last_usage = None
replies: list = []
calls = 0
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
index = min(ScriptedProvider.calls, len(ScriptedProvider.replies) - 1)
ScriptedProvider.calls += 1
yield ("text", ScriptedProvider.replies[index])
@pytest.fixture() @pytest.fixture()
def client(monkeypatch): def client(monkeypatch):
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
-7
View File
@@ -16,14 +16,7 @@ place, would silently skip the DDL and test only half the change.
python -m pytest tests/test_tree_migration.py -v python -m pytest tests/test_tree_migration.py -v
""" """
import json import json
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
+5 -19
View File
@@ -7,15 +7,6 @@ adventure's stored gold total stays correct across play, undo, and retry.
python -m pytest tests/test_turn_flow_integration.py -v python -m pytest tests/test_turn_flow_integration.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -23,9 +14,10 @@ from fastapi.testclient import TestClient
from app import auth, limits, models from app import auth, limits, models
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
GOLD_SCRIPT = """ GOLD_SCRIPT = """
const modifier = (text) => { const modifier = (text) => {
state.gold = (state.gold || 0) + 10; state.gold = (state.gold || 0) + 10;
@@ -35,14 +27,7 @@ modifier(text);
""" """
class FakeProvider: AI_REPLY = "The torch flickers as you press onward."
"""Stand-in for OpenAICompatibleProvider: streams one fixed line, no network."""
last_usage = None
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
yield ("text", "The torch flickers as you press onward.")
@pytest.fixture() @pytest.fixture()
@@ -66,7 +51,8 @@ def client(monkeypatch):
setup.close() setup.close()
# Force a real, non-demo turn that uses the fake provider. # Force a real, non-demo turn that uses the fake provider.
monkeypatch.setattr(adventures, "OpenAICompatibleProvider", FakeProvider) ScriptedProvider.replies = [AI_REPLY]
monkeypatch.setattr(adventures, "OpenAICompatibleProvider", ScriptedProvider)
monkeypatch.setattr(auth, "resolve_provider_config", lambda s: auth.ProviderConfig( monkeypatch.setattr(auth, "resolve_provider_config", lambda s: auth.ProviderConfig(
"http://fake", "k", "test-model", False)) "http://fake", "k", "test-model", False))
monkeypatch.setattr(limits, "rate_limit", lambda *a, **k: None) monkeypatch.setattr(limits, "rate_limit", lambda *a, **k: None)
+4 -20
View File
@@ -4,15 +4,6 @@ undo rolling the world state back.
python -m pytest tests/test_worldstate_integration.py -v python -m pytest tests/test_worldstate_integration.py -v
""" """
import os
import tempfile
_tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp.close()
os.environ["AIDND_DB_PATH"] = _tmp.name
os.environ.pop("AIDND_DATABASE_URL", None)
os.environ.pop("DATABASE_URL", None)
import pytest import pytest
from fastapi import Depends from fastapi import Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -20,9 +11,10 @@ from fastapi.testclient import TestClient
from app import auth, limits, models from app import auth, limits, models
from app.database import Base, SessionLocal, engine, get_db from app.database import Base, SessionLocal, engine, get_db
from app.main import app from app.main import app
from app.providers import PromptParts
from app.routers import adventures from app.routers import adventures
from fakes import ScriptedProvider
SCHEMA = { SCHEMA = {
"player": {"hp": {"min": 0, "max": 100, "initial": 100, "max_delta_per_turn": 30}}, "player": {"hp": {"min": 0, "max": 100, "initial": 100, "max_delta_per_turn": 30}},
"npcs": { "npcs": {
@@ -45,15 +37,6 @@ AI_REPLY = (
) )
class FakeProvider:
last_usage = None
def __init__(self, *a, **k):
pass
async def generate(self, parts: PromptParts, *, temperature, max_tokens):
yield ("text", AI_REPLY)
@pytest.fixture() @pytest.fixture()
def client(monkeypatch): def client(monkeypatch):
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
@@ -78,7 +61,8 @@ def client(monkeypatch):
adv_id, user_id = adv.id, user.id adv_id, user_id = adv.id, user.id
setup.close() setup.close()
monkeypatch.setattr(adventures, "OpenAICompatibleProvider", FakeProvider) ScriptedProvider.replies = [AI_REPLY]
monkeypatch.setattr(adventures, "OpenAICompatibleProvider", ScriptedProvider)
monkeypatch.setattr(auth, "resolve_provider_config", lambda s: auth.ProviderConfig( monkeypatch.setattr(auth, "resolve_provider_config", lambda s: auth.ProviderConfig(
"http://fake", "k", "test-model", False)) "http://fake", "k", "test-model", False))
monkeypatch.setattr(limits, "rate_limit", lambda *a, **k: None) monkeypatch.setattr(limits, "rate_limit", lambda *a, **k: None)