/** * RAG Space Integration Tests — Next.js / TypeScript * ===================================================== * Run these to verify your HuggingFace RAG Space is working correctly. * * SETUP: * 1. Copy this file into your Next.js project (e.g. app/tests/rag.test.ts) * 2. Set the env vars below to match your Space * 3. Run: npx ts-node app/tests/rag.test.ts * * Or call each function manually from a Next.js API route / page for manual testing. */ // ─── CONFIG — Update these ──────────────────────────────────────────────────── const RAG_BASE_URL = process.env.RAG_SPACE_URL || "https://YOUR-HF-USERNAME-rag-knowledge-base.hf.space"; const RAG_API_KEY = process.env.RAG_API_KEY || "dev-insecure-change-me"; const TEST_COMPANY = "comp_test_001"; // test tenant ID — will be created and deleted // ─── Helper ─────────────────────────────────────────────────────────────────── async function ragFetch( endpoint: string, options: RequestInit = {} ): Promise<{ ok: boolean; status: number; data: any }> { const url = `${RAG_BASE_URL}${endpoint}`; const res = await fetch(url, { ...options, headers: { "Content-Type": "application/json", "X-RAG-API-Key": RAG_API_KEY, ...(options.headers || {}), }, }); let data: any; try { data = await res.json(); } catch { data = { raw: await res.text() }; } return { ok: res.ok, status: res.status, data }; } // ─── Test 1: Health Check ───────────────────────────────────────────────────── export async function testHealth() { console.log("\n🔍 TEST 1: Health Check"); const res = await fetch(`${RAG_BASE_URL}/health`); const data = await res.json(); console.log("Status:", res.status); console.log("Response:", data); console.assert(res.ok, "❌ Health check failed"); console.assert(data.status === "ok" || data.status === "degraded", "❌ Unexpected status"); console.log("✅ Health check passed"); return data; } // ─── Test 2: Provision a Tenant Collection ──────────────────────────────────── export async function testProvision() { console.log("\n🔍 TEST 2: Provision Tenant"); const res = await ragFetch("/api/v1/provision", { method: "POST", body: JSON.stringify({ company_id: TEST_COMPANY }), }); console.log("Status:", res.status); console.log("Response:", res.data); console.assert(res.ok, `❌ Provision failed: ${JSON.stringify(res.data)}`); console.assert( res.data.collection === `kb_${TEST_COMPANY}`, `❌ Wrong collection name: ${res.data.collection}` ); console.log("✅ Provision passed"); return res.data; } // ─── Test 3: Ingest a Text File ─────────────────────────────────────────────── export async function testIngestFile() { console.log("\n🔍 TEST 3: Ingest File"); // Create a simple test document in memory const testContent = ` Company: Bytestream Technologies Return Policy: All products can be returned within 30 days with receipt. Shipping: Standard delivery takes 3-5 business days. Express is 1-2 days. Support Hours: Monday to Saturday, 9 AM to 6 PM PKT. Contact: support@bytestream.pk or call 0300-123-4567. Products: SmartBot AI Package - Rs. 25,000/month. Basic Plan - Rs. 8,000/month. Refund Process: Refunds are processed within 7 business days after return approval. `.trim(); const blob = new Blob([testContent], { type: "text/plain" }); const formData = new FormData(); formData.append("company_id", TEST_COMPANY); formData.append("file", blob, "test_company_kb.txt"); const res = await fetch(`${RAG_BASE_URL}/api/v1/ingest/file`, { method: "POST", headers: { "X-RAG-API-Key": RAG_API_KEY }, body: formData, }); const data = await res.json(); console.log("Status:", res.status); console.log("Response:", data); console.assert(res.ok, `❌ File ingest failed: ${JSON.stringify(data)}`); console.assert(data.chunks_ingested > 0, "❌ No chunks ingested"); console.log(`✅ File ingest passed — ${data.chunks_ingested} chunks ingested`); return data; } // ─── Test 4: Ingest a URL ───────────────────────────────────────────────────── export async function testIngestUrl() { console.log("\n🔍 TEST 4: Ingest URL"); // Use a real publicly accessible page (replace with your client's URL) const testUrl = "https://en.wikipedia.org/wiki/Customer_service"; const res = await ragFetch("/api/v1/ingest/url", { method: "POST", body: JSON.stringify({ company_id: TEST_COMPANY, url: testUrl }), }); console.log("Status:", res.status); console.log("Response:", { ...res.data, collection: res.data.collection }); console.assert(res.ok, `❌ URL ingest failed: ${JSON.stringify(res.data)}`); console.assert(res.data.chunks_ingested > 0, "❌ No chunks ingested from URL"); console.log(`✅ URL ingest passed — ${res.data.chunks_ingested} chunks ingested`); return res.data; } // ─── Test 5: Query — Filler (should skip RAG) ───────────────────────────────── export async function testQueryFiller() { console.log("\n🔍 TEST 5: Query — Filler Phrase (should skip RAG)"); const res = await ragFetch("/api/v1/query", { method: "POST", body: JSON.stringify({ company_id: TEST_COMPANY, query: "Hello", }), }); console.log("Status:", res.status); console.log("needs_rag:", res.data.needs_rag); console.log("search_mode:", res.data.search_mode); console.assert(res.ok, "❌ Query failed"); console.assert(res.data.needs_rag === false, "❌ Should have skipped RAG for 'Hello'"); console.assert(res.data.search_mode === "skipped", "❌ search_mode should be 'skipped'"); console.log("✅ Filler query test passed — RAG correctly skipped"); return res.data; } // ─── Test 6: Query — Real Question (should hit Qdrant) ──────────────────────── export async function testQueryReal() { console.log("\n🔍 TEST 6: Query — Real KB Question (should hit Qdrant)"); const res = await ragFetch("/api/v1/query", { method: "POST", body: JSON.stringify({ company_id: TEST_COMPANY, query: "What is the return policy?", top_k: 3, }), }); console.log("Status:", res.status); console.log("needs_rag:", res.data.needs_rag); console.log("search_mode:", res.data.search_mode); console.log("chunks found:", res.data.chunks.length); console.log("context preview:", res.data.context.substring(0, 200) + "..."); console.assert(res.ok, "❌ Query failed"); console.assert(res.data.needs_rag === true, "❌ Should have triggered RAG"); console.assert(res.data.chunks.length > 0, "❌ Should have found chunks"); console.log("✅ Real query test passed"); return res.data; } // ─── Test 7: Cross-Tenant Isolation Check ──────────────────────────────────── export async function testTenantIsolation() { console.log("\n🔍 TEST 7: Tenant Isolation — Company B cannot read Company A data"); const companyB = "comp_other_tenant_999"; // Query as company B (no data ingested for them) const res = await ragFetch("/api/v1/query", { method: "POST", body: JSON.stringify({ company_id: companyB, query: "What is the return policy?", skip_intent_check: true, // force RAG even without provision }), }); console.log("Status:", res.status); console.log("chunks found:", res.data.chunks?.length ?? "N/A"); // Either 404 (collection doesn't exist) or 0 results — both are correct const isolated = !res.ok || res.data.chunks?.length === 0; console.assert(isolated, "❌ CRITICAL: Company B got Company A's data!"); console.log("✅ Tenant isolation verified — Company B has zero results"); } // ─── Test 8: Collection Info ────────────────────────────────────────────────── export async function testCollectionInfo() { console.log("\n🔍 TEST 8: Collection Info"); const res = await ragFetch(`/api/v1/collections/${TEST_COMPANY}`); console.log("Status:", res.status); console.log("Info:", res.data); console.assert(res.ok, "❌ Collection info failed"); console.assert(res.data.points_count > 0, "❌ No points in collection after ingestion"); console.log(`✅ Collection info passed — ${res.data.points_count} points stored`); } // ─── Test 9: Cleanup — Delete Test Collection ───────────────────────────────── export async function testCleanup() { console.log("\n🔍 TEST 9: Cleanup — Delete Test Collection"); const res = await ragFetch("/api/v1/delete", { method: "DELETE", body: JSON.stringify({ company_id: TEST_COMPANY, confirm: true }), }); console.log("Status:", res.status); console.log("Response:", res.data); console.assert(res.ok, `❌ Delete failed: ${JSON.stringify(res.data)}`); console.log("✅ Cleanup passed — test collection deleted"); } // ─── Run All Tests ──────────────────────────────────────────────────────────── export async function runAllTests() { console.log("═".repeat(60)); console.log(" RAG SPACE — FULL INTEGRATION TEST SUITE"); console.log(` Target: ${RAG_BASE_URL}`); console.log("═".repeat(60)); try { await testHealth(); await testProvision(); await testIngestFile(); // Wait a moment for Qdrant to index console.log("\n⏳ Waiting 2s for Qdrant indexing..."); await new Promise((r) => setTimeout(r, 2000)); await testQueryFiller(); await testQueryReal(); await testTenantIsolation(); await testCollectionInfo(); await testCleanup(); console.log("\n" + "═".repeat(60)); console.log(" ✅ ALL TESTS PASSED — RAG Space is fully operational"); console.log("═".repeat(60)); } catch (err) { console.error("\n❌ TEST SUITE FAILED:", err); throw err; } } // Auto-run when executed directly // In Next.js, import and call runAllTests() from a test page or API route if (typeof window === "undefined") { runAllTests().catch(console.error); }