mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 03:14:29 +00:00
Users can now pick a tuning preference before booking a gig: - Any (default), Standard only, Drop only, or a specific tuning - Backend filters the song pool (stubs + filler) by that preference - Empty-filter case returns a 404 with a descriptive message; frontend reverts the pref to 'any' and shows a notification - Interstitial pause before first song and on tuning changes (all prefs except 'specific') via window.feedBack.holdAutoplay(); opens the tuner panel in auto mode while the user retunes - 'Specific' gigs skip interstitials (every song already shares one tuning) - Graceful degradation: no holdAutoplay → interstitial silently skipped New backend: - _tuning_ok_fn helper for standard/drop/specific classification - _fill_genre_songs accepts optional tuning_ok filter - propose_gig batch-fetches tuning_name for played stubs, applies filter - GET /gigs/tunings endpoint for the specific-tuning picker Tests: - tests/test_career_gig_tuning.py — 17 Python tests (classification, filter) - tests/js/career_gig_tuning.test.js — 9 JS tests (interstitial logic) - tests/plugins/career/conftest.py — songs table schema gets tuning_name Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H2bM5jSbMskpdxm2CmuQVj
97 lines
3.5 KiB
Python
97 lines
3.5 KiB
Python
import json
|
|
import sqlite3
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / 'plugins' / 'career'))
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
# Drop a sibling 'routes' cached by another plugin's tests (bare-name collision).
|
|
sys.modules.pop('routes', None)
|
|
import routes as career_routes
|
|
|
|
|
|
class FakeMetaDb:
|
|
"""song_stats/songs stand-in for MetadataDB (the plugin reads nothing else).
|
|
|
|
The real song_stats.arrangement is an INTEGER index into the song's
|
|
arrangements JSON; the legacy star tests pass strings ("guitar"), which
|
|
the passport code treats as index-less → instrument defaults to guitar."""
|
|
|
|
def __init__(self):
|
|
self.conn = sqlite3.connect(":memory:", check_same_thread=False)
|
|
self.conn.execute(
|
|
"""CREATE TABLE song_stats (
|
|
filename TEXT, arrangement TEXT, best_accuracy REAL,
|
|
last_accuracy REAL, last_played_at TEXT,
|
|
seconds_total REAL NOT NULL DEFAULT 0
|
|
)"""
|
|
)
|
|
self.conn.execute(
|
|
"""CREATE TABLE songs (
|
|
filename TEXT, title TEXT, artist TEXT,
|
|
genre TEXT DEFAULT '', arrangements TEXT,
|
|
tuning_name TEXT DEFAULT '', tuning_sort_key INTEGER DEFAULT 0
|
|
)"""
|
|
)
|
|
|
|
def add(self, filename, arrangement, best_accuracy, in_library=True,
|
|
genre="", arrangements=None, last_played_at=None, seconds_total=0,
|
|
last_accuracy=None):
|
|
self.conn.execute("INSERT INTO song_stats VALUES (?, ?, ?, ?, ?, ?)",
|
|
(filename, arrangement, best_accuracy,
|
|
last_accuracy if last_accuracy is not None else best_accuracy,
|
|
last_played_at, seconds_total))
|
|
if in_library:
|
|
self.conn.execute(
|
|
"INSERT INTO songs SELECT ?, ?, ?, ?, ?, '', 0 WHERE NOT EXISTS "
|
|
"(SELECT 1 FROM songs WHERE filename = ?)",
|
|
(filename, filename.replace(".feedpak", "").title(), "Test Artist",
|
|
genre,
|
|
json.dumps(arrangements) if arrangements is not None else None,
|
|
filename))
|
|
self.conn.commit()
|
|
|
|
def add_song_only(self, filename, genre="", tuning_name=""):
|
|
"""A library song with no plays — feeds the genre (brochure) list."""
|
|
self.conn.execute("INSERT INTO songs VALUES (?, ?, ?, ?, ?, ?, 0)",
|
|
(filename, filename, "Test Artist", genre, None, tuning_name))
|
|
self.conn.commit()
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _bind_career_routes():
|
|
"""Keep sys.modules['routes'] pointing at THIS plugin's routes for these tests."""
|
|
prev = sys.modules.get('routes')
|
|
sys.modules['routes'] = career_routes
|
|
try:
|
|
yield
|
|
finally:
|
|
if prev is not None:
|
|
sys.modules['routes'] = prev
|
|
else:
|
|
sys.modules.pop('routes', None)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_state():
|
|
# Module state outlives tests when the module stays imported — reset the
|
|
# mutable bits so ordering can't leak downloads/content between tests.
|
|
career_routes._state["downloads"] = {}
|
|
yield
|
|
|
|
|
|
@pytest.fixture
|
|
def meta_db():
|
|
return FakeMetaDb()
|
|
|
|
|
|
@pytest.fixture
|
|
def client(tmp_path, meta_db):
|
|
app = FastAPI()
|
|
career_routes.setup(app, {"config_dir": str(tmp_path), "meta_db": meta_db})
|
|
return TestClient(app)
|