Files
memind/scripts/verify-children-hobby-diet-survey.mjs
T
john 32fb2cdeaf feat: chat uploads, vision turn isolation, and MindSpace agent improvements
Add chat file/image upload UX, attachment proxying, vision thumbnails, and per-turn image scoping so agents only use the current upload. Extend MindSpace asset context, billing token state, OA/scenario verify scripts, and related runtime config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 00:23:01 +08:00

173 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* 验证 john4 儿童爱好问卷「饮食偏好 / 每日几顿」增量改造链路(无需重新聊天)。
*
* Usage:
* node scripts/verify-children-hobby-diet-survey.mjs
* node scripts/verify-children-hobby-diet-survey.mjs --no-insert
* node scripts/verify-children-hobby-diet-survey.mjs --user-id <uuid> --port 8081
*/
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadH5Environment } from './load-env.mjs';
import {
CHILDREN_HOBBY_DIET_SURVEY,
createReporter,
resolvePortalBase,
verifyChildrenHobbyDietSurvey,
} from './scenario-test-lib.mjs';
import { PUBLISH_ROOT_DIR } from '../user-publish.mjs';
import { createDbPool } from '../db.mjs';
const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
loadH5Environment(import.meta.dirname);
function parseArgs(argv) {
const options = {
userId: CHILDREN_HOBBY_DIET_SURVEY.userId,
port: Number(process.env.H5_PORT ?? 8081),
testInsert: true,
checkRuns: true,
};
for (let i = 2; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === '--no-insert') options.testInsert = false;
else if (arg === '--no-runs') options.checkRuns = false;
else if (arg === '--user-id' && argv[i + 1]) options.userId = argv[++i];
else if (arg === '--port' && argv[i + 1]) options.port = Number(argv[++i]);
else if (arg === '-h' || arg === '--help') {
console.log(`Usage: node scripts/verify-children-hobby-diet-survey.mjs [--no-insert] [--no-runs] [--user-id <uuid>] [--port <port>]`);
process.exit(0);
} else {
throw new Error(`未知参数: ${arg}`);
}
}
return options;
}
async function ensurePortalReady(baseUrl) {
const response = await fetch(`${baseUrl}/auth/status`);
if (!response.ok) {
throw new Error(`Portal 未就绪: ${baseUrl}/auth/status -> ${response.status}`);
}
}
function verifySqliteColumn(sqlitePath, tableName, columnName, reporter) {
try {
const output = execSync(
`sqlite3 ${JSON.stringify(sqlitePath)} "PRAGMA table_info(${tableName});"`,
{ encoding: 'utf8' },
);
if (!output.includes(`|${columnName}|`)) {
reporter.fail('SQLite 列', `${tableName}.${columnName} 不存在`);
return false;
}
reporter.pass('SQLite 列', `${tableName}.${columnName}`);
const datasetJson = execSync(
`sqlite3 ${JSON.stringify(sqlitePath)} "SELECT config_json FROM __page_data_datasets WHERE name='${CHILDREN_HOBBY_DIET_SURVEY.dataset}';"`,
{ encoding: 'utf8' },
).trim();
const config = JSON.parse(datasetJson);
const insertCols = config?.columns?.insert ?? [];
if (!insertCols.includes(columnName)) {
reporter.fail('dataset insert 列', `${columnName} 不在 ${insertCols.join(', ')}`);
return false;
}
reporter.pass('dataset insert 列', insertCols.join(', '));
return true;
} catch (error) {
reporter.fail('SQLite 检查', error instanceof Error ? error.message : String(error));
return false;
}
}
async function verifyAgentRunHealth(userId, reporter) {
if (!process.env.DATABASE_URL && !fs.existsSync(path.join(repoRoot, '.env'))) {
reporter.pass('Agent run 健康', '跳过(无 DATABASE_URL');
return true;
}
try {
for (const line of fs.readFileSync(path.join(repoRoot, '.env'), 'utf8').split('\n')) {
const t = line.trim();
if (!t || t.startsWith('#')) continue;
const eq = t.indexOf('=');
if (eq < 0) continue;
const k = t.slice(0, eq).trim();
const v = t.slice(eq + 1).trim();
if (!process.env[k]) process.env[k] = v;
}
const pool = createDbPool();
const [runningRows] = await pool.query(
`SELECT id, agent_session_id, updated_at
FROM h5_agent_runs
WHERE user_id = ? AND status = 'running'
ORDER BY updated_at DESC
LIMIT 5`,
[userId],
);
await pool.end();
if (runningRows.length > 0) {
reporter.fail('Agent run 健康', `${runningRows.length} 条 running(可能前端仍在 loading`);
for (const row of runningRows) {
console.error(` - ${row.id} session=${row.agent_session_id}`);
}
return false;
}
reporter.pass('Agent run 健康', '无 orphan running');
return true;
} catch (error) {
reporter.fail('Agent run 健康', error instanceof Error ? error.message : String(error));
return false;
}
}
async function main() {
const options = parseArgs(process.argv);
const reporter = createReporter();
const baseUrl = resolvePortalBase(options.port);
const publishKey = options.userId;
const sqlitePath = path.join(
repoRoot,
PUBLISH_ROOT_DIR,
publishKey,
'.mindspace',
'private-data.sqlite',
);
console.log('==> 儿童爱好问卷 · 饮食偏好链路验证');
console.log(` Portal: ${baseUrl}`);
console.log(` 用户: ${publishKey}\n`);
await ensurePortalReady(baseUrl);
let ok = true;
ok = verifySqliteColumn(
sqlitePath,
CHILDREN_HOBBY_DIET_SURVEY.dataset,
CHILDREN_HOBBY_DIET_SURVEY.dietField,
reporter,
) && ok;
ok = await verifyChildrenHobbyDietSurvey({
publishKey,
baseUrl,
reporter,
testInsert: options.testInsert,
}) && ok;
if (options.checkRuns) {
ok = await verifyAgentRunHealth(publishKey, reporter) && ok;
}
const code = reporter.summary();
process.exit(code);
}
main().catch((error) => {
console.error(error instanceof Error ? error.stack ?? error.message : error);
process.exit(1);
});