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:
578
tests/test_mapare_integrare_l14.py
Normal file
578
tests/test_mapare_integrare_l14.py
Normal file
@@ -0,0 +1,578 @@
|
||||
"""TDD L14-S6 — Integrare Layer 2/3 in editor (suggestion-only, DUPA 5.15).
|
||||
|
||||
Scenarii acoperite:
|
||||
- F1-regression CRITIC: SILVER/shared GOLD NU auto-trimit (resolve_prestatii neschimbat)
|
||||
- pending_unmapped include sugestie GOLD partajat > SILVER > embeddings (precedenta Eng-F2)
|
||||
- record_human_validation apelat la confirmare umana (POST /mapari -> shared_mappings)
|
||||
- Degradare gratioasa cand embeddings indisponibil (mock is_available=False)
|
||||
- Separare structurala #13: resolve_prestatii/load_mapping NU citesc tabelele de sugestii
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Fixtures #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
@pytest.fixture()
|
||||
def env(monkeypatch):
|
||||
"""DB temporara cu schema initiata, auth dezactivata (mod dev)."""
|
||||
tmp = tempfile.mkdtemp()
|
||||
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "l14_s6_test.db"))
|
||||
monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
|
||||
from app.config import get_settings
|
||||
get_settings.cache_clear()
|
||||
from app.db import init_db
|
||||
init_db()
|
||||
yield monkeypatch
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def conn(env):
|
||||
from app.db import get_connection
|
||||
c = get_connection()
|
||||
# Seed nomenclator (OE-1, OE-2, OE-3, OE-4 suficient pentru teste)
|
||||
c.executemany(
|
||||
"INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
|
||||
[
|
||||
("OE-1", "REPARATIE MOTOR"),
|
||||
("OE-2", "INTRETINERE"),
|
||||
("OE-3", "REVIZIE PERIODICA"),
|
||||
("OE-4", "REGLARE"),
|
||||
],
|
||||
)
|
||||
c.commit()
|
||||
yield c
|
||||
c.close()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client(env):
|
||||
from app.main import app
|
||||
from fastapi.testclient import TestClient
|
||||
with TestClient(app) as c:
|
||||
yield c
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# F1-regression CRITIC: SILVER/shared GOLD NU auto-trimit #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_f1_silver_nu_auto_trimite(conn):
|
||||
"""CRITICAL F1: un cod in SILVER (mapping_suggestions) NU produce auto-trimitere.
|
||||
|
||||
resolve_prestatii cu mapping gol + SILVER existent -> operatie ramane nemapata.
|
||||
Submissionul ar ramane needs_mapping, NU queued.
|
||||
"""
|
||||
from app.shared_store import seed_suggestions
|
||||
from app.mapping import resolve_prestatii
|
||||
|
||||
seed_suggestions(conn, [
|
||||
{"denumire": "Revizie periodica", "cod_prestatie": "OE-3", "source": "llm", "confidence": 0.95},
|
||||
])
|
||||
conn.commit()
|
||||
|
||||
# resolve_prestatii cu mapping gol -> SILVER nu se vede
|
||||
resolved, unmapped = resolve_prestatii(
|
||||
[{"cod_op_service": "OP-REV", "denumire": "Revizie periodica"}],
|
||||
{}, # operations_mapping gol
|
||||
)
|
||||
# Operatia ramane nemapata (SILVER nu e in resolve, #13)
|
||||
assert resolved[0]["cod_prestatie"] is None
|
||||
assert len(unmapped) == 1
|
||||
|
||||
|
||||
def test_f1_shared_gold_nu_auto_trimite(conn):
|
||||
"""CRITICAL F1: un cod in shared_mappings (GOLD partajat) NU produce auto-trimitere.
|
||||
|
||||
resolve_prestatii cu mapping gol + shared GOLD existent -> operatie ramane nemapata.
|
||||
"""
|
||||
from app.shared_store import record_human_validation
|
||||
from app.mapping import resolve_prestatii
|
||||
|
||||
record_human_validation(conn, "Schimb ulei motor", "OE-3")
|
||||
conn.commit()
|
||||
|
||||
# resolve_prestatii cu mapping gol -> GOLD partajat nu se vede
|
||||
resolved, unmapped = resolve_prestatii(
|
||||
[{"cod_op_service": "OP-ULEI", "denumire": "Schimb ulei motor"}],
|
||||
{}, # operations_mapping gol
|
||||
)
|
||||
# Operatia ramane nemapata (GOLD partajat nu e in resolve, #13)
|
||||
assert resolved[0]["cod_prestatie"] is None
|
||||
assert len(unmapped) == 1
|
||||
|
||||
|
||||
def test_f1_load_mapping_nu_citeste_shared_gold(conn):
|
||||
"""Separare #13: load_mapping NU returneaza coduri din shared_mappings."""
|
||||
from app.shared_store import record_human_validation
|
||||
from app.mapping import load_mapping
|
||||
|
||||
record_human_validation(conn, "Revizie anuala", "OE-3")
|
||||
conn.commit()
|
||||
|
||||
mapping = load_mapping(conn, account_id=1)
|
||||
# GOLD partajat nu trebuie sa apara in load_mapping (citit de resolve_prestatii)
|
||||
assert "Revizie anuala" not in mapping
|
||||
# Maparea propriu-zisa (operations_mapping) ramane goala
|
||||
assert len(mapping) == 0
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# enrich_suggestions: GOLD partajat > SILVER > embeddings #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_enrich_fara_surse_returneaza_none(conn):
|
||||
"""Fara GOLD/SILVER/embedding -> sugestie_principala = None."""
|
||||
from app.mapping import enrich_suggestions
|
||||
|
||||
result = enrich_suggestions(conn, "Operatie inexistenta")
|
||||
assert result["sugestie_principala"] is None
|
||||
assert result["surse"]["gold_partajat"] is None
|
||||
assert result["surse"]["silver"] is None
|
||||
assert result["surse"]["embedding"] is None
|
||||
|
||||
|
||||
def test_enrich_include_gold_partajat(conn):
|
||||
"""enrich_suggestions returneaza sugestie GOLD partajat cand shared_mappings are match."""
|
||||
from app.shared_store import record_human_validation
|
||||
from app.mapping import enrich_suggestions
|
||||
|
||||
record_human_validation(conn, "Schimb ulei", "OE-3")
|
||||
conn.commit()
|
||||
|
||||
result = enrich_suggestions(conn, "Schimb ulei")
|
||||
|
||||
assert result["sugestie_principala"] is not None
|
||||
assert result["sugestie_principala"]["cod_prestatie"] == "OE-3"
|
||||
assert result["sugestie_principala"]["sursa"] == "gold_partajat"
|
||||
assert result["surse"]["gold_partajat"] == "OE-3"
|
||||
|
||||
|
||||
def test_enrich_include_silver(conn):
|
||||
"""enrich_suggestions returneaza sugestie SILVER cand mapping_suggestions are match."""
|
||||
from app.shared_store import seed_suggestions
|
||||
from app.mapping import enrich_suggestions
|
||||
|
||||
seed_suggestions(conn, [
|
||||
{"denumire": "Reparatie motor", "cod_prestatie": "OE-1", "source": "llm", "confidence": 0.9},
|
||||
])
|
||||
conn.commit()
|
||||
|
||||
result = enrich_suggestions(conn, "Reparatie motor")
|
||||
|
||||
assert result["sugestie_principala"] is not None
|
||||
assert result["sugestie_principala"]["cod_prestatie"] == "OE-1"
|
||||
assert result["sugestie_principala"]["sursa"] == "silver"
|
||||
assert result["surse"]["silver"] == "OE-1"
|
||||
|
||||
|
||||
def test_enrich_precedenta_gold_peste_silver(conn):
|
||||
"""Precedenta Eng-F2: GOLD partajat castiga fata de SILVER cand ambele exista."""
|
||||
from app.shared_store import seed_suggestions, record_human_validation
|
||||
from app.mapping import enrich_suggestions
|
||||
|
||||
# SILVER spune OE-1, GOLD spune OE-3
|
||||
seed_suggestions(conn, [
|
||||
{"denumire": "Verificare tehnica", "cod_prestatie": "OE-1", "source": "llm", "confidence": 0.8},
|
||||
])
|
||||
record_human_validation(conn, "Verificare tehnica", "OE-3")
|
||||
conn.commit()
|
||||
|
||||
result = enrich_suggestions(conn, "Verificare tehnica")
|
||||
|
||||
assert result["sugestie_principala"] is not None
|
||||
assert result["sugestie_principala"]["cod_prestatie"] == "OE-3"
|
||||
assert result["sugestie_principala"]["sursa"] == "gold_partajat"
|
||||
# SILVER prezent dar nu castiga
|
||||
assert result["surse"]["silver"] == "OE-1"
|
||||
assert result["surse"]["gold_partajat"] == "OE-3"
|
||||
|
||||
|
||||
def test_enrich_degradare_embeddings_indisponibil(conn, monkeypatch):
|
||||
"""Degradare gratioasa (#16b): cand embeddings nu e disponibil, nu eroare."""
|
||||
import app.embeddings as emb_mod
|
||||
monkeypatch.setattr(emb_mod, "is_available", lambda: False)
|
||||
|
||||
from app.mapping import enrich_suggestions
|
||||
|
||||
# Fara surse -> sugestie_principala = None, fara exceptie
|
||||
result = enrich_suggestions(conn, "Operatie demo", include_embeddings=True)
|
||||
assert result["sugestie_principala"] is None
|
||||
assert result["surse"]["embedding"] is None
|
||||
|
||||
|
||||
def test_enrich_corpus_gol_nu_incarca_modelul(conn, monkeypatch):
|
||||
"""Bug fix (code-review): enrich_suggestions NU lazy-load-eaza modelul de 220MB
|
||||
cand corpus-ul embeddings e gol.
|
||||
|
||||
Implementarea veche apela `is_available()` neconditionat -> `_get_engine()` ->
|
||||
`_load_engine()` -> `FastEmbedBackend()` (incarcare sincrona 30-120s) chiar daca
|
||||
`index_corpus` nu a fost apelat niciodata in productie -> corpus gol ->
|
||||
`suggest_nearest` ar fi returnat [] oricum (zero beneficiu, cost mare).
|
||||
Fix: poarta `has_corpus()` (ieftina, nu construieste engine-ul cand `_engine is None`).
|
||||
"""
|
||||
import app.embeddings as emb_mod
|
||||
|
||||
# Engine ne-initializat -> corpus gol prin definitie.
|
||||
monkeypatch.setattr(emb_mod, "_engine", None, raising=False)
|
||||
incarcari = {"n": 0}
|
||||
orig_load = emb_mod._load_engine
|
||||
|
||||
def _spy_load():
|
||||
incarcari["n"] += 1
|
||||
return orig_load()
|
||||
|
||||
monkeypatch.setattr(emb_mod, "_load_engine", _spy_load)
|
||||
|
||||
from app.mapping import enrich_suggestions
|
||||
result = enrich_suggestions(conn, "Operatie oarecare", include_embeddings=True)
|
||||
|
||||
assert result["surse"]["embedding"] is None
|
||||
assert incarcari["n"] == 0, (
|
||||
"Modelul de embeddings NU trebuie incarcat cand corpus-ul e gol "
|
||||
f"(index_corpus nu e wired). _load_engine apelat de {incarcari['n']} ori."
|
||||
)
|
||||
|
||||
|
||||
class _FakeEmbedBackend:
|
||||
"""Backend embedding determinist (3 dimensiuni keyword) — fara model real 230MB."""
|
||||
|
||||
def embed(self, texts):
|
||||
out = []
|
||||
for t in texts:
|
||||
tl = str(t).lower()
|
||||
out.append([
|
||||
1.0 if "ulei" in tl else 0.0,
|
||||
1.0 if "motor" in tl else 0.0,
|
||||
1.0 if "frana" in tl else 0.0,
|
||||
])
|
||||
return out
|
||||
|
||||
|
||||
def test_embeddings_functional_cand_flag_activ(conn, monkeypatch):
|
||||
"""PRD #15: cu AUTOPASS_EMBEDDINGS_ENABLED=true, embeddings produce efectiv o sugestie.
|
||||
|
||||
Wire-uieste ensure_embeddings_corpus (corpus din nomenclator) + enrich_suggestions.
|
||||
Backend injectat (determinist) -> nu incarca modelul real de 230MB.
|
||||
"""
|
||||
import app.embeddings as emb_mod
|
||||
from app.embeddings import EmbeddingEngine
|
||||
from app.config import get_settings
|
||||
|
||||
# Activeaza flagul + injecteaza backend fals in singleton-ul global.
|
||||
monkeypatch.setenv("AUTOPASS_EMBEDDINGS_ENABLED", "true")
|
||||
get_settings.cache_clear()
|
||||
monkeypatch.setattr(emb_mod, "_engine", EmbeddingEngine(backend=_FakeEmbedBackend()))
|
||||
|
||||
# Nomenclatorul (din fixtura conn) are OE-1..OE-4; adaug coduri cu denumiri keyword.
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
|
||||
("UL-1", "Schimb ulei"),
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
|
||||
("FR-1", "Placute frana"),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
from app.mapping import ensure_embeddings_corpus, enrich_suggestions
|
||||
|
||||
ensure_embeddings_corpus(conn)
|
||||
assert emb_mod.has_corpus(), "corpusul trebuie indexat cand flagul e activ"
|
||||
|
||||
# "schimbat uleiul motor" -> vector [1,1,0] -> cel mai apropiat = UL-1 (Schimb ulei).
|
||||
result = enrich_suggestions(conn, "schimbat uleiul motor", include_embeddings=True)
|
||||
assert result["surse"]["embedding"] == "UL-1", (
|
||||
f"embeddings trebuie sa sugereze UL-1, got {result['surse']}"
|
||||
)
|
||||
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
def test_embeddings_flag_off_ramane_noop(conn, monkeypatch):
|
||||
"""Cu flagul off (default), ensure_embeddings_corpus e no-op total (nu indexeaza)."""
|
||||
import app.embeddings as emb_mod
|
||||
from app.embeddings import EmbeddingEngine
|
||||
from app.config import get_settings
|
||||
|
||||
monkeypatch.setenv("AUTOPASS_EMBEDDINGS_ENABLED", "false")
|
||||
get_settings.cache_clear()
|
||||
# Engine cu backend disponibil, dar flagul off -> NU se indexeaza nimic.
|
||||
monkeypatch.setattr(emb_mod, "_engine", EmbeddingEngine(backend=_FakeEmbedBackend()))
|
||||
|
||||
from app.mapping import ensure_embeddings_corpus
|
||||
ensure_embeddings_corpus(conn)
|
||||
assert not emb_mod.has_corpus(), "flag off -> corpusul NU trebuie indexat"
|
||||
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
def test_enrich_silver_nul_ignorat(conn):
|
||||
"""SILVER cu is_nul=1 (non-operatie) NU apare ca sugestie."""
|
||||
from app.shared_store import seed_suggestions
|
||||
from app.mapping import enrich_suggestions
|
||||
|
||||
seed_suggestions(conn, [
|
||||
{"denumire": "ITP CT 12 ABC", "is_nul": True, "source": "llm", "confidence": 0.99},
|
||||
])
|
||||
conn.commit()
|
||||
|
||||
result = enrich_suggestions(conn, "ITP CT 12 ABC")
|
||||
assert result["sugestie_principala"] is None
|
||||
assert result["surse"]["silver"] is None
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# pending_unmapped: include sugestie_principala #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_pending_unmapped_include_sugestie_principala(conn):
|
||||
"""pending_unmapped returneaza entries cu sugestie_principala din GOLD/SILVER."""
|
||||
from app.shared_store import record_human_validation
|
||||
from app.mapping import pending_unmapped
|
||||
import json
|
||||
|
||||
record_human_validation(conn, "Schimb ulei motor", "OE-3")
|
||||
conn.commit()
|
||||
|
||||
# Creeaza un submission needs_mapping cu "Schimb ulei motor"
|
||||
conn.execute(
|
||||
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
|
||||
"VALUES (1, 'needs_mapping', ?, 'key-test-001')",
|
||||
(json.dumps({
|
||||
"vin": "WVWZZZ1KZAW001111",
|
||||
"prestatii": [{"cod_op_service": "OP-ULEI", "denumire": "Schimb ulei motor"}],
|
||||
}),),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
pending = pending_unmapped(conn, account_id=1)
|
||||
assert len(pending) == 1
|
||||
entry = pending[0]
|
||||
|
||||
# sugestie_principala adaugat de enrich_suggestions (L14-S6)
|
||||
assert "sugestie_principala" in entry
|
||||
sp = entry["sugestie_principala"]
|
||||
assert sp is not None
|
||||
assert sp["cod_prestatie"] == "OE-3"
|
||||
assert sp["sursa"] == "gold_partajat"
|
||||
|
||||
|
||||
def test_pending_unmapped_fara_surse_sugestie_principala_none(conn, monkeypatch):
|
||||
"""pending_unmapped -> sugestie_principala = None cand nu exista nicio sursa.
|
||||
|
||||
Dezactiveaza embeddings prin poarta reala `has_corpus`=False (gate-ul folosit de
|
||||
enrich_suggestions dupa wiring), independent de starea singleton-ului global lasata
|
||||
de alte teste (izolare de ordine).
|
||||
"""
|
||||
import app.embeddings as emb_mod
|
||||
monkeypatch.setattr(emb_mod, "has_corpus", lambda: False)
|
||||
monkeypatch.setattr(emb_mod, "is_available", lambda: False)
|
||||
|
||||
from app.mapping import pending_unmapped
|
||||
import json
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
|
||||
"VALUES (1, 'needs_mapping', ?, 'key-test-002')",
|
||||
(json.dumps({
|
||||
"vin": "WVWZZZ1KZAW002222",
|
||||
"prestatii": [{"cod_op_service": "OP-FARA-SURSA", "denumire": "Operatie de nisa"}],
|
||||
}),),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
pending = pending_unmapped(conn, account_id=1)
|
||||
assert len(pending) == 1
|
||||
entry = pending[0]
|
||||
|
||||
assert "sugestie_principala" in entry
|
||||
assert entry["sugestie_principala"] is None
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# record_human_validation apelat la confirmare umana #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_record_human_validation_la_post_mapari(env, client):
|
||||
"""POST /mapari (tab Mapari) -> record_human_validation scrie in shared_mappings.
|
||||
|
||||
Testul verifica ca GOLD partajat se populeaza automat la confirmarea umana
|
||||
din interfata de mapari.
|
||||
"""
|
||||
from app.db import get_connection
|
||||
import json
|
||||
|
||||
# Creeaza un submission needs_mapping
|
||||
conn_setup = get_connection()
|
||||
try:
|
||||
conn_setup.executemany(
|
||||
"INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
|
||||
[("OE-3", "REVIZIE PERIODICA"), ("OE-1", "REPARATIE")],
|
||||
)
|
||||
conn_setup.execute(
|
||||
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
|
||||
"VALUES (1, 'needs_mapping', ?, 'key-hv-001')",
|
||||
(json.dumps({
|
||||
"vin": "WVWZZZ1KZAW003333",
|
||||
"prestatii": [{"cod_op_service": "OP-REV", "denumire": "Revizie anuala"}],
|
||||
}),),
|
||||
)
|
||||
conn_setup.commit()
|
||||
finally:
|
||||
conn_setup.close()
|
||||
|
||||
# POST /mapari cu denumire (L14-S6: form include denumire hidden)
|
||||
resp = client.post(
|
||||
"/mapari",
|
||||
data={
|
||||
"cod_op_service": "OP-REV",
|
||||
"cod_prestatie": "OE-3",
|
||||
"denumire": "Revizie anuala",
|
||||
"csrf_token": "",
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
# Verifica ca shared_mappings contine intrarea
|
||||
conn_check = get_connection()
|
||||
try:
|
||||
from app.shared_store import lookup_shared_gold
|
||||
row = lookup_shared_gold(conn_check, "Revizie anuala")
|
||||
assert row is not None, "record_human_validation nu a scris in shared_mappings"
|
||||
assert row["cod_prestatie"] == "OE-3"
|
||||
finally:
|
||||
conn_check.close()
|
||||
|
||||
|
||||
def test_record_human_validation_la_mapeaza_inline(env, client):
|
||||
"""POST /trimitere/{id}/mapeaza -> record_human_validation scrie in shared_mappings.
|
||||
|
||||
Testul verifica ca GOLD partajat se populeaza la maparea inline din panoul de detaliu.
|
||||
"""
|
||||
from app.db import get_connection
|
||||
import json
|
||||
|
||||
# Setup submission needs_mapping
|
||||
conn_setup = get_connection()
|
||||
try:
|
||||
conn_setup.executemany(
|
||||
"INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
|
||||
[("OE-1", "REPARATIE"), ("OE-3", "REVIZIE")],
|
||||
)
|
||||
conn_setup.execute(
|
||||
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
|
||||
"VALUES (1, 'needs_mapping', ?, 'key-inline-001')",
|
||||
(json.dumps({
|
||||
"vin": "WVWZZZ1KZAW004444",
|
||||
"data_prestatie": "2026-06-15",
|
||||
"odometru_final": 100000,
|
||||
"prestatii": [{"cod_op_service": "OP-REP", "denumire": "Reparatie chiulasa"}],
|
||||
}),),
|
||||
)
|
||||
conn_setup.commit()
|
||||
|
||||
# Preia ID-ul submission-ului
|
||||
sid = conn_setup.execute("SELECT id FROM submissions WHERE idempotency_key='key-inline-001'").fetchone()["id"]
|
||||
finally:
|
||||
conn_setup.close()
|
||||
|
||||
resp = client.post(
|
||||
f"/trimitere/{sid}/mapeaza",
|
||||
data={
|
||||
"cod_op_service": "OP-REP",
|
||||
"cod_prestatie": "OE-1",
|
||||
"csrf_token": "",
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
# Verifica shared_mappings
|
||||
conn_check = get_connection()
|
||||
try:
|
||||
from app.shared_store import lookup_shared_gold
|
||||
row = lookup_shared_gold(conn_check, "Reparatie chiulasa")
|
||||
assert row is not None, "record_human_validation nu a scris in shared_mappings pentru mapeaza inline"
|
||||
assert row["cod_prestatie"] == "OE-1"
|
||||
finally:
|
||||
conn_check.close()
|
||||
|
||||
|
||||
def test_mapare_salvata_fara_denumire_nu_polueaza_gold(env, client):
|
||||
"""Bug fix (code-review 5.15): editarea unei mapari salvate FARA denumire NU scrie
|
||||
o intrare bogus in GOLD partajat (cheiata pe cod_op_service in loc de denumire umana).
|
||||
|
||||
Formularul din _mapari.html nu trimite denumire; vechiul fallback `denumire or
|
||||
cod_op_service` scria shared_mappings cheiat pe cod_op_service -> lookup_shared_gold
|
||||
(pe denumirea umana) nu il potrivea niciodata -> poluare. Fix: _record_gold_validation
|
||||
sare scrierea cand denumire lipseste sau == cod_op_service.
|
||||
"""
|
||||
from app.db import get_connection
|
||||
|
||||
conn_setup = get_connection()
|
||||
try:
|
||||
conn_setup.execute(
|
||||
"INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
|
||||
("OE-1", "REPARATIE"),
|
||||
)
|
||||
conn_setup.commit()
|
||||
finally:
|
||||
conn_setup.close()
|
||||
|
||||
# Editare mapare salvata FARA denumire (ca formularul real din _mapari.html).
|
||||
resp = client.post(
|
||||
"/mapari/salvate",
|
||||
data={
|
||||
"cod_op_service": "OP-SALV",
|
||||
"cod_prestatie": "OE-1",
|
||||
"csrf_token": "",
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
conn_check = get_connection()
|
||||
try:
|
||||
from app.shared_store import lookup_shared_gold
|
||||
# NICIO intrare bogus cheiata pe cod_op_service.
|
||||
assert lookup_shared_gold(conn_check, "OP-SALV") is None, (
|
||||
"GOLD partajat poluat cu cod_op_service ca si cheie (denumire lipsa)"
|
||||
)
|
||||
finally:
|
||||
conn_check.close()
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Separare structurala #13 (redundant cu test_shared_store dar explicit L14) #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_separare_silver_din_resolve_prestatii():
|
||||
"""#13: resolve_prestatii nu citeste mapping_suggestions (SILVER)."""
|
||||
from app.mapping import resolve_prestatii
|
||||
|
||||
# Apelam fara conn (pur) — SILVER nu e parametru si nu e accesat
|
||||
resolved, unmapped = resolve_prestatii(
|
||||
[{"cod_op_service": "OP-TEST", "denumire": "Test silver"}],
|
||||
{}, # mapping gol
|
||||
)
|
||||
assert resolved[0]["cod_prestatie"] is None
|
||||
assert len(unmapped) == 1
|
||||
|
||||
|
||||
def test_separare_shared_gold_din_resolve_prestatii():
|
||||
"""#13: resolve_prestatii nu citeste shared_mappings (GOLD partajat)."""
|
||||
from app.mapping import resolve_prestatii
|
||||
|
||||
resolved, unmapped = resolve_prestatii(
|
||||
[{"cod_op_service": "OP-TEST2", "denumire": "Test gold partajat"}],
|
||||
{}, # mapping gol
|
||||
)
|
||||
assert resolved[0]["cod_prestatie"] is None
|
||||
assert len(unmapped) == 1
|
||||
Reference in New Issue
Block a user