Acoperire 49 tests offline (fără Oracle real): test_comanda_helpers (16): _build_pc_nr toate prefixele VFP + fallback, _build_sir_id_operatii csv + limit 4000 chars, _PREFIX_MAP regression. test_router_authorization (9): _company_id fallback JWT companies[0], 403 firmă neautorizată, 400 companies[] gol, string→int coercion; _server_id extragere din request.state. test_lookup_endpoints (15): cache hit/miss per schema pentru tip_deviz, masini, asiguratori, inspectori (per-asig), operatii; LIKE escape %/_/\; min 2 chars short-circuit; server_id propagat la get_connection. test_partener_create (9): 5 Pydantic validation (denumire min 2, id_firma ge 1, cui opțional), 4 service mocked (happy path, 409 duplicat CUI, fără CUI, lipsă GRANT → 500 log.critical). Pattern mock Oracle: fake context managers (async get_connection + sync cursor), monkeypatch pe lookup_service.get_schema (not _context, din cauza binding copy la import). Rulare: pytest backend/modules/service_auto/tests/ -q → 62 passed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
198 lines
6.7 KiB
Python
198 lines
6.7 KiB
Python
"""
|
|
Unit tests pentru creare partener nou:
|
|
- Validare PartnerCreateRequest (denumire min_length=2, id_firma ge=1)
|
|
- LookupService.create_partener — happy path + duplicat CUI (409) + lipsă GRANT (500)
|
|
|
|
Folosește mock pentru oracle_pool și _context.get_schema (fără DB).
|
|
"""
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import oracledb
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
from pydantic import ValidationError
|
|
|
|
from backend.modules.service_auto.schemas.comanda import PartnerCreateRequest
|
|
from backend.modules.service_auto.services.lookup_service import LookupService
|
|
|
|
|
|
# ---- PartnerCreateRequest validation ----
|
|
|
|
def test_partner_request_denumire_too_short_raises():
|
|
"""denumire cu 1 caracter → ValidationError (min_length=2)."""
|
|
with pytest.raises(ValidationError) as exc:
|
|
PartnerCreateRequest(denumire="X", id_firma=167)
|
|
assert "denumire" in str(exc.value).lower()
|
|
|
|
|
|
def test_partner_request_denumire_empty_raises():
|
|
"""denumire goală → ValidationError."""
|
|
with pytest.raises(ValidationError):
|
|
PartnerCreateRequest(denumire="", id_firma=167)
|
|
|
|
|
|
def test_partner_request_minimal_valid():
|
|
"""Doar denumire + id_firma → CUI și adresa optionale = None."""
|
|
req = PartnerCreateRequest(denumire="ACME SRL", id_firma=167)
|
|
assert req.denumire == "ACME SRL"
|
|
assert req.cui is None
|
|
assert req.adresa is None
|
|
assert req.id_firma == 167
|
|
|
|
|
|
def test_partner_request_full():
|
|
req = PartnerCreateRequest(
|
|
denumire="ACME SRL",
|
|
cui="RO12345678",
|
|
adresa="Str. Exemplu nr. 1, București",
|
|
id_firma=167,
|
|
)
|
|
assert req.cui == "RO12345678"
|
|
assert req.adresa is not None and req.adresa.startswith("Str.")
|
|
|
|
|
|
def test_partner_request_id_firma_zero_raises():
|
|
"""id_firma=0 → ValidationError (ge=1)."""
|
|
with pytest.raises(ValidationError):
|
|
PartnerCreateRequest(denumire="ACME", id_firma=0)
|
|
|
|
|
|
# ---- LookupService.create_partener (mocked) ----
|
|
|
|
def _make_pool_ctx(cursor_mock):
|
|
"""
|
|
Construiește un context manager async pentru oracle_pool.get_connection.
|
|
Returnează: pool_mock cu .get_connection() → async ctx → conn cu .cursor()
|
|
sync ctx care returnează cursor_mock.
|
|
"""
|
|
conn_mock = MagicMock()
|
|
conn_mock.cursor.return_value.__enter__.return_value = cursor_mock
|
|
conn_mock.cursor.return_value.__exit__.return_value = None
|
|
conn_mock.commit = MagicMock()
|
|
|
|
async_ctx = MagicMock()
|
|
async_ctx.__aenter__ = AsyncMock(return_value=conn_mock)
|
|
async_ctx.__aexit__ = AsyncMock(return_value=None)
|
|
|
|
pool_mock = MagicMock()
|
|
pool_mock.get_connection = MagicMock(return_value=async_ctx)
|
|
return pool_mock, conn_mock
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_partener_happy_path():
|
|
"""
|
|
Cazul nominal:
|
|
- Pre-check CUI: nicio coliziune (fetchone() → None)
|
|
- SELECT MAX(id_part)+1 → 4242
|
|
- INSERT reușește; conn.commit() apelat; întoarce PartenerItem.
|
|
"""
|
|
cursor = MagicMock()
|
|
# fetchone secvență: pre-check CUI (None), SELECT MAX (4242,)
|
|
cursor.fetchone.side_effect = [None, (4242,)]
|
|
cursor.execute = MagicMock()
|
|
|
|
pool_mock, conn_mock = _make_pool_ctx(cursor)
|
|
|
|
with patch(
|
|
"backend.modules.service_auto.services.lookup_service.oracle_pool",
|
|
pool_mock,
|
|
), patch(
|
|
"backend.modules.service_auto.services.lookup_service.get_schema",
|
|
new=AsyncMock(return_value="MARIUSM_AUTO"),
|
|
):
|
|
req = PartnerCreateRequest(
|
|
denumire="ACME SRL", cui="RO12345678", adresa="Str. X", id_firma=167,
|
|
)
|
|
result = await LookupService.create_partener(req, server_id="mariusm_test")
|
|
|
|
assert result.id_part == 4242
|
|
assert result.denumire == "ACME SRL"
|
|
conn_mock.commit.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_partener_duplicate_cui_raises_409():
|
|
"""Pre-check CUI găsește rând existent → HTTPException 409, NU INSERT."""
|
|
cursor = MagicMock()
|
|
cursor.fetchone.return_value = (1,) # CUI deja există
|
|
cursor.execute = MagicMock()
|
|
|
|
pool_mock, conn_mock = _make_pool_ctx(cursor)
|
|
|
|
with patch(
|
|
"backend.modules.service_auto.services.lookup_service.oracle_pool",
|
|
pool_mock,
|
|
), patch(
|
|
"backend.modules.service_auto.services.lookup_service.get_schema",
|
|
new=AsyncMock(return_value="MARIUSM_AUTO"),
|
|
):
|
|
req = PartnerCreateRequest(
|
|
denumire="ACME SRL", cui="RO12345678", id_firma=167,
|
|
)
|
|
with pytest.raises(HTTPException) as exc:
|
|
await LookupService.create_partener(req, server_id="mariusm_test")
|
|
|
|
assert exc.value.status_code == 409
|
|
assert "CUI" in exc.value.detail
|
|
conn_mock.commit.assert_not_called()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_partener_no_cui_skips_precheck():
|
|
"""Fără CUI → pre-check sărit, doar SELECT MAX + INSERT."""
|
|
cursor = MagicMock()
|
|
cursor.fetchone.side_effect = [(99,)] # doar SELECT MAX
|
|
cursor.execute = MagicMock()
|
|
|
|
pool_mock, conn_mock = _make_pool_ctx(cursor)
|
|
|
|
with patch(
|
|
"backend.modules.service_auto.services.lookup_service.oracle_pool",
|
|
pool_mock,
|
|
), patch(
|
|
"backend.modules.service_auto.services.lookup_service.get_schema",
|
|
new=AsyncMock(return_value="MARIUSM_AUTO"),
|
|
):
|
|
req = PartnerCreateRequest(denumire="Persoană fizică", id_firma=167)
|
|
result = await LookupService.create_partener(req, server_id=None)
|
|
|
|
assert result.id_part == 99
|
|
conn_mock.commit.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_partener_missing_grant_raises_500():
|
|
"""ORA-01031 (lipsă INSERT privilege) → HTTPException 500 cu mesaj clar."""
|
|
cursor = MagicMock()
|
|
# CUI furnizat → fetchone secvență: pre-check (None=fără duplicat), SELECT MAX (1,)
|
|
cursor.fetchone.side_effect = [None, (1,)]
|
|
# INSERT primește ORA-01031
|
|
err = oracledb.DatabaseError()
|
|
err.args = (MagicMock(code=1031, message="ORA-01031: insufficient privileges"),)
|
|
|
|
def execute_side_effect(sql, *args, **kw):
|
|
del args, kw
|
|
if "INSERT" in sql.upper():
|
|
raise err
|
|
cursor.execute.side_effect = execute_side_effect
|
|
|
|
pool_mock, conn_mock = _make_pool_ctx(cursor)
|
|
|
|
with patch(
|
|
"backend.modules.service_auto.services.lookup_service.oracle_pool",
|
|
pool_mock,
|
|
), patch(
|
|
"backend.modules.service_auto.services.lookup_service.get_schema",
|
|
new=AsyncMock(return_value="MARIUSM_AUTO"),
|
|
):
|
|
req = PartnerCreateRequest(
|
|
denumire="ACME SRL", cui="RO99999999", id_firma=167,
|
|
)
|
|
with pytest.raises(HTTPException) as exc:
|
|
await LookupService.create_partener(req, server_id="mariusm_test")
|
|
|
|
assert exc.value.status_code == 500
|
|
assert "privilegii" in exc.value.detail.lower()
|
|
conn_mock.commit.assert_not_called()
|