feat(test): conditionally skip database writes during test run
This commit is contained in:
parent
d74cdc445e
commit
51f82100f9
3 changed files with 152 additions and 74 deletions
|
|
@ -48,6 +48,13 @@ export async function POST(request: NextRequest) {
|
||||||
// Generate candidate embedding
|
// Generate candidate embedding
|
||||||
const embedding = await generateEmbedding(cleanText);
|
const embedding = await generateEmbedding(cleanText);
|
||||||
|
|
||||||
|
const isTest = formData.get("isTest") === "true";
|
||||||
|
|
||||||
|
let candidateId = "00000000-0000-0000-0000-000000000000";
|
||||||
|
let interviewId = "00000000-0000-0000-0000-000000000000";
|
||||||
|
let candidateName = name;
|
||||||
|
|
||||||
|
if (!isTest) {
|
||||||
// Initialize Supabase admin client
|
// Initialize Supabase admin client
|
||||||
const supabase = createServerSupabaseClient();
|
const supabase = createServerSupabaseClient();
|
||||||
|
|
||||||
|
|
@ -88,14 +95,14 @@ export async function POST(request: NextRequest) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTest = formData.get("isTest") === "true";
|
candidateId = candidate.id;
|
||||||
|
interviewId = interview.id;
|
||||||
|
candidateName = candidate.name;
|
||||||
|
}
|
||||||
|
|
||||||
// Call n8n webhook
|
// Call n8n webhook
|
||||||
let n8nResponseData = null;
|
let n8nResponseData = null;
|
||||||
let webhookUrl = process.env.NEXT_PUBLIC_N8N_WEBHOOK_URL;
|
const webhookUrl = process.env.NEXT_PUBLIC_N8N_WEBHOOK_URL;
|
||||||
if (isTest && webhookUrl) {
|
|
||||||
webhookUrl = webhookUrl.replace("/webhook/", "/webhook-test/");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (webhookUrl) {
|
if (webhookUrl) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -105,11 +112,12 @@ export async function POST(request: NextRequest) {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
candidateId: candidate.id,
|
candidateId,
|
||||||
interviewId: interview.id,
|
interviewId,
|
||||||
candidateName: candidate.name,
|
candidateName,
|
||||||
candidateEmail: email,
|
candidateEmail: email,
|
||||||
text: cleanText,
|
text: cleanText,
|
||||||
|
isTest,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -133,10 +141,9 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
candidateId: candidate.id,
|
candidateId,
|
||||||
interviewId: interview.id,
|
interviewId,
|
||||||
candidateName: candidate.name,
|
candidateName,
|
||||||
candidateEmail: email,
|
|
||||||
n8nResponse: n8nResponseData,
|
n8nResponse: n8nResponseData,
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
|
|
||||||
|
|
@ -89,15 +89,27 @@ export async function GET(request: NextRequest) {
|
||||||
|
|
||||||
const { candidateId, interviewId, n8nResponse } = parseResult;
|
const { candidateId, interviewId, n8nResponse } = parseResult;
|
||||||
|
|
||||||
// 5. Verification - Poll the Supabase `scores` table to verify n8n updated the DB
|
|
||||||
let scoreRecord = null;
|
let scoreRecord = null;
|
||||||
|
let verified = false;
|
||||||
let verificationAttempts = 0;
|
let verificationAttempts = 0;
|
||||||
|
|
||||||
|
if (testMode) {
|
||||||
|
// In test mode, we skip DB insertion, so n8n returns the structured response directly.
|
||||||
|
// Let's verify that the response contains the expected evaluation structure.
|
||||||
|
const hasEvaluation = n8nResponse && typeof n8nResponse === "object" && "evaluation" in n8nResponse;
|
||||||
|
const hasAiScore = n8nResponse && typeof n8nResponse === "object" && "ai_score" in n8nResponse;
|
||||||
|
|
||||||
|
if (hasEvaluation && hasAiScore) {
|
||||||
|
verified = true;
|
||||||
|
scoreRecord = n8nResponse;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In live mode, we poll the DB to verify, but we MUST clean it up immediately afterwards
|
||||||
const maxAttempts = 10;
|
const maxAttempts = 10;
|
||||||
const delayMs = 1500;
|
const delayMs = 1500;
|
||||||
|
|
||||||
for (let i = 0; i < maxAttempts; i++) {
|
for (let i = 0; i < maxAttempts; i++) {
|
||||||
verificationAttempts++;
|
verificationAttempts++;
|
||||||
// Wait for n8n execution to finish and write back
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||||
|
|
||||||
const { data: score, error: scoreError } = await supabase
|
const { data: score, error: scoreError } = await supabase
|
||||||
|
|
@ -109,15 +121,25 @@ export async function GET(request: NextRequest) {
|
||||||
|
|
||||||
if (score && !scoreError) {
|
if (score && !scoreError) {
|
||||||
scoreRecord = score;
|
scoreRecord = score;
|
||||||
|
verified = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup immediately to avoid database clutter!
|
||||||
|
if (candidateId && candidateId !== "00000000-0000-0000-0000-000000000000") {
|
||||||
|
console.log(`Cleaning up test candidate: ${candidateId}`);
|
||||||
|
await supabase.from("scores").delete().eq("candidate_id", candidateId);
|
||||||
|
await supabase.from("interviews").delete().eq("candidate_id", candidateId);
|
||||||
|
await supabase.from("candidates").delete().eq("id", candidateId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
status: scoreRecord ? "success" : "completed_with_pending_evaluation",
|
status: verified ? "success" : "completed_with_pending_evaluation",
|
||||||
message: scoreRecord
|
message: verified
|
||||||
? "Pipeline test executed and verified successfully!"
|
? (testMode ? "Pipeline test executed and verified successfully (In-Memory / No DB Write)!" : "Pipeline test executed, verified, and cleaned successfully from DB!")
|
||||||
: "Pipeline executed but AI evaluation write-back is pending or failed.",
|
: "Pipeline executed but AI evaluation verification failed.",
|
||||||
testDetails: {
|
testDetails: {
|
||||||
jobUsed: {
|
jobUsed: {
|
||||||
id: job.id,
|
id: job.id,
|
||||||
|
|
@ -131,7 +153,7 @@ export async function GET(request: NextRequest) {
|
||||||
},
|
},
|
||||||
verification: {
|
verification: {
|
||||||
attempts: verificationAttempts,
|
attempts: verificationAttempts,
|
||||||
verified: !!scoreRecord,
|
verified,
|
||||||
scoreData: scoreRecord,
|
scoreData: scoreRecord,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -371,6 +371,36 @@ async function main() {
|
||||||
position: [700, 300],
|
position: [700, 300],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkIfTestNode = {
|
||||||
|
parameters: {
|
||||||
|
conditions: {
|
||||||
|
options: {
|
||||||
|
caseSensitive: true,
|
||||||
|
leftValue: "",
|
||||||
|
typeValidation: "loose"
|
||||||
|
},
|
||||||
|
combinator: "and",
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
id: "is-test-check",
|
||||||
|
operator: {
|
||||||
|
name: "filter.operator.equals",
|
||||||
|
type: "boolean",
|
||||||
|
operation: "equals"
|
||||||
|
},
|
||||||
|
leftValue: "={{ $('Webhook Trigger').item.json.body.isTest }}",
|
||||||
|
rightValue: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "check-if-test",
|
||||||
|
name: "Check If Test",
|
||||||
|
type: "n8n-nodes-base.if",
|
||||||
|
typeVersion: 2.2,
|
||||||
|
position: [900, 300],
|
||||||
|
};
|
||||||
|
|
||||||
const supabaseInsertNode = {
|
const supabaseInsertNode = {
|
||||||
parameters: {
|
parameters: {
|
||||||
operation: "create",
|
operation: "create",
|
||||||
|
|
@ -382,7 +412,7 @@ async function main() {
|
||||||
name: "Insert Score to Supabase",
|
name: "Insert Score to Supabase",
|
||||||
type: "n8n-nodes-base.supabase",
|
type: "n8n-nodes-base.supabase",
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
position: [900, 300],
|
position: [1100, 420],
|
||||||
credentials: {
|
credentials: {
|
||||||
supabaseApi: {
|
supabaseApi: {
|
||||||
id: supabaseCredId,
|
id: supabaseCredId,
|
||||||
|
|
@ -399,7 +429,7 @@ async function main() {
|
||||||
name: "Respond to Webhook",
|
name: "Respond to Webhook",
|
||||||
type: "n8n-nodes-base.respondToWebhook",
|
type: "n8n-nodes-base.respondToWebhook",
|
||||||
typeVersion: 1.1,
|
typeVersion: 1.1,
|
||||||
position: [1100, 300],
|
position: [1300, 300],
|
||||||
};
|
};
|
||||||
|
|
||||||
const wNodes: any[] = [
|
const wNodes: any[] = [
|
||||||
|
|
@ -408,6 +438,7 @@ async function main() {
|
||||||
primaryModelNode,
|
primaryModelNode,
|
||||||
jsonParserNode,
|
jsonParserNode,
|
||||||
setNode,
|
setNode,
|
||||||
|
checkIfTestNode,
|
||||||
supabaseInsertNode,
|
supabaseInsertNode,
|
||||||
respondWebhookNode,
|
respondWebhookNode,
|
||||||
];
|
];
|
||||||
|
|
@ -459,6 +490,24 @@ async function main() {
|
||||||
},
|
},
|
||||||
"Format Evaluation Data": {
|
"Format Evaluation Data": {
|
||||||
main: [
|
main: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
node: "Check If Test",
|
||||||
|
type: "main",
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Check If Test": {
|
||||||
|
main: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
node: "Respond to Webhook",
|
||||||
|
type: "main",
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
node: "Insert Score to Supabase",
|
node: "Insert Score to Supabase",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue