"""Tests for /tunes endpoints.""" from tests.conftest import AUTH, BAD_AUTH def create_tune(client, **kwargs): data = {"name": "Cluck Old Hen", "key": "A", **kwargs} r = client.post("/tunes/", json=data, headers=AUTH) assert r.status_code == 201, r.get_json() return r.get_json() def get_instrument_id(client, name): instruments = client.get("/instruments").get_json() return next(i["id"] for i in instruments if i["name"] == name) # ── Auth ────────────────────────────────────────────────────── class TestAuth: def test_missing_key_rejected_on_write(self, client): r = client.post("/tunes/", json={"name": "Test"}) assert r.status_code == 401 def test_wrong_key_rejected_on_write(self, client): r = client.post("/tunes/", json={"name": "Test"}, headers=BAD_AUTH) assert r.status_code == 401 def test_reads_are_public(self, client): assert client.get("/tunes/").status_code == 200 def test_correct_key_accepted_on_write(self, client): r = client.post("/tunes/", json={"name": "Test"}, headers=AUTH) assert r.status_code == 201 # ── Health ──────────────────────────────────────────────────── class TestHealth: def test_health(self, client): assert client.get("/health").get_json()["status"] == "ok" # ── Instruments lookup ──────────────────────────────────────── class TestInstrumentsLookup: def test_list_instruments(self, client): names = [i["name"] for i in client.get("/instruments").get_json()] assert "banjo" in names assert "fiddle" in names # ── Tune CRUD ───────────────────────────────────────────────── class TestTuneCRUD: def test_create_minimal(self, client): tune = create_tune(client) assert tune["name"] == "Cluck Old Hen" assert tune["key"] == "A" assert tune["modal"] is None assert tune["source"] is None assert tune["instruments"] == [] assert tune["notes"] == [] assert tune["references"] == [] def test_create_with_instrument(self, client): banjo_id = get_instrument_id(client, "banjo") tuning = client.post("/tunings/", json={"name": "double C"}, headers=AUTH).get_json() tune = create_tune(client, instruments=[{ "instrument_id": banjo_id, "tuning_id": tuning["id"], "callable": True, }]) assert len(tune["instruments"]) == 1 assert tune["instruments"][0]["instrument_name"] == "banjo" assert tune["instruments"][0]["tuning"] == "double C" def test_create_with_two_instruments(self, client): banjo_id = get_instrument_id(client, "banjo") fiddle_id = get_instrument_id(client, "fiddle") tune = create_tune(client, instruments=[ {"instrument_id": banjo_id, "tuning": "double C"}, {"instrument_id": fiddle_id, "tuning": "cross"}, ]) assert len(tune["instruments"]) == 2 def test_create_with_notes_and_references(self, client): tune = create_tune(client, notes=[{"note": "tricky B part"}], references=[{"link": "https://youtube.com/x", "site": "YouTube"}], ) assert tune["notes"][0]["note"] == "tricky B part" assert tune["references"][0]["site"] == "YouTube" def test_get_single(self, client): created = create_tune(client) r = client.get(f'/tunes/{created["id"]}', headers=AUTH) assert r.status_code == 200 assert r.get_json()["id"] == created["id"] def test_get_nonexistent_returns_404(self, client): assert client.get("/tunes/99999", headers=AUTH).status_code == 404 def test_list_returns_all(self, client): create_tune(client, name="Tune A") create_tune(client, name="Tune B") names = [t["name"] for t in client.get("/tunes/", headers=AUTH).get_json()] assert "Tune A" in names and "Tune B" in names def test_patch_core_fields(self, client): tune = create_tune(client) r = client.patch(f'/tunes/{tune["id"]}', json={"key": "G", "name": "New Name", "modal": True}, headers=AUTH) assert r.status_code == 200 u = r.get_json() assert u["key"] == "G" and u["name"] == "New Name" and u["modal"] is True def test_delete(self, client): tune = create_tune(client) r = client.delete(f'/tunes/{tune["id"]}', headers=AUTH) assert r.status_code == 200 assert client.get(f'/tunes/{tune["id"]}', headers=AUTH).status_code == 404 def test_delete_cascades_to_instrument_entry(self, client): from app.models import TuneByInstrument from app.extensions import db banjo_id = get_instrument_id(client, "banjo") tune = create_tune(client, instruments=[{"instrument_id": banjo_id}]) client.delete(f'/tunes/{tune["id"]}', headers=AUTH) assert db.session.query(TuneByInstrument).filter_by(tune_id=tune["id"]).count() == 0 # ── Filtering ───────────────────────────────────────────────── class TestFiltering: def test_filter_by_key(self, client): create_tune(client, name="D tune", key="D") create_tune(client, name="G tune", key="G") results = client.get("/tunes/?key=D", headers=AUTH).get_json() assert all(t["key"] == "D" for t in results) def test_search_by_name(self, client): create_tune(client, name="Whiskey Before Breakfast") create_tune(client, name="Salt Creek") results = client.get("/tunes/?search=whiskey", headers=AUTH).get_json() assert len(results) == 1 assert results[0]["name"] == "Whiskey Before Breakfast" def test_filter_by_instrument(self, client): banjo_id = get_instrument_id(client, "banjo") create_tune(client, name="Has Banjo", instruments=[{"instrument_id": banjo_id, "tuning": "standard"}]) create_tune(client, name="No Banjo") results = client.get(f"/tunes/?instrument_id={banjo_id}", headers=AUTH).get_json() names = [t["name"] for t in results] assert "Has Banjo" in names and "No Banjo" not in names def test_filter_by_tuning(self, client): fiddle_id = get_instrument_id(client, "fiddle") cross_tuning = client.post("/tunings/", json={"name": "cross"}, headers=AUTH).get_json() std_tuning = client.post("/tunings/", json={"name": "standard"}, headers=AUTH).get_json() create_tune(client, name="Cross", instruments=[{"instrument_id": fiddle_id, "tuning_id": cross_tuning["id"]}]) create_tune(client, name="Std", instruments=[{"instrument_id": fiddle_id, "tuning_id": std_tuning["id"]}]) results = client.get(f'/tunes/?instrument_id={fiddle_id}&tuning={cross_tuning["id"]}', headers=AUTH).get_json() assert len(results) == 1 and results[0]["name"] == "Cross" def test_filter_callable(self, client): banjo_id = get_instrument_id(client, "banjo") create_tune(client, name="Can call", instruments=[{"instrument_id": banjo_id, "callable": True}]) create_tune(client, name="Cannot call", instruments=[{"instrument_id": banjo_id, "callable": False}]) results = client.get(f"/tunes/?instrument_id={banjo_id}&callable=true", headers=AUTH).get_json() names = [t["name"] for t in results] assert "Can call" in names and "Cannot call" not in names def test_filter_to_learn(self, client): fiddle_id = get_instrument_id(client, "fiddle") create_tune(client, name="To learn", instruments=[{"instrument_id": fiddle_id, "to_learn": True}]) create_tune(client, name="Learned", instruments=[{"instrument_id": fiddle_id, "to_learn": False}]) results = client.get(f"/tunes/?instrument_id={fiddle_id}&to_learn=true", headers=AUTH).get_json() names = [t["name"] for t in results] assert "To learn" in names and "Learned" not in names def test_filter_modal(self, client): create_tune(client, name="Modal", modal=True) create_tune(client, name="Regular", modal=False) results = client.get("/tunes/?modal=true", headers=AUTH).get_json() names = [t["name"] for t in results] assert "Modal" in names and "Regular" not in names