181 lines
5.6 KiB
TypeScript
181 lines
5.6 KiB
TypeScript
/**
|
|
* Integration Test: Modal → Learning Pipeline
|
|
*
|
|
* Tests the full flow:
|
|
* 1. Mock modal postMessage feedback
|
|
* 2. Validate with shared Zod schemas
|
|
* 3. Transform to FeedbackEvent
|
|
* 4. Feed into learning pipeline
|
|
* 5. Verify output
|
|
*/
|
|
|
|
import { parseModalMessage, modalResponseToFeedbackEvent } from "../../packages/shared/src/integration/modal-to-learning.js";
|
|
import type { ModalResponse } from "../../packages/shared/src/types/feedback.js";
|
|
|
|
// ─── Test Helpers ───
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function assert(condition: boolean, message: string) {
|
|
if (condition) {
|
|
console.log(` ✅ ${message}`);
|
|
passed++;
|
|
} else {
|
|
console.error(` ❌ ${message}`);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
// ─── Mock Data ───
|
|
|
|
const mockModalResponse: ModalResponse = {
|
|
type: "factory_modal_response",
|
|
modalType: "traffic-light",
|
|
modalVersion: "1.0.0",
|
|
pipelineId: "pipeline-123",
|
|
itemId: "item-456",
|
|
sessionId: "session-789",
|
|
timestamp: new Date().toISOString(),
|
|
responseTimeMs: 3500,
|
|
feedback: {
|
|
decision: {
|
|
decision: "approved",
|
|
reason: "Code looks clean, tests passing",
|
|
tags: ["clean-code", "tests-passing"],
|
|
},
|
|
confidence: {
|
|
confidencePercent: 85,
|
|
zone: "confident",
|
|
wouldDelegateToAI: false,
|
|
needsExpertReview: false,
|
|
},
|
|
},
|
|
meta: {
|
|
timeToFirstInteractionMs: 1200,
|
|
timeToDecisionMs: 3500,
|
|
fieldsModified: ["decision", "confidence"],
|
|
scrollDepth: 0.8,
|
|
revisits: 0,
|
|
totalInteractions: 5,
|
|
viewportSize: { width: 1920, height: 1080 },
|
|
deviceType: "desktop",
|
|
},
|
|
};
|
|
|
|
// ─── Tests ───
|
|
|
|
console.log("\n🧪 Modal → Learning Integration Tests\n");
|
|
|
|
// Test 1: Valid modal message parsing
|
|
console.log("Test 1: Parse valid modal response");
|
|
{
|
|
const result = parseModalMessage(mockModalResponse);
|
|
assert(result.valid === true, "Should parse valid modal response");
|
|
if (result.valid) {
|
|
assert(result.response.modalType === "traffic-light", "Modal type should be traffic-light");
|
|
assert(result.response.feedback.decision?.decision === "approved", "Decision should be approved");
|
|
}
|
|
}
|
|
|
|
// Test 2: Invalid message (wrong type)
|
|
console.log("\nTest 2: Reject non-response messages");
|
|
{
|
|
const result = parseModalMessage({ type: "factory_modal_ready", modalType: "test", version: "1.0.0" });
|
|
assert(result.valid === false, "Should reject non-response messages");
|
|
}
|
|
|
|
// Test 3: Invalid message (bad schema)
|
|
console.log("\nTest 3: Reject schema-invalid messages");
|
|
{
|
|
const result = parseModalMessage({
|
|
type: "factory_modal_response",
|
|
modalType: "test",
|
|
// Missing required fields
|
|
});
|
|
assert(result.valid === false, "Should reject schema-invalid messages");
|
|
}
|
|
|
|
// Test 4: Null/undefined input
|
|
console.log("\nTest 4: Handle null/undefined input");
|
|
{
|
|
assert(parseModalMessage(null).valid === false, "Should reject null");
|
|
assert(parseModalMessage(undefined).valid === false, "Should reject undefined");
|
|
assert(parseModalMessage("string").valid === false, "Should reject string");
|
|
}
|
|
|
|
// Test 5: Transform to FeedbackEvent
|
|
console.log("\nTest 5: Transform ModalResponse → FeedbackEvent");
|
|
{
|
|
const feedbackEvent = modalResponseToFeedbackEvent(mockModalResponse, {
|
|
workProductType: "mcp_server",
|
|
workProductId: "ghl-mcp",
|
|
workProductVersion: "abc123",
|
|
workProductSummary: "Go High Level MCP server",
|
|
pipelineStage: "review",
|
|
mcpServerType: "go-high-level",
|
|
});
|
|
|
|
assert(typeof feedbackEvent.id === "string", "Should have UUID id");
|
|
assert(feedbackEvent.modalType === "traffic-light", "Modal type preserved");
|
|
assert(feedbackEvent.pipelineId === "pipeline-123", "Pipeline ID preserved");
|
|
assert(feedbackEvent.workProduct.type === "mcp_server", "Work product type set");
|
|
assert(feedbackEvent.pipelineStage === "review", "Pipeline stage set");
|
|
assert(feedbackEvent.feedback.decision?.decision === "approved", "Feedback preserved");
|
|
assert(feedbackEvent.meta.timeOfDay !== undefined, "Time of day enriched");
|
|
assert(feedbackEvent.meta.dayOfWeek !== undefined, "Day of week enriched");
|
|
}
|
|
|
|
// Test 6: Scores validation in FeedbackEvent
|
|
console.log("\nTest 6: Scores feedback type");
|
|
{
|
|
const responseWithScores: ModalResponse = {
|
|
...mockModalResponse,
|
|
modalType: "report-card",
|
|
feedback: {
|
|
scores: [
|
|
{ dimension: "code_quality", score: 8, comment: "Clean" },
|
|
{ dimension: "test_coverage", score: 9 },
|
|
{ dimension: "documentation", score: 7 },
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = parseModalMessage(responseWithScores);
|
|
assert(result.valid === true, "Should accept scores feedback");
|
|
if (result.valid) {
|
|
assert(result.response.feedback.scores?.length === 3, "Should have 3 scores");
|
|
}
|
|
}
|
|
|
|
// Test 7: Batch decisions feedback type
|
|
console.log("\nTest 7: Batch decisions feedback type");
|
|
{
|
|
const responseWithBatch: ModalResponse = {
|
|
...mockModalResponse,
|
|
modalType: "tinder-swipe",
|
|
feedback: {
|
|
batchDecisions: [
|
|
{ itemId: "item-1", decision: "approve", timeMs: 1200, flippedForDetails: false },
|
|
{ itemId: "item-2", decision: "reject", timeMs: 3500, flippedForDetails: true },
|
|
{ itemId: "item-3", decision: "love", timeMs: 800, flippedForDetails: false },
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = parseModalMessage(responseWithBatch);
|
|
assert(result.valid === true, "Should accept batch decisions");
|
|
if (result.valid) {
|
|
assert(result.response.feedback.batchDecisions?.length === 3, "Should have 3 batch decisions");
|
|
}
|
|
}
|
|
|
|
// ─── Summary ───
|
|
console.log(`\n${"═".repeat(50)}`);
|
|
console.log(`Results: ${passed} passed, ${failed} failed out of ${passed + failed} assertions`);
|
|
console.log(`${"═".repeat(50)}\n`);
|
|
|
|
if (failed > 0) {
|
|
process.exit(1);
|
|
}
|