/** * verifyChatCopilotContract.ts * * Static contract checks for the chat copilot surface. * Verifies the supported action set, runtime-context prompt contract, * and safe quick-link guidance without starting the backend server. */ import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const apiServerSource = readFileSync(join(__dirname, 'src/services/apiServer.ts'), 'utf8'); function assertSourceIncludes(fragment: string, message: string) { assert.ok(apiServerSource.includes(fragment), message); } function assertSourceMatches(pattern: RegExp, message: string) { assert.match(apiServerSource, pattern, message); } function testChatRouteRequiresAuth() { assertSourceMatches( /this\.app\.post\('\/api\/chat',\s*this\.requireAuth,/, '/api/chat must be protected by requireAuth', ); console.log('[PASS] /api/chat requires authenticated requests.'); } function testChatActionContract() { for (const action of [ 'create_profile', 'update_profile', 'recommend_profile_change', 'recommend_trade_plan', 'recommend_reconciliation_followup', 'review_recent_trades', 'explain', 'explain_position', 'explain_waiting', 'explain_blocker', 'summarize_reconciliation', ]) { assertSourceIncludes(`'${action}'`, `chat copilot must support ${action}`); } assertSourceIncludes( '"action": "create_profile" | "update_profile" | "recommend_profile_change" | "recommend_trade_plan" | "recommend_reconciliation_followup" | "review_recent_trades" | "explain" | "explain_position" | "explain_waiting" | "explain_blocker" | "summarize_reconciliation"', 'chat prompt contract must document the full action enum', ); console.log('[PASS] chat action contract is documented and supported.'); } function testQuickLinkContract() { assertSourceIncludes( `"insights": string[] (optional concise evidence or key facts)`, 'chat prompt contract must document structured insights', ); assertSourceIncludes( `"quickLinks": [{ kind: "portfolio" | "plans" | "settings", ... }] (optional safe in-app destinations)`, 'chat prompt contract must document quickLinks', ); assertSourceIncludes( 'Include safe "insights", "nextActions", and "quickLinks" whenever they would help the user move to the right app surface.', 'chat prompt rules must require safe insights/nextActions/quickLinks guidance', ); assertSourceIncludes( "{ kind: 'plans', label: 'Manage in Plans'", 'chat copilot must be able to deep-link into Trade Plans', ); assertSourceIncludes( "{ kind: 'settings', label: 'Open Admin Panel', section: 'Admin Panel' }", 'chat copilot must be able to deep-link into the admin panel', ); assertSourceIncludes( "{ kind: 'portfolio', label: 'Open Portfolio'", 'chat copilot must be able to deep-link into Portfolio', ); console.log('[PASS] quick-link contract is covered.'); } function testRuntimeCopilotFallbacks() { assertSourceIncludes( 'this.buildTradePlanRecommendation(message, context)', 'local fallback must support trade-plan recommendations', ); assertSourceIncludes( 'return this.buildReconciliationFollowup(context);', 'local fallback must support reconciliation follow-up guidance', ); assertSourceIncludes( 'this.buildRecentTradeReview(context)', 'local fallback must support recent-trade review guidance', ); console.log('[PASS] runtime copilot fallback intents are covered.'); } function main() { testChatRouteRequiresAuth(); testChatActionContract(); testQuickLinkContract(); testRuntimeCopilotFallbacks(); console.log('Chat copilot contract checks passed'); } main();