mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-24 05:41:53 +00:00
* refactor(server): move the metadata-enrichment subsystem into lib/enrichment.py (R3)
MusicBrainz / Cover-Art-Archive / AcoustID transport, the match-scorer glue, and
the background enrichment worker (~930 lines, 61 defs) leave server.py as one
cohesive unit. Bodies are verbatim; the only changes are seam reads:
meta_db / config_dir / sloppak_cache_dir / art_cache_dir -> appstate.<slot>
_song_pack_art_exists / _art_override_paths (stay in server.py for the art +
delete routes) -> appstate.<callable> (new seam slots, injected by reference)
_env_flag -> env_compat.env_flag_compat (the existing identical helper)
_artist_title_from_filename -> imported from metadata_db (its home)
the User-Agent VERSION lookup: Path(__file__).parent ->
Path(__file__).resolve().parents[1] (lib/enrichment.py -> app root)
server.py drives the worker through the module (import enrichment; the routes +
scan lifecycle call enrichment.X). Tests that faked the network on `server`
(_mb_http_get, _enrich_network_enabled, _caa_http_get, ...) now patch the same
names on `enrichment` — the module attribute is resolved at call time, so one
setattr reaches both the routes and the worker's internal callers. Acyclic:
enrichment imports appstate/appconfig/dlc_paths/metadata_db/mb_match/
acoustid_match/sloppak/loosefolder, never server.
server.py: 6,917 -> 5,988 (-929).
Verified: pyflakes clean (bar the pre-existing File/safe_join/tuning_name/ET);
route table IDENTICAL (143); full pytest 2400 passed (140 enrichment/art cases
incl the offline-safety + transport-error-pauses-pass contracts that fake the
network); test_packaging 44 passed (enrichment.py resolves under lib/); eslint 0.
Boot smoke: /api/enrichment/status + POST /kick serve from the new module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: reset enrichment worker state between tests (CodeRabbit)
lib/enrichment.py now owns the worker, and it stays imported for the whole
session while the `server` fixtures pop-and-reimport `server` — so the cancel
Event / status dict / caches would leak across tests, and a stale `_enrich_cancel`
could short-circuit a later direct `_background_enrich()`. An autouse conftest
fixture clears that process-global state before each test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: tighten the enrichment-reset fixture (CodeRabbit)
Narrow the import guard to ImportError (not blind Exception, BLE001), and stop
clearing _caa_index_locks — it's guarded by _caa_index_locks_guard, so an
unlocked clear() would race a still-alive worker, and its per-release mutexes
carry no test state.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
2.7 KiB
Python
79 lines
2.7 KiB
Python
"""Shared pytest fixtures for the feedBack test suite."""
|
|
|
|
import logging
|
|
|
|
import pytest
|
|
import structlog
|
|
|
|
|
|
_LOGGING_NAMES = ("feedBack", "uvicorn", "uvicorn.error", "uvicorn.access")
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_enrichment_state():
|
|
"""Reset the enrichment worker's process-global state between tests.
|
|
|
|
The `server` fixtures pop-and-reimport `server`, but `lib/enrichment.py`
|
|
(which now owns the worker) stays imported for the whole session, so its
|
|
module globals — the cancel Event, the status dict, the caches — would
|
|
otherwise leak across tests. A test that set `_enrich_cancel` (or a stale
|
|
`running` status) could silently short-circuit a later direct
|
|
`_background_enrich()` call. Clear it up front so each test starts clean.
|
|
"""
|
|
try:
|
|
import enrichment
|
|
except ImportError:
|
|
yield
|
|
return
|
|
enrichment._enrich_cancel.clear()
|
|
enrichment._enrich_pending_pass = False
|
|
enrichment._enrich_status.update(
|
|
{"running": False, "processed": 0, "last_pass_at": None,
|
|
"total": 0, "matched": 0, "current": None})
|
|
enrichment._enrich_last_fetch = 0.0
|
|
enrichment._artist_alias_cache.clear()
|
|
# _caa_index_locks is deliberately left alone: it's guarded by
|
|
# _caa_index_locks_guard, so clearing it here (unlocked) would race a
|
|
# still-alive worker thread, and its entries are stateless per-release
|
|
# mutexes that don't leak test state anyway.
|
|
yield
|
|
|
|
|
|
@pytest.fixture()
|
|
def isolate_logging():
|
|
"""Restore feedBack / uvicorn logger state after each test.
|
|
|
|
Saves handlers, level, and propagate flag before the test runs and
|
|
restores all three on teardown. Import into any test module that calls
|
|
configure_logging() so mutations don't bleed across tests.
|
|
"""
|
|
saved = {}
|
|
for name in _LOGGING_NAMES:
|
|
lg = logging.getLogger(name)
|
|
saved[name] = (
|
|
list(lg.handlers), # snapshot the handler list
|
|
lg.level,
|
|
lg.propagate,
|
|
)
|
|
yield
|
|
for name in _LOGGING_NAMES:
|
|
lg = logging.getLogger(name)
|
|
original_handlers, original_level, original_propagate = saved[name]
|
|
|
|
# Close and remove any handlers that were added during the test.
|
|
for h in list(lg.handlers):
|
|
if h not in original_handlers:
|
|
lg.removeHandler(h)
|
|
h.close()
|
|
# Remove any original handlers that may have been removed during the test
|
|
# so we can add them back cleanly.
|
|
for h in list(lg.handlers):
|
|
lg.removeHandler(h)
|
|
# Reattach the original handlers.
|
|
for h in original_handlers:
|
|
lg.addHandler(h)
|
|
|
|
lg.setLevel(original_level)
|
|
lg.propagate = original_propagate
|
|
structlog.reset_defaults()
|