my-rag / tests /test_api_nextjs.ts
voice-rag's picture
Update tests/test_api_nextjs.ts
398c9c8 verified
Raw
History Blame Contribute Delete
11.1 kB
/**
* 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);
}