Ship Novya PWA with backend for deployable production use.
Fix production TypeScript builds and add DEPLOY.md so the app can be hosted from this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Offline tests: AI Coach session completion schema + validation.
|
||||
* Run: npx tsx --tsconfig tsconfig.json scripts/verify-coach-completion.ts
|
||||
*/
|
||||
import { getSessionByOrder } from '../src/data/program/sessions';
|
||||
import { buildAiCoachSessionContext } from '../src/lib/aiCoach/sessionContext';
|
||||
import {
|
||||
COACH_SESSION_COMPLETE_CLOSE,
|
||||
COACH_SESSION_COMPLETE_OPEN,
|
||||
exampleCompletionSignal,
|
||||
extractSessionExerciseLabels,
|
||||
extractSessionHeadings,
|
||||
parseCoachCompletionFromAssistantText,
|
||||
validateCoachSessionCompletionSignal,
|
||||
type CoachSessionCompletionSignal
|
||||
} from '../src/lib/aiCoach/sessionCompletion';
|
||||
import { buildCoachSystemPrompt } from '../src/lib/aiCoach/buildCoachSystemPrompt';
|
||||
|
||||
const USER_ID = 'verify-coach-user';
|
||||
|
||||
const session1 = getSessionByOrder(1);
|
||||
if (!session1) throw new Error('Session 1 missing');
|
||||
|
||||
const ctx = buildAiCoachSessionContext(session1);
|
||||
const prompt = buildCoachSystemPrompt(ctx);
|
||||
|
||||
const validSignal = exampleCompletionSignal(ctx);
|
||||
|
||||
const wrap = (signal: CoachSessionCompletionSignal, visible = 'جلسه به پایان رسید.') =>
|
||||
`${visible}\n${COACH_SESSION_COMPLETE_OPEN}\n${JSON.stringify(signal)}\n${COACH_SESSION_COMPLETE_CLOSE}`;
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
const assert = (name: string, condition: boolean) => {
|
||||
if (condition) {
|
||||
passed += 1;
|
||||
return;
|
||||
}
|
||||
failed += 1;
|
||||
console.error(`FAIL: ${name}`);
|
||||
};
|
||||
|
||||
// --- parse ---
|
||||
const parsed = parseCoachCompletionFromAssistantText(wrap(validSignal));
|
||||
assert('parse extracts signal', parsed.signal?.sessionComplete === true);
|
||||
assert('parse strips marker from display', !parsed.displayContent.includes(COACH_SESSION_COMPLETE_OPEN));
|
||||
assert('parse keeps visible Persian text', parsed.displayContent.includes('جلسه به پایان رسید'));
|
||||
|
||||
// --- validate valid ---
|
||||
const ok = validateCoachSessionCompletionSignal(validSignal, ctx, {
|
||||
userId: USER_ID,
|
||||
userTurnCount: 3
|
||||
});
|
||||
assert('valid signal passes', ok.valid === true);
|
||||
|
||||
// --- reject: single user turn ---
|
||||
const lowTurns = validateCoachSessionCompletionSignal(validSignal, ctx, {
|
||||
userId: USER_ID,
|
||||
userTurnCount: 1
|
||||
});
|
||||
assert('rejects single user turn', lowTurns.valid === false);
|
||||
|
||||
// --- reject: session id mismatch ---
|
||||
const badId = validateCoachSessionCompletionSignal(
|
||||
{ ...validSignal, sessionId: 'session-99' },
|
||||
ctx,
|
||||
{ userId: USER_ID, userTurnCount: 3 }
|
||||
);
|
||||
assert('rejects sessionId mismatch', badId.valid === false);
|
||||
|
||||
// --- reject: user not confirmed ---
|
||||
const noConfirm = validateCoachSessionCompletionSignal(
|
||||
{ ...validSignal, userConfirmed: false },
|
||||
ctx,
|
||||
{ userId: USER_ID, userTurnCount: 3 }
|
||||
);
|
||||
assert('rejects missing userConfirmed', noConfirm.valid === false);
|
||||
|
||||
// --- reject: missing exercise ---
|
||||
const missingExercise = validateCoachSessionCompletionSignal(
|
||||
{ ...validSignal, completedExercises: [] },
|
||||
ctx,
|
||||
{ userId: USER_ID, userTurnCount: 3 }
|
||||
);
|
||||
assert('rejects missing completed exercises', missingExercise.valid === false);
|
||||
|
||||
// --- reject: insufficient covered topics ---
|
||||
const thinTopics = validateCoachSessionCompletionSignal(
|
||||
{ ...validSignal, coveredTopics: ['نامرتبط'] },
|
||||
ctx,
|
||||
{ userId: USER_ID, userTurnCount: 3 }
|
||||
);
|
||||
assert('rejects insufficient coveredTopics', thinTopics.valid === false);
|
||||
|
||||
// --- prompt includes completion instructions ---
|
||||
assert('prompt includes COACH_SESSION_COMPLETE marker', prompt.includes('COACH_SESSION_COMPLETE'));
|
||||
assert('prompt includes sessionComplete field', prompt.includes('"sessionComplete":true'));
|
||||
assert('prompt includes userConfirmed rule', prompt.includes('userConfirmed'));
|
||||
|
||||
// --- session 1 has pretest exercise in labels ---
|
||||
const exerciseLabels = extractSessionExerciseLabels(ctx.blocks);
|
||||
assert('session 1 has pretest exercise label', exerciseLabels.some((l) => l.includes('پیشآزمون')));
|
||||
|
||||
const headings = extractSessionHeadings(ctx.blocks);
|
||||
assert('session 1 has headings', headings.length > 0);
|
||||
|
||||
console.log(JSON.stringify({ passed, failed, exerciseLabels, headingCount: headings.length }, null, 2));
|
||||
|
||||
if (failed > 0) {
|
||||
console.error(`Verification failed: ${failed} check(s)`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Coach completion schema + validation OK');
|
||||
Reference in New Issue
Block a user