Der Monitor löst den KI-Weg als Lage vor Organisation vor Servervorgabe auf und liest dafür das Org-Setting ai_backend. Geschrieben werden konnte es bisher nur direkt in der Datenbank, eine Oberfläche gab es nicht. Für einen Kunden, der die DSGVO-Fassung will, ist das der praktisch wichtigste Weg, denn der stellt einmal zentral um und nicht Lage für Lage. Das Feld steht jetzt dort, wo die Pipeline-Sprache schon steht, also als eigene Spalte in der Lizenztabelle der aktiven Lizenz, und zusätzlich im Anlege-Dialog für neue Organisationen. Drei Werte. Leer bedeutet Servervorgabe, cli den heutigen Weg über Anthropic, bedrock die EU-Fassung über AWS Bedrock in Frankfurt mit Recherche über den europäischen Suchindex staan. Der Leerstring ist dabei ein gültiger Wert und keine fehlende Angabe. Der Router prüft deshalb auf None statt auf Wahrheit, sonst ließe sich eine gesetzte Vorgabe nie wieder auf die Servervorgabe zurücknehmen. 26 Prüfungen, ohne Datenbank und ohne Netz. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
139 Zeilen
4.8 KiB
Python
139 Zeilen
4.8 KiB
Python
"""Prueft die organisationsweite KI-Vorgabe ohne Datenbank und ohne Netz.
|
|
|
|
Der Monitor loest den KI-Weg als Lage vor Organisation vor Servervorgabe auf.
|
|
Diese Datei deckt das mittlere Glied ab, also das Org-Setting 'ai_backend'.
|
|
Geprueft werden die Modelle (welche Werte kommen durch), der Router (wird das
|
|
Setting geschrieben und gelesen) und die Oberflaeche (Spaltenzahl der Tabelle,
|
|
Behandlung des Leerstrings als gueltiger Wert).
|
|
"""
|
|
import os
|
|
import re
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from models import OrgCreate, OrgResponse, OrgUpdate
|
|
|
|
STAMM = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
|
def lies(*teile):
|
|
with open(os.path.join(STAMM, *teile), encoding="utf-8") as f:
|
|
return f.read()
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def router():
|
|
return lies("src", "routers", "organizations.py")
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def html():
|
|
return lies("src", "static", "dashboard.html")
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def js():
|
|
return lies("src", "static", "js", "app.js")
|
|
|
|
|
|
# --- Modelle ---------------------------------------------------------------
|
|
|
|
@pytest.mark.parametrize("wert", ["", "cli", "bedrock"])
|
|
def test_orgcreate_nimmt_gueltige_werte(wert):
|
|
assert OrgCreate(name="Test", slug="test", ai_backend=wert).ai_backend == wert
|
|
|
|
|
|
@pytest.mark.parametrize("wert", ["gpt", "CLI", "bedrock ", "openai", "anthropic"])
|
|
def test_orgcreate_lehnt_ungueltige_werte_ab(wert):
|
|
with pytest.raises(ValidationError):
|
|
OrgCreate(name="Test", slug="test", ai_backend=wert)
|
|
|
|
|
|
def test_orgcreate_ohne_angabe_ist_leer():
|
|
assert OrgCreate(name="T", slug="t").ai_backend == ""
|
|
|
|
|
|
@pytest.mark.parametrize("wert", ["", "cli", "bedrock"])
|
|
def test_orgupdate_nimmt_gueltige_werte(wert):
|
|
assert OrgUpdate(ai_backend=wert).ai_backend == wert
|
|
|
|
|
|
def test_orgupdate_unterscheidet_leerstring_von_fehlender_angabe():
|
|
"""Der Leerstring bedeutet ausdruecklich "Servervorgabe" und muss den
|
|
Router erreichen. Eine fehlende Angabe darf dagegen nichts aendern."""
|
|
assert OrgUpdate(ai_backend="").ai_backend == ""
|
|
assert OrgUpdate(name="x").ai_backend is None
|
|
|
|
|
|
def test_orgresponse_liefert_das_feld():
|
|
felder = OrgResponse.model_fields
|
|
assert "ai_backend" in felder
|
|
assert felder["ai_backend"].default == ""
|
|
|
|
|
|
# --- Router ----------------------------------------------------------------
|
|
|
|
def test_router_setzt_beim_anlegen(router):
|
|
assert 'set_org_setting(db, org_id, "ai_backend", data.ai_backend or "")' in router
|
|
|
|
|
|
def test_router_setzt_beim_aendern(router):
|
|
assert 'set_org_setting(db, org_id, "ai_backend", data.ai_backend)' in router
|
|
|
|
|
|
def test_router_prueft_auf_none_statt_auf_wahrheit(router):
|
|
"""Eine Pruefung auf Wahrheit wuerde das Zuruecksetzen auf die
|
|
Servervorgabe verschlucken, denn der Leerstring ist falsy."""
|
|
assert "if data.ai_backend is not None:" in router
|
|
|
|
|
|
def test_router_liefert_das_setting_mit(router):
|
|
assert "key = 'ai_backend'" in router
|
|
assert 'org["ai_backend"] = (ai_row["value"] if ai_row else "") or ""' in router
|
|
|
|
|
|
# --- Oberflaeche -----------------------------------------------------------
|
|
|
|
def test_lizenztabelle_hat_acht_spalten(html):
|
|
bis = html.index('<tbody id="licenseTable">')
|
|
kopf = html[html.rindex("<thead>", 0, bis):bis]
|
|
assert len(re.findall(r"<th>", kopf)) == 8
|
|
assert "<th>KI-Verarbeitung</th>" in kopf
|
|
|
|
|
|
def test_jede_zeile_fuellt_acht_spalten(js):
|
|
"""Beide bedingten Zellen liefern in jedem Zweig genau ein td, im
|
|
else-Zweig steht es woertlich im Template. Zaehlen genuegt also."""
|
|
zeile = re.search(r"tbody\.innerHTML = licenses\.map.*?\.join\(\"\"\);", js, re.S).group(0)
|
|
assert len(re.findall(r"<td", zeile)) == 8
|
|
assert "spracheZelle" in zeile and "kiZelle" in zeile
|
|
|
|
|
|
def test_leere_tabelle_spannt_ueber_acht_spalten(js):
|
|
assert 'colspan="8"' in js
|
|
assert 'colspan="7"' not in js
|
|
|
|
|
|
def test_speicherfunktion_reicht_den_wert_unveraendert_durch(js):
|
|
assert "function setOrgAiBackend(wert)" in js
|
|
assert "_saveOrgSetting({ ai_backend: wert }" in js
|
|
assert 'onchange="setOrgAiBackend(this.value)"' in js
|
|
|
|
|
|
def test_auswahl_bietet_drei_werte(js):
|
|
auswahl = re.search(r"const kiZelle = `.*?`;", js, re.S).group(0)
|
|
assert re.findall(r'value="([^"]*)"', auswahl) == ["", "cli", "bedrock"]
|
|
assert auswahl.count("selected") == 3 # jeder Wert kann vorausgewaehlt sein
|
|
|
|
|
|
def test_anlege_dialog_hat_dasselbe_feld(html, js):
|
|
auswahl = re.search(r'<select id="newOrgAiBackend">.*?</select>', html, re.S)
|
|
assert auswahl is not None
|
|
assert re.findall(r'value="([^"]*)"', auswahl.group(0)) == ["", "cli", "bedrock"]
|
|
assert 'ai_backend: (document.getElementById("newOrgAiBackend") || {}).value || ""' in js
|
|
|
|
|
|
def test_cache_buster_wurde_erhoeht(html):
|
|
assert re.findall(r"/static/js/app\.js\?v=([0-9a-z]+)", html) == ["20260802a"]
|