server-up/tests/test_categories.py
Ramon ff1979ba51
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m10s
v0.7.10-beta - categorieen met kleur in de app store en bij je apps
- Nieuw core/categories.py: zeventien categorieen, elk met naam (nl/en), kleur
  en pictogram. Kleuren zijn middentonen die in licht en donker leesbaar zijn;
  de interface gebruikt ze als tekstkleur op een doorzichtige variant.
- Alle 96 apps ingedeeld via metadata.categories. Een app mag in meerdere
  categorieen zitten; 39 apps doen dat (Frigate onder domotica en media,
  Immich onder fotos en opslag, Kavita onder media en documenten).
- Apps uit externe repos geven geen categorieen op; die worden afgeleid uit
  tags en omschrijving, zodat ook zij een kleur en een plek in het filter
  krijgen.
- App store: gekleurde categoriebalk met aantallen, klikbare badges per app,
  werkt naast het bestaande zoekveld en de tagfilters.
- Stackslijst: dezelfde balk met alleen de categorieen die je draait, gekleurde
  rand per kaart en klikbare badges.
- Nieuw /api/categories geeft de definities met kleur, zodat interface en
  backend dezelfde kleur voor dezelfde categorie gebruiken. /api/stacks geeft
  de categorieen mee; een stack van voor deze versie valt terug op "overig"
  in plaats van kleurloos te blijven.
- Tests: elke app heeft geldige categorieen, kleuren zijn uniek, en er staat
  geen categorie in het filter zonder apps.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 14:14:25 +02:00

113 lines
4.1 KiB
Python

"""Categorieën: indeling, kleuren en hoe ze door de API komen."""
import json
import sys
from pathlib import Path
import pytest
WORTEL = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(WORTEL / "server-up"))
from core import boilerplates as bp
from core import categories as cat
from conftest import login
APPS = sorted(d for d in (WORTEL / "apps").iterdir() if d.is_dir())
def test_elke_categorie_heeft_naam_en_kleur():
for cid, c in cat.CATEGORIES.items():
assert c["nl"] and c["en"], cid
assert c["color"].startswith("#") and len(c["color"]) == 7, cid
assert c["icon"].startswith("mdi-"), cid
def test_kleuren_zijn_uniek():
"""Twee categorieën met dezelfde kleur zijn niet uit elkaar te houden."""
kleuren = [c["color"] for c in cat.CATEGORIES.values()]
assert len(kleuren) == len(set(kleuren))
@pytest.mark.parametrize("app", APPS, ids=[d.name for d in APPS])
def test_elke_app_heeft_geldige_categorieen(app):
cats = bp.metadata(app)["categories"]
assert cats, "geen categorie"
for c in cats:
assert c in cat.CATEGORIES, f"onbekende categorie '{c}'"
assert len(cats) == len(set(cats)), "dubbele categorie"
def test_apps_mogen_in_meerdere_categorieen():
meervoudig = [d.name for d in APPS if len(bp.metadata(d)["categories"]) > 1]
assert len(meervoudig) >= 20, "vrijwel niemand valt onder meerdere categorieën"
def test_elke_categorie_wordt_gebruikt():
"""Een categorie die nergens voorkomt, is alleen maar ruis in het filter."""
gebruikt = {c for d in APPS for c in bp.metadata(d)["categories"]}
ongebruikt = set(cat.CATEGORIES) - gebruikt - {"overig"}
assert not ongebruikt, f"categorieën zonder apps: {sorted(ongebruikt)}"
def test_afleiden_uit_tags_voor_externe_repos():
"""Apps uit een externe repo geven geen categorieën op; dan leiden we ze af."""
assert "media" in cat.derive(["films", "series"])
assert "domotica" in cat.derive(["zigbee", "mqtt"])
assert "beveiliging" in cat.derive(["wachtwoorden"])
assert cat.derive([]) == ["overig"]
assert cat.derive(["iets-heel-onbekends"]) == ["overig"]
def test_afleiden_geeft_hoogstens_drie():
veel = ["films", "zigbee", "wachtwoorden", "backup", "wiki", "taken", "ai"]
assert len(cat.derive(veel)) <= 3
def test_normalize_gooit_onbekende_weg():
assert cat.normalize(["media", "bestaatniet", "media"]) == ["media"]
def test_normalize_valt_terug_op_tags():
assert cat.normalize([], ["films"]) == ["media"]
def test_endpoint_geeft_kleuren(client):
login(client)
d = client.get("/api/categories").get_json()
ids = {c["id"] for c in d["categories"]}
assert "media" in ids and "domotica" in ids
media = next(c for c in d["categories"] if c["id"] == "media")
assert media["color"].startswith("#") and media["name"]
def test_endpoint_in_het_engels(client):
login(client)
d = client.get("/api/categories?lang=en").get_json()
media = next(c for c in d["categories"] if c["id"] == "media")
assert media["name"] == "Media"
fam = next(c for c in d["categories"] if c["id"] == "gezin")
assert fam["name"] == "Family & home"
def test_stacklijst_geeft_categorieen(client, env):
login(client)
stack = env["lib"] / "jellyfin"
stack.mkdir(parents=True)
(stack / "docker-compose.yml").write_text("services:\n a:\n image: x\n")
(stack / ".serverup.json").write_text(json.dumps(
{"source": "jellyfin", "name": "Jellyfin", "categories": ["media"]}))
d = client.get("/api/stacks").get_json()
assert d[0]["categories"] == ["media"]
def test_oude_installatie_zonder_categorieen(client, env):
"""Een stack van vóór deze versie heeft geen categorieën opgeslagen; die
mogen niet leeg blijven, anders valt de kleurmarkering weg."""
login(client)
stack = env["lib"] / "oud"
stack.mkdir(parents=True)
(stack / "docker-compose.yml").write_text("services:\n a:\n image: x\n")
(stack / ".serverup.json").write_text(json.dumps({"source": "oud", "name": "Oud"}))
d = client.get("/api/stacks").get_json()
assert d[0]["categories"] == ["overig"]