-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest.js
More file actions
125 lines (100 loc) · 6.41 KB
/
test.js
File metadata and controls
125 lines (100 loc) · 6.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
* src/test.js — RENDER-OPTIMISED (stateless API shape)
* ───────────────────────────────────────────────────────
* Unit tests run with no API key.
* Integration tests fire live Gemini calls if GEMINI_API_KEY is set,
* using the new stateless { message, history } payload.
*
* GITHUB PATH → src/test.js
*/
"use strict";
require("dotenv").config();
const { getFullKnowledgeBase, getRelevantContext, SECTIONS } = require("./knowledgeBase");
const { GoogleGenerativeAI } = require("@google/generative-ai");
const G = "\x1b[32m", R = "\x1b[31m", Y = "\x1b[33m", D = "\x1b[0m";
let passed = 0, failed = 0;
function assert(condition, label) {
if (condition) { console.log(` ${G}✓${D} ${label}`); passed++; }
else { console.log(` ${R}✗${D} ${label}`); failed++; }
}
// ══════════════════════════════════════════════════════════════════════════════
// 1. UNIT TESTS — knowledgeBase.js
// ══════════════════════════════════════════════════════════════════════════════
console.log(`\n${Y}▶ Unit Tests — knowledgeBase.js${D}\n`);
const fullKB = getFullKnowledgeBase();
assert(fullKB.length > 0, "getFullKnowledgeBase() non-empty");
assert(fullKB.includes("Skedulelt"), "Contains product name");
assert(fullKB.includes("Trinidad & Tobago"), "Contains T&T");
assert(fullKB.includes("Payment Processing"), "Contains payment section");
assert(fullKB.includes("No-Show Tracking"), "Contains cancellation policy");
assert(SECTIONS.length === 4, "4 sections");
const bookingCtx = getRelevantContext("I want to book an appointment");
assert(bookingCtx.matched.length > 0, "Booking → ≥1 section");
assert(bookingCtx.matched.some(t => t.includes("Customer")), "Booking → Customer FAQ");
const payCtx = getRelevantContext("how do I get paid as a provider?");
assert(payCtx.matched.length > 0, "Payment → ≥1 section");
const unknownCtx = getRelevantContext("xyzzy random gibberish");
assert(unknownCtx.fullText === fullKB, "Unknown → full KB fallback");
const cancelCtx = getRelevantContext("I need to cancel my haircut");
assert(cancelCtx.matched.some(t => t.includes("Policies")), "Cancel → Policies");
const secCtx = getRelevantContext("Is my credit card safe?");
assert(secCtx.matched.some(t => t.includes("Policies")), "Security → Policies");
// ══════════════════════════════════════════════════════════════════════════════
// 2. INTEGRATION TESTS — stateless multi-turn via live Gemini
// ══════════════════════════════════════════════════════════════════════════════
const HYPOTHETICAL_QUERIES = [
{ query: "I need to cancel my haircut.", expectKw: ["cancel", "reschedule"] },
{ query: "How do I get paid?", expectKw: ["financials", "earnings", "analytics"] },
{ query: "Is my credit card safe?", expectKw: ["encrypted", "secure", "Firebase"] },
{ query: "How do I see my barber's work?", expectKw: ["portfolio", "Search & Discovery"] },
{ query: "How do I book an appointment?", expectKw: ["Search & Discovery", "time slot"] }
];
// Helper — mirrors the stateless prompt assembly in geminiClient.js
function buildSystemPrompt() {
return `You are the official Skedulelt Support Assistant.\n\nKnowledge base:\n${fullKB}\n\nAnswer only from the KB above.`;
}
async function runIntegration() {
if (!process.env.GEMINI_API_KEY) {
console.log(`\n${Y}⚠ GEMINI_API_KEY not set — skipping live tests.${D}\n`);
return;
}
console.log(`\n${Y}▶ Integration Tests — stateless, live Gemini 1.5 Flash${D}\n`);
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
// Simulate a 5-turn conversation where each turn is a fresh startChat
// with the accumulated history — exactly what the Render-optimised server does.
const conversationHistory = []; // grows each turn
for (const { query, expectKw } of HYPOTHETICAL_QUERIES) {
try {
// Build history array the same way the server does
const geminiHistory = [
{ role: "user", parts: [{ text: buildSystemPrompt() }] },
{ role: "model", parts: [{ text: "Got it. How can I help?" }] },
...conversationHistory.map(t => ({
role : t.role === "assistant" ? "model" : "user",
parts: [{ text: t.text }]
}))
];
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
const chatSession = model.startChat({ history: geminiHistory });
const res = await chatSession.sendMessage(query);
const answer = res.text;
// Accumulate
conversationHistory.push({ role: "user", text: query });
conversationHistory.push({ role: "assistant", text: answer });
const anyMatch = expectKw.some(kw => answer.toLowerCase().includes(kw.toLowerCase()));
assert(anyMatch, `"${query}" → contains expected keyword`);
console.log(` ${D} ↳ "${answer.slice(0, 90)}…"\n`);
} catch (err) {
console.log(` ${R}✗${D} "${query}" → ${err.message}`);
failed++;
}
}
}
// ── Run ──────────────────────────────────────────────────────────────────────
(async () => {
await runIntegration();
console.log(`\n${Y}══════════════════════════════════════${D}`);
console.log(` Results: ${G}${passed} passed${D} ${failed ? R + failed + " failed" + D : "0 failed"}`);
console.log(`${Y}══════════════════════════════════════${D}\n`);
process.exit(failed > 0 ? 1 : 0);
})();