feat(5.15+5.14): CLOSE — fix-uri code-review + embeddings functional
5.15 (propagare design + dashboard editare) si 5.14 (mapare LLM distilata) inchise dupa /code-review high. 8 buguri reparate TDD: - HIGH modal nu se deschidea pe randul slim (base.html: trimitere-slim) - HIGH /repune trunchia prestatii (declaratie incompleta la RAR) -> iterare peste existing, codes pozitional - HIGH embeddings incarca model ~230MB degeaba pe corpus gol -> poarta has_corpus() - HIGH picker chips gol pe re-render eroare -> conn/account_id pe toate ramurile - MED obs re-derivat dupa stergere explicita -> _merge_override pastreaza obs='' - MED mapare salvata fara denumire poluă GOLD -> _record_gold_validation guard - MED typo nome_prestatie -> nume_prestatie in select /repune - MED bucketare timp +3h gresita iarna -> SQLite localtime + TZ=Europe/Bucharest Embeddings WIRE-uit functional (PRD #15, decizie user): ensure_embeddings_corpus construieste corpus din nomenclator, gated pe AUTOPASS_EMBEDDINGS_ENABLED (default off). Marime model corectata ~50MB->~230MB (estimare PRD gresita). Cleanup: hoist load_* din bucla bulk-fix; import re la top. Regresie: 1256 passed, 1 deselected (live), 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -186,3 +186,187 @@ def test_fragmente_fara_fundal_hardcodat():
|
||||
"Fragmente cu fundal hardcodat dark (nu adapteaza la tema light):\n"
|
||||
+ "\n".join(vinovate)
|
||||
)
|
||||
|
||||
|
||||
# ── US-001 (PRD 5.15): Teme aditive + tokeni --card2/--line2 ──────────────────
|
||||
|
||||
def test_cele_4_teme_definite(client):
|
||||
"""Cele 4 teme noi (grafit/cobalt/cupru/hartie) au blocuri CSS [data-theme="..."]
|
||||
cu tokenul minim: --bg/--card/--ink/--muted/--line/--ok/--err/--accent."""
|
||||
resp = client.get("/login")
|
||||
assert resp.status_code == 200
|
||||
html = resp.text
|
||||
|
||||
for tema in ("grafit", "cobalt", "cupru", "hartie"):
|
||||
blk = re.search(
|
||||
r'\[data-theme=["\']' + tema + r'["\']\]\s*\{([^}]+)\}',
|
||||
html, re.DOTALL,
|
||||
)
|
||||
assert blk, f"Bloc CSS [data-theme=\"{tema}\"] negasit in HTML"
|
||||
block = blk.group(1)
|
||||
for var in ("--bg", "--card", "--ink", "--muted", "--line", "--ok", "--err", "--accent"):
|
||||
assert var in block, (
|
||||
f"Token {var} lipseste din blocul CSS [data-theme=\"{tema}\"]"
|
||||
)
|
||||
|
||||
|
||||
def test_tokeni_card2_line2_in_toate_temele(client):
|
||||
"""--card2 si --line2 sunt definiti in TOATE cele 7 teme:
|
||||
dark (:root), light, petrol, grafit, cobalt, cupru, hartie."""
|
||||
resp = client.get("/login")
|
||||
assert resp.status_code == 200
|
||||
html = resp.text
|
||||
|
||||
# dark e in :root
|
||||
root_blk = re.search(r':root\s*\{([^}]+)\}', html, re.DOTALL)
|
||||
assert root_blk, ":root CSS block negasit"
|
||||
root_block = root_blk.group(1)
|
||||
for var in ("--card2", "--line2"):
|
||||
assert var in root_block, f"{var} lipseste din :root (dark)"
|
||||
|
||||
for tema in ("light", "petrol", "grafit", "cobalt", "cupru", "hartie"):
|
||||
blk = re.search(
|
||||
r'\[data-theme=["\']' + tema + r'["\']\]\s*\{([^}]+)\}',
|
||||
html, re.DOTALL,
|
||||
)
|
||||
assert blk, f"Bloc CSS [data-theme=\"{tema}\"] negasit"
|
||||
block = blk.group(1)
|
||||
for var in ("--card2", "--line2"):
|
||||
assert var in block, f"{var} lipseste din blocul CSS [data-theme=\"{tema}\"]"
|
||||
|
||||
|
||||
def test_anti_fouc_7_stari(client):
|
||||
"""Anti-FOUC din <head> cunoaste TOATE cele 7+1 stari valide:
|
||||
light/dark/petrol/grafit/cobalt/cupru/hartie + auto.
|
||||
Valoare necunoscuta -> auto, fara blink.
|
||||
Fostul test test_anti_fouc_4_stari acoperea doar light/dark/petrol/auto;
|
||||
acum verifica toate 8 starile."""
|
||||
resp = client.get("/login")
|
||||
assert resp.status_code == 200
|
||||
html = resp.text
|
||||
|
||||
head_match = re.search(r'<head>(.*?)</head>', html, re.DOTALL | re.IGNORECASE)
|
||||
assert head_match, "<head> negasit"
|
||||
head = head_match.group(1)
|
||||
style_pos = head.find('<style>')
|
||||
assert style_pos >= 0, "<style> negasit in <head>"
|
||||
head_before_style = head[:style_pos]
|
||||
|
||||
for tema in ("light", "dark", "petrol", "grafit", "cobalt", "cupru", "hartie", "auto"):
|
||||
assert tema in head_before_style, (
|
||||
f"Tema '{tema}' lipseste din scriptul anti-FOUC (section inainte de <style>). "
|
||||
f"Utilizatorul cu localStorage.theme='{tema}' va vedea blink la prima incarcare."
|
||||
)
|
||||
|
||||
|
||||
def test_migrare_localStorage_legacy(client):
|
||||
"""Valorile vechi (light/dark/petrol) din localStorage raman VALIDE dupa adaugarea
|
||||
temelor noi. Fara migrare fortata; preferinta setata inainte de update e pastrata.
|
||||
Valoare lipsa/necunoscuta -> auto (fallback sigur, fara blink)."""
|
||||
resp = client.get("/login")
|
||||
assert resp.status_code == 200
|
||||
html = resp.text
|
||||
|
||||
head_match = re.search(r'<head>(.*?)</head>', html, re.DOTALL | re.IGNORECASE)
|
||||
assert head_match, "<head> negasit"
|
||||
head = head_match.group(1)
|
||||
style_pos = head.find('<style>')
|
||||
head_before_style = head[:style_pos]
|
||||
|
||||
# Valorile vechi trebuie sa fie recunoscute ca valide in anti-FOUC
|
||||
for tema_veche in ("light", "dark", "petrol"):
|
||||
assert tema_veche in head_before_style, (
|
||||
f"Tema legacy '{tema_veche}' a disparut din scriptul anti-FOUC. "
|
||||
f"Userii cu localStorage.theme='{tema_veche}' vor vedea blink (tratati ca necunoscut)."
|
||||
)
|
||||
|
||||
# Fallback la 'auto' trebuie sa fie prezent
|
||||
assert "auto" in head_before_style, (
|
||||
"'auto' (fallback pentru valori necunoscute) lipseste din anti-FOUC"
|
||||
)
|
||||
|
||||
|
||||
def test_themes_dry_single_source(client):
|
||||
"""DRY (E2): config temelor traieste intr-o singura structura sursa-de-adevar
|
||||
(var THEMES). ICONS/LABELS NU sunt literali separati (ar putea diverge de THEMES).
|
||||
Un test prinde o intrare ICONS/LABELS lipsa, nu doar token CSS lipsa.
|
||||
Adaugarea unei teme noi = O singura intrare in THEMES."""
|
||||
resp = client.get("/login")
|
||||
assert resp.status_code == 200
|
||||
html = resp.text
|
||||
|
||||
# Structura THEMES trebuie sa existe
|
||||
assert "THEMES" in html, (
|
||||
"var THEMES (sursa de adevar unica pentru config teme) negasit in HTML. "
|
||||
"E2: config trebuie consolidat intr-o singura structura."
|
||||
)
|
||||
|
||||
themes_match = re.search(r'var THEMES\s*=\s*\[(.*?)\];', html, re.DOTALL)
|
||||
assert themes_match, "var THEMES = [...]; nu a fost gasit (forma asteptata: var THEMES = [...])"
|
||||
themes_body = themes_match.group(1)
|
||||
|
||||
# Fiecare tema (inclusiv cele 4 noi) trebuie sa fie in THEMES
|
||||
for tema in ("light", "dark", "petrol", "grafit", "cobalt", "cupru", "hartie", "auto"):
|
||||
assert (f"'{tema}'" in themes_body or f'"{tema}"' in themes_body), (
|
||||
f"Tema '{tema}' lipseste din var THEMES. "
|
||||
f"DRY (E2): adaugarea temei = O singura intrare in THEMES."
|
||||
)
|
||||
|
||||
# ICONS si LABELS NU trebuie sa fie literali separati cu cheile hardcodate
|
||||
# (daca sunt literali, o tema noua in THEMES nu apare automat in ICONS/LABELS)
|
||||
icons_literal = re.search(r'var ICONS\s*=\s*\{', html)
|
||||
labels_literal = re.search(r'var LABELS\s*=\s*\{', html)
|
||||
assert not icons_literal, (
|
||||
"var ICONS = {...} e inca un literal separat (nu derivat din THEMES). "
|
||||
"O tema noua in THEMES nu va aparea automat in ICONS — rupe DRY (E2)."
|
||||
)
|
||||
assert not labels_literal, (
|
||||
"var LABELS = {...} e inca un literal separat (nu derivat din THEMES). "
|
||||
"O tema noua in THEMES nu va aparea automat in LABELS — rupe DRY (E2)."
|
||||
)
|
||||
|
||||
|
||||
# ── US-008: Test parametrizat robust — token critic in fiecare tema ─────────────
|
||||
|
||||
@pytest.mark.parametrize("token", ["--card2", "--line2", "--accent", "--ok", "--err"])
|
||||
@pytest.mark.parametrize("tema", ["light", "dark", "petrol", "grafit", "cobalt", "cupru", "hartie"])
|
||||
def test_token_critic_in_tema_parametrizat(client, tema, token):
|
||||
"""US-008: test parametrizat robust — fiecare token critic e definit in fiecare tema.
|
||||
|
||||
Ancorare pe selectorul CSS [data-theme="X"] {...} (sau :root pentru dark),
|
||||
NU pe felii fixe [idx:idx+N]. Evita false-green-ul din regresia 5.13:
|
||||
testele care feliau cu [idx:idx+N] nu prindeau un token lipsa dintr-o tema specifica
|
||||
(offset-ul era mascat de continut din alte teme).
|
||||
|
||||
La esec, pytest raporteaza EXACT combinatia (tema, token) care lipseste —
|
||||
debugging rapid fara cautare manuala in CSS.
|
||||
|
||||
Auto (tema 8): rezolvat la dark/light de anti-FOUC, fara bloc CSS propriu;
|
||||
acoperit de test_anti_fouc_7_stari. Verificam cele 7 teme concrete (cu bloc CSS).
|
||||
"""
|
||||
resp = client.get("/login")
|
||||
assert resp.status_code == 200
|
||||
html = resp.text
|
||||
|
||||
if tema == "dark":
|
||||
# Dark e tema implicita — traieste in :root {}
|
||||
blk = re.search(r':root\s*\{([^}]+)\}', html, re.DOTALL)
|
||||
assert blk, ":root CSS block negasit — dark tema nu are paleta definita"
|
||||
block = blk.group(1)
|
||||
else:
|
||||
blk = re.search(
|
||||
r'\[data-theme=["\']' + re.escape(tema) + r'["\']\]\s*\{([^}]+)\}',
|
||||
html, re.DOTALL,
|
||||
)
|
||||
assert blk, (
|
||||
f'Bloc CSS [data-theme="{tema}"] negasit in HTML. '
|
||||
f'Tema "{tema}" nu are paleta definita — adauga blocul CSS.'
|
||||
)
|
||||
block = blk.group(1)
|
||||
|
||||
assert token in block, (
|
||||
f"Token '{token}' lipseste din tema '{tema}' "
|
||||
f"({'\":root\"' if tema == 'dark' else f'\"[data-theme={tema}]\"'}). "
|
||||
f"Componentele cu var({token}) vor arata gresit pe aceasta tema. "
|
||||
f"Adauga '{token}:<valoare>;' in blocul CSS al temei '{tema}'."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user