#!/usr/bin/env python3 """Test Meilisearch integration (Mejora #2). Validates: 1. Meilisearch health 2. Search returns results faster than PostgreSQL tsvector 3. Fallback to PostgreSQL when Meilisearch is unreachable 4. Results are enriched with local stock """ import os import sys import time sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) os.environ.setdefault('MASTER_DB_URL', 'postgresql://postgres@/nexus_autoparts') os.environ.setdefault('TENANT_DB_URL_TEMPLATE', 'postgresql://postgres@/{db_name}') os.environ.setdefault('POS_JWT_SECRET', 'test-secret-12345678901234567890123456789012') from services.meili_search import health_check, search_parts from services.catalog_service import smart_search, _search_meili_fallback from tenant_db import get_master_conn, get_tenant_conn_by_dbname RED = '\033[91m' GREEN = '\033[92m' YELLOW = '\033[93m' RESET = '\033[0m' def print_result(name, passed, detail=""): status = f"{GREEN}PASS{RESET}" if passed else f"{RED}FAIL{RESET}" print(f" [{status}] {name}" + (f" — {detail}" if detail else "")) def main(): print("=" * 60) print("MEILISEARCH — VALIDATION SUITE") print("=" * 60) passed = 0 failed = 0 # ── Test 1: Health check ──────────────────────────────────── print("\n[1] Meilisearch Health") if health_check(): print_result("Health", True, "available") passed += 1 else: print_result("Health", False, "unreachable") failed += 1 print(f"\n{RED}Meilisearch down — aborting{RESET}") return passed, failed # ── Test 2: Direct Meilisearch search ─────────────────────── print("\n[2] Direct Meilisearch Search") try: t0 = time.perf_counter() result = search_parts("filtro de aceite", limit=10) t_meili = (time.perf_counter() - t0) * 1000 if result and result.get('hits'): hits = result['hits'] print_result("Search", True, f"{len(hits)} hits in {t_meili:.1f} ms") passed += 1 else: print_result("Search", False, "no hits") failed += 1 except Exception as e: print_result("Search", False, str(e)) failed += 1 # ── Test 3: smart_search uses Meilisearch ─────────────────── print("\n[3] smart_search() with Meilisearch") try: master = get_master_conn() tenant = get_tenant_conn_by_dbname('tenant_acct_test') # Meilisearch path t0 = time.perf_counter() meili_results = smart_search(master, "filtro aceite", tenant, branch_id=None, limit=10) t_smart = (time.perf_counter() - t0) * 1000 # Pure PostgreSQL path (force fallback by using a query that might not match) t0 = time.perf_counter() pg_results = smart_search(master, "zzzzzzzz", tenant, branch_id=None, limit=10) t_pg = (time.perf_counter() - t0) * 1000 master.close() tenant.close() if meili_results and len(meili_results) > 0: print_result("Meili path", True, f"{len(meili_results)} results in {t_smart:.1f} ms") passed += 1 else: print_result("Meili path", False, "no results") failed += 1 print(f" PostgreSQL fallback (no-match query): {t_pg:.1f} ms") except Exception as e: print_result("smart_search", False, str(e)) failed += 1 # ── Test 4: _search_meili_fallback returns None on error ──── print("\n[4] Fallback behavior") try: master = get_master_conn() # Pass a garbage URL to force failure old_url = os.environ.get('MEILI_URL') os.environ['MEILI_URL'] = 'http://localhost:99999' from services.meili_search import reset_client reset_client() fallback = _search_meili_fallback(master, "aceite", 10) # Restore if old_url: os.environ['MEILI_URL'] = old_url else: os.environ.pop('MEILI_URL', None) reset_client() master.close() if fallback is None: print_result("Fallback", True, "returns None on unreachable Meilisearch") passed += 1 else: print_result("Fallback", False, f"unexpected return: {fallback}") failed += 1 except Exception as e: print_result("Fallback", False, str(e)) failed += 1 # ── Summary ───────────────────────────────────────────────── print("\n" + "=" * 60) print(f"RESULTS: {GREEN}{passed} passed{RESET}, {RED}{failed} failed{RESET}") print("=" * 60) return passed, failed if __name__ == '__main__': passed, failed = main() sys.exit(0 if failed == 0 else 1)