""" Oracle Integration Tests for GoMag Import Manager ================================================== Requires Oracle connectivity and valid .env configuration. Usage: cd /mnt/e/proiecte/vending/gomag python api/test_integration.py Note: Run from the project root so that relative paths in .env resolve correctly. The .env file is read from the api/ directory. """ import os import sys # Set working directory to project root so relative paths in .env work _script_dir = os.path.dirname(os.path.abspath(__file__)) _project_root = os.path.dirname(_script_dir) os.chdir(_project_root) # Load .env from api/ before importing app modules from dotenv import load_dotenv _env_path = os.path.join(_script_dir, ".env") load_dotenv(_env_path, override=True) # Add api/ to path so app package is importable sys.path.insert(0, _script_dir) from fastapi.testclient import TestClient # Import the app (triggers lifespan on first TestClient use) from app.main import app results = [] def record(name: str, passed: bool, detail: str = ""): status = "PASS" if passed else "FAIL" msg = f"[{status}] {name}" if detail: msg += f" -- {detail}" print(msg) results.append(passed) # --------------------------------------------------------------------------- # Test A: GET /health — Oracle must show as connected # --------------------------------------------------------------------------- def test_health(client: TestClient): test_name = "GET /health - Oracle connected" try: resp = client.get("/health") assert resp.status_code == 200, f"HTTP {resp.status_code}" body = resp.json() oracle_status = body.get("oracle", "") sqlite_status = body.get("sqlite", "") assert oracle_status == "ok", f"oracle={oracle_status!r}" assert sqlite_status == "ok", f"sqlite={sqlite_status!r}" record(test_name, True, f"oracle={oracle_status}, sqlite={sqlite_status}") except Exception as exc: record(test_name, False, str(exc)) # --------------------------------------------------------------------------- # Test B: Mappings CRUD cycle # POST create -> GET list (verify present) -> PUT update -> DELETE -> verify # --------------------------------------------------------------------------- def test_mappings_crud(client: TestClient): test_sku = "TEST_INTEG_SKU_001" test_codmat = "TEST_CODMAT_001" # -- CREATE -- try: resp = client.post("/api/mappings", json={ "sku": test_sku, "codmat": test_codmat, "cantitate_roa": 2.5, "procent_pret": 80.0 }) assert resp.status_code == 200, f"HTTP {resp.status_code}" body = resp.json() assert body.get("success") is True, f"create returned: {body}" record("POST /api/mappings - create mapping", True, f"sku={test_sku}, codmat={test_codmat}") except Exception as exc: record("POST /api/mappings - create mapping", False, str(exc)) # Skip the rest of CRUD if creation failed return # -- LIST (verify present) -- try: resp = client.get("/api/mappings", params={"search": test_sku}) assert resp.status_code == 200, f"HTTP {resp.status_code}" body = resp.json() mappings = body.get("mappings", []) found = any( m["sku"] == test_sku and m["codmat"] == test_codmat for m in mappings ) assert found, f"mapping not found in list; got {mappings}" record("GET /api/mappings - mapping visible after create", True, f"total={body.get('total')}") except Exception as exc: record("GET /api/mappings - mapping visible after create", False, str(exc)) # -- UPDATE -- try: resp = client.put(f"/api/mappings/{test_sku}/{test_codmat}", json={ "cantitate_roa": 3.0, "procent_pret": 90.0 }) assert resp.status_code == 200, f"HTTP {resp.status_code}" body = resp.json() assert body.get("success") is True, f"update returned: {body}" record("PUT /api/mappings/{sku}/{codmat} - update mapping", True, "cantitate_roa=3.0, procent_pret=90.0") except Exception as exc: record("PUT /api/mappings/{sku}/{codmat} - update mapping", False, str(exc)) # -- DELETE (soft: sets activ=0) -- try: resp = client.delete(f"/api/mappings/{test_sku}/{test_codmat}") assert resp.status_code == 200, f"HTTP {resp.status_code}" body = resp.json() assert body.get("success") is True, f"delete returned: {body}" record("DELETE /api/mappings/{sku}/{codmat} - soft delete", True) except Exception as exc: record("DELETE /api/mappings/{sku}/{codmat} - soft delete", False, str(exc)) # -- VERIFY: after soft-delete activ=0, listing without search filter should # show it as activ=0 (it is still in DB). Search for it and confirm activ=0. -- try: resp = client.get("/api/mappings", params={"search": test_sku}) assert resp.status_code == 200, f"HTTP {resp.status_code}" body = resp.json() mappings = body.get("mappings", []) deleted = any( m["sku"] == test_sku and m["codmat"] == test_codmat and m.get("activ") == 0 for m in mappings ) assert deleted, ( f"expected activ=0 for deleted mapping, got: " f"{[m for m in mappings if m['sku'] == test_sku]}" ) record("GET /api/mappings - mapping has activ=0 after delete", True) except Exception as exc: record("GET /api/mappings - mapping has activ=0 after delete", False, str(exc)) # --------------------------------------------------------------------------- # Test C: GET /api/articles/search?q= — must return results # --------------------------------------------------------------------------- def test_articles_search(client: TestClient): # Use a short generic term that should exist in most ROA databases search_terms = ["01", "A", "PH"] test_name = "GET /api/articles/search - returns results" try: found_results = False last_body = {} for term in search_terms: resp = client.get("/api/articles/search", params={"q": term}) assert resp.status_code == 200, f"HTTP {resp.status_code}" body = resp.json() last_body = body results_list = body.get("results", []) if results_list: found_results = True record(test_name, True, f"q={term!r} returned {len(results_list)} results; " f"first={results_list[0].get('codmat')!r}") break if not found_results: # Search returned empty — not necessarily a failure if DB is empty, # but we flag it as a warning. record(test_name, False, f"all search terms returned empty; last response: {last_body}") except Exception as exc: record(test_name, False, str(exc)) # --------------------------------------------------------------------------- # Test D: POST /api/validate/scan — triggers scan of JSON folder # --------------------------------------------------------------------------- def test_validate_scan(client: TestClient): test_name = "POST /api/validate/scan - returns valid response" try: resp = client.post("/api/validate/scan") assert resp.status_code == 200, f"HTTP {resp.status_code}" body = resp.json() # Must have at least these keys for key in ("json_files", "orders", "skus"): # "orders" may be "total_orders" if orders exist; "orders" key only # present in the "No orders found" path. pass # Accept both shapes: no-orders path has "orders" key, full path has "total_orders" has_shape = "json_files" in body and ("orders" in body or "total_orders" in body) assert has_shape, f"unexpected response shape: {body}" record(test_name, True, f"json_files={body.get('json_files')}, " f"orders={body.get('total_orders', body.get('orders'))}") except Exception as exc: record(test_name, False, str(exc)) # --------------------------------------------------------------------------- # Test E: GET /api/sync/history — must return a list structure # --------------------------------------------------------------------------- def test_sync_history(client: TestClient): test_name = "GET /api/sync/history - returns list structure" try: resp = client.get("/api/sync/history") assert resp.status_code == 200, f"HTTP {resp.status_code}" body = resp.json() assert "runs" in body, f"missing 'runs' key; got keys: {list(body.keys())}" assert isinstance(body["runs"], list), f"'runs' is not a list: {type(body['runs'])}" assert "total" in body, f"missing 'total' key" record(test_name, True, f"total={body.get('total')}, page={body.get('page')}, pages={body.get('pages')}") except Exception as exc: record(test_name, False, str(exc)) # --------------------------------------------------------------------------- # Main runner # --------------------------------------------------------------------------- def main(): print("=" * 60) print("GoMag Import Manager - Oracle Integration Tests") print(f"Env file: {_env_path}") print(f"Oracle DSN: {os.environ.get('ORACLE_DSN', '(not set)')}") print("=" * 60) with TestClient(app) as client: test_health(client) test_mappings_crud(client) test_articles_search(client) test_validate_scan(client) test_sync_history(client) passed = sum(results) total = len(results) print("=" * 60) print(f"Summary: {passed}/{total} tests passed") if passed < total: print("Some tests FAILED — review output above for details.") sys.exit(1) else: print("All tests PASSED.") if __name__ == "__main__": main()