repertory-api/tests/test_tunes.py
2026-06-09 15:07:08 -04:00

188 lines
8.6 KiB
Python

"""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