import uuid from datetime import UTC, datetime import pytest from httpx import ASGITransport, AsyncClient from app.main import app async def _get_tenant_id(client, auth_headers): me = await client.get("/api/auth/me", headers=auth_headers) return me.json()["tenant_id"] @pytest.mark.asyncio async def test_full_sync_returns_all_tables(client, auth_headers): r = await client.get("/api/sync/full", headers=auth_headers) assert r.status_code == 200 data = r.json() assert "tables" in data and "synced_at" in data assert "vehicles" in data["tables"] assert "catalog_marci" in data["tables"] assert "orders" in data["tables"] assert "mecanici" in data["tables"] @pytest.mark.asyncio async def test_sync_push_insert_vehicle(client, auth_headers): # Get tenant_id from /me me = await client.get("/api/auth/me", headers=auth_headers) tenant_id = me.json()["tenant_id"] vid = str(uuid.uuid4()) now = datetime.now(UTC).isoformat() r = await client.post( "/api/sync/push", headers=auth_headers, json={ "operations": [ { "table": "vehicles", "id": vid, "operation": "INSERT", "data": { "id": vid, "tenant_id": tenant_id, "nr_inmatriculare": "CTA01ABC", "client_nume": "Popescu", "created_at": now, "updated_at": now, }, "timestamp": now, } ] }, ) assert r.status_code == 200 assert r.json()["applied"] == 1 # Verify via full sync full = await client.get("/api/sync/full", headers=auth_headers) vehicles = full.json()["tables"]["vehicles"] assert len(vehicles) == 1 assert vehicles[0]["nr_inmatriculare"] == "CTA01ABC" @pytest.mark.asyncio async def test_sync_changes_since(client, auth_headers): me = await client.get("/api/auth/me", headers=auth_headers) tenant_id = me.json()["tenant_id"] before = datetime.now(UTC).isoformat() vid = str(uuid.uuid4()) now = datetime.now(UTC).isoformat() await client.post( "/api/sync/push", headers=auth_headers, json={ "operations": [ { "table": "vehicles", "id": vid, "operation": "INSERT", "data": { "id": vid, "tenant_id": tenant_id, "nr_inmatriculare": "B99XYZ", "created_at": now, "updated_at": now, }, "timestamp": now, } ] }, ) r = await client.get( f"/api/sync/changes?since={before}", headers=auth_headers ) assert r.status_code == 200 data = r.json() assert "vehicles" in data["tables"] @pytest.mark.asyncio async def test_sync_push_rejects_wrong_tenant(client, auth_headers): now = datetime.now(UTC).isoformat() vid = str(uuid.uuid4()) r = await client.post( "/api/sync/push", headers=auth_headers, json={ "operations": [ { "table": "vehicles", "id": vid, "operation": "INSERT", "data": { "id": vid, "tenant_id": "wrong-tenant-id", "nr_inmatriculare": "HACK", "created_at": now, "updated_at": now, }, "timestamp": now, } ] }, ) assert r.status_code == 200 # Wrong tenant_id is rejected (skipped) assert r.json()["applied"] == 0 @pytest.mark.asyncio async def test_sync_push_insert_order_with_frontend_fields(client, auth_headers): """Frontend sends nr_comanda, client_nume, nr_auto, marca_denumire, model_denumire. Backend must accept these without 500.""" tenant_id = await _get_tenant_id(client, auth_headers) # First insert a vehicle vid = str(uuid.uuid4()) now = datetime.now(UTC).isoformat() await client.post( "/api/sync/push", headers=auth_headers, json={ "operations": [ { "table": "vehicles", "id": vid, "operation": "INSERT", "data": { "id": vid, "tenant_id": tenant_id, "nr_inmatriculare": "B01TST", "client_nume": "Ion Popescu", "client_telefon": "0722000000", "client_cod_fiscal": "RO12345", "serie_sasiu": "WBA1234567890", "created_at": now, "updated_at": now, }, "timestamp": now, } ] }, ) # Now insert an order like the frontend does oid = str(uuid.uuid4()) r = await client.post( "/api/sync/push", headers=auth_headers, json={ "operations": [ { "table": "orders", "id": oid, "operation": "INSERT", "data": { "id": oid, "tenant_id": tenant_id, "nr_comanda": "CMD-ABC123", "data_comanda": now, "vehicle_id": vid, "tip_deviz_id": None, "status": "DRAFT", "km_intrare": 50000, "observatii": "Test order", "client_nume": "Ion Popescu", "client_telefon": "0722000000", "nr_auto": "B01TST", "marca_denumire": "Dacia", "model_denumire": "Logan", "created_at": now, "updated_at": now, }, "timestamp": now, } ] }, ) assert r.status_code == 200 result = r.json() assert result["applied"] == 1 assert result["conflicts"] == [] # Verify the order appears in full sync full = await client.get("/api/sync/full", headers=auth_headers) orders = full.json()["tables"]["orders"] assert any(o["id"] == oid for o in orders) order = next(o for o in orders if o["id"] == oid) assert order["nr_comanda"] == "CMD-ABC123" assert order["client_nume"] == "Ion Popescu" assert order["marca_denumire"] == "Dacia" @pytest.mark.asyncio async def test_sync_push_unknown_columns_ignored(client, auth_headers): """If frontend sends extra fields not in the DB schema, they must be silently ignored (not cause a 500 error).""" tenant_id = await _get_tenant_id(client, auth_headers) vid = str(uuid.uuid4()) now = datetime.now(UTC).isoformat() r = await client.post( "/api/sync/push", headers=auth_headers, json={ "operations": [ { "table": "vehicles", "id": vid, "operation": "INSERT", "data": { "id": vid, "tenant_id": tenant_id, "nr_inmatriculare": "CT99XYZ", "created_at": now, "updated_at": now, "oracle_id": 12345, # frontend-only field "nonexistent_column": "boom", # completely unknown field }, "timestamp": now, } ] }, ) assert r.status_code == 200 result = r.json() assert result["applied"] == 1 assert result["conflicts"] == [] @pytest.mark.asyncio async def test_sync_push_insert_order_line_with_frontend_fields(client, auth_headers): """Frontend sends norma_id, mecanic_id, ordine in order_line — must not cause 500.""" tenant_id = await _get_tenant_id(client, auth_headers) now = datetime.now(UTC).isoformat() # Create order first vid = str(uuid.uuid4()) oid = str(uuid.uuid4()) lid = str(uuid.uuid4()) await client.post( "/api/sync/push", headers=auth_headers, json={ "operations": [ { "table": "vehicles", "id": vid, "operation": "INSERT", "data": {"id": vid, "tenant_id": tenant_id, "nr_inmatriculare": "B02TST", "created_at": now, "updated_at": now}, "timestamp": now, }, { "table": "orders", "id": oid, "operation": "INSERT", "data": { "id": oid, "tenant_id": tenant_id, "nr_comanda": "CMD-XYZ", "status": "DRAFT", "vehicle_id": vid, "created_at": now, "updated_at": now, }, "timestamp": now, }, ] }, ) r = await client.post( "/api/sync/push", headers=auth_headers, json={ "operations": [ { "table": "order_lines", "id": lid, "operation": "INSERT", "data": { "id": lid, "order_id": oid, "tenant_id": tenant_id, "tip": "manopera", "descriere": "Schimb ulei", "norma_id": None, "ore": 1.5, "pret_ora": 150.0, "um": "ora", "cantitate": 0, "pret_unitar": 0, "total": 225.0, "mecanic_id": None, "ordine": 1, "created_at": now, "updated_at": now, }, "timestamp": now, } ] }, ) assert r.status_code == 200 result = r.json() assert result["applied"] == 1 assert result["conflicts"] == []