mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 05:14:29 +00:00
Two real findings on #833, both fixed: (1) `isolated_server` closed server's DB connections but left `appstate.meta_db` and `appstate.audio_effect_mappings` published and pointing at the closed handles -- a live-looking, dead singleton for any later test. Teardown now snapshots and restores both slots. (2) `test_reimporting_server_republishes_the_fresh_singletons` never performed a second import: it only re-asserted what `test_server_wires_the_seam` already covers, so it could not detect the very staleness it names. (I introduced that regression while fixing Codex's CONFIG_DIR isolation finding.) It now pops `server`, re-imports under a SECOND CONFIG_DIR, and asserts the seam republishes -- `second_server.meta_db is not first_db` and `appstate.meta_db is second_server.meta_db`. Negative-checked both directions: simulating an appstate-OWNED singleton (configure() only-first-wins) now fails the re-import test, and dropping server's configure() call still fails exactly the two wiring tests. NB CodeRabbit's committable suggestion inserted the snapshot above the fixture docstring, which would have demoted it from __doc__; written by hand instead. pytest 2348 passed; the full suite leaves the real ~/.local/share/feedback untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
154 lines
5.8 KiB
Python
154 lines
5.8 KiB
Python
"""The router seam (`appstate.py`).
|
|
|
|
The load-bearing assertion here is `test_server_wires_the_seam`: that `server`
|
|
actually calls `appstate.configure(...)`. Every other test in this file would
|
|
pass just fine against a seam nothing ever wires up — the same class of silent
|
|
no-op that bit the frontend refactor twice when a scripted `setHostHooks` edit
|
|
stopped matching its anchor. Unit tests cannot see wiring unless you make them
|
|
look at it.
|
|
"""
|
|
|
|
import importlib
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
import appstate
|
|
|
|
|
|
def _close_server_dbs(mod):
|
|
conn = getattr(getattr(mod, "meta_db", None), "conn", None)
|
|
if conn is not None:
|
|
getattr(mod, "_join_background_db_threads", lambda: None)()
|
|
conn.close()
|
|
ae_conn = getattr(getattr(mod, "audio_effect_mappings", None), "conn", None)
|
|
if ae_conn is not None:
|
|
ae_conn.close()
|
|
|
|
|
|
@pytest.fixture()
|
|
def isolated_server(tmp_path, monkeypatch):
|
|
"""A freshly imported `server` bound to a throwaway CONFIG_DIR.
|
|
|
|
Importing `server` constructs `MetadataDB` + `AudioEffectsMappingDB` at
|
|
module level, so it MUST be re-imported under a patched CONFIG_DIR — an
|
|
unguarded `import server` would create/mutate the developer's real
|
|
`~/.local/share/feedback` databases. Same idiom as the other ~49
|
|
server-importing suites.
|
|
|
|
Teardown restores the appstate slots as well as closing the connections:
|
|
leaving `appstate.meta_db` published but pointing at a closed sqlite handle
|
|
would hand a later test (or router) a live-looking, dead singleton.
|
|
"""
|
|
previous = (appstate.meta_db, appstate.audio_effect_mappings)
|
|
monkeypatch.setenv("CONFIG_DIR", str(tmp_path))
|
|
sys.modules.pop("server", None)
|
|
mod = importlib.import_module("server")
|
|
yield mod
|
|
_close_server_dbs(mod)
|
|
# Leave no half-torn-down `server` behind: the next fixture re-imports it.
|
|
sys.modules.pop("server", None)
|
|
appstate.configure(meta_db=previous[0], audio_effect_mappings=previous[1])
|
|
|
|
|
|
def test_import_is_side_effect_free():
|
|
"""`import appstate` must construct nothing and touch no disk.
|
|
|
|
This is why the ~49 fixtures that `sys.modules.pop("server")` and re-import
|
|
(to rebuild `meta_db` under a patched CONFIG_DIR) keep working untouched:
|
|
server owns construction, appstate only mirrors it. A singleton *owned*
|
|
here would survive that pop and go stale.
|
|
"""
|
|
sys.modules.pop("appstate", None)
|
|
fresh = importlib.import_module("appstate")
|
|
try:
|
|
assert fresh.meta_db is None
|
|
assert fresh.audio_effect_mappings is None
|
|
finally:
|
|
sys.modules["appstate"] = appstate
|
|
|
|
|
|
def test_configure_publishes_known_slots():
|
|
sentinel = object()
|
|
original = appstate.meta_db
|
|
try:
|
|
appstate.configure(meta_db=sentinel)
|
|
assert appstate.meta_db is sentinel
|
|
finally:
|
|
appstate.configure(meta_db=original)
|
|
|
|
|
|
def test_configure_is_idempotent():
|
|
"""server re-imports call configure() again; the last write must win."""
|
|
original = appstate.meta_db
|
|
try:
|
|
appstate.configure(meta_db="first")
|
|
appstate.configure(meta_db="second")
|
|
assert appstate.meta_db == "second"
|
|
finally:
|
|
appstate.configure(meta_db=original)
|
|
|
|
|
|
def test_configure_rejects_an_unknown_slot():
|
|
"""A typo'd or stale keyword must raise, not silently create a global that
|
|
nothing reads. A seam whose wiring can no-op undetected is worse than none."""
|
|
with pytest.raises(TypeError, match="unknown slot"):
|
|
appstate.configure(met_db="typo")
|
|
assert not hasattr(appstate, "met_db")
|
|
|
|
|
|
def test_late_bound_read_sees_a_later_configure():
|
|
"""Routers must read `appstate.meta_db`, never `from appstate import meta_db`.
|
|
This pins the property that makes that rule work."""
|
|
def router_style_read():
|
|
return appstate.meta_db # module attribute, resolved at call time
|
|
|
|
original = appstate.meta_db
|
|
try:
|
|
appstate.configure(meta_db="before")
|
|
assert router_style_read() == "before"
|
|
appstate.configure(meta_db="after")
|
|
assert router_style_read() == "after"
|
|
finally:
|
|
appstate.configure(meta_db=original)
|
|
|
|
|
|
def test_server_wires_the_seam(isolated_server):
|
|
"""The one that catches a dropped `appstate.configure(...)` call.
|
|
|
|
Identity, not truthiness, so a stray re-assignment or a half-applied edit
|
|
fails here rather than in some router months later.
|
|
"""
|
|
assert appstate.meta_db is isolated_server.meta_db
|
|
assert appstate.audio_effect_mappings is isolated_server.audio_effect_mappings
|
|
assert appstate.meta_db is not None
|
|
|
|
|
|
def test_reimporting_server_republishes_the_fresh_singletons(
|
|
isolated_server, tmp_path, monkeypatch
|
|
):
|
|
"""The 49-fixture contract, exercised end to end.
|
|
|
|
Those fixtures `sys.modules.pop("server")` + re-import to rebuild `meta_db`
|
|
under a new CONFIG_DIR, and know nothing about appstate. So the seam must
|
|
re-publish on that second import. This is the test that would fail if
|
|
`appstate` ever *owned* the singletons: a module-level `meta_db` there
|
|
survives the pop and the assertions below would still see the FIRST DB.
|
|
"""
|
|
first_db = isolated_server.meta_db
|
|
assert appstate.meta_db is first_db
|
|
assert str(tmp_path) in first_db.db_path
|
|
|
|
second_config = tmp_path / "second"
|
|
monkeypatch.setenv("CONFIG_DIR", str(second_config))
|
|
sys.modules.pop("server", None)
|
|
second_server = importlib.import_module("server")
|
|
try:
|
|
assert second_server.meta_db is not first_db # genuinely rebuilt
|
|
assert str(second_config) in second_server.meta_db.db_path
|
|
assert appstate.meta_db is second_server.meta_db # ...and re-published
|
|
assert appstate.audio_effect_mappings is second_server.audio_effect_mappings
|
|
finally:
|
|
_close_server_dbs(second_server)
|
|
sys.modules.pop("server", None)
|