Add page data delivery and publication guards
This commit is contained in:
@@ -75,20 +75,24 @@ export async function loginViaApi(baseUrl, { username, password }, reporter) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildUserMessage(text) {
|
||||
function buildUserMessage(text, { selectedChatSkill = null } = {}) {
|
||||
const metadata = { userVisible: true, displayText: text };
|
||||
if (selectedChatSkill) {
|
||||
metadata.memindRun = { selectedChatSkill };
|
||||
}
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text }],
|
||||
metadata: { userVisible: true, displayText: text },
|
||||
metadata,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createAgentRun(baseUrl, cookie, { message, sessionId = null }) {
|
||||
export async function createAgentRun(baseUrl, cookie, { message, sessionId = null, selectedChatSkill = null }) {
|
||||
const requestId = crypto.randomUUID();
|
||||
const body = {
|
||||
request_id: requestId,
|
||||
user_message: buildUserMessage(message),
|
||||
user_message: buildUserMessage(message, { selectedChatSkill }),
|
||||
};
|
||||
if (sessionId) body.session_id = sessionId;
|
||||
|
||||
@@ -301,6 +305,102 @@ export async function verifyPageAccess({
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function verifySurveyDelivery({
|
||||
publishKey,
|
||||
replyText = '',
|
||||
expect = {},
|
||||
reporter,
|
||||
}) {
|
||||
const publishDir = path.join(repoRoot, PUBLISH_ROOT_DIR, publishKey);
|
||||
const publicDir = path.join(publishDir, 'public');
|
||||
const policyDir = path.join(publishDir, '.mindspace', 'page-data-policies');
|
||||
const sqlitePath = path.join(publishDir, '.mindspace', 'private-data.sqlite');
|
||||
|
||||
const forbidReply = expect.forbidReplyPatterns ?? [];
|
||||
for (const pattern of forbidReply) {
|
||||
if (pattern && replyText.includes(pattern)) {
|
||||
reporter.fail('回复禁用模式', `命中 ${pattern}`);
|
||||
}
|
||||
}
|
||||
if (forbidReply.length && !forbidReply.some((pattern) => pattern && replyText.includes(pattern))) {
|
||||
reporter.pass('回复禁用模式', '未出现旁路 API / PLACEHOLDER');
|
||||
}
|
||||
|
||||
let htmlFiles = [];
|
||||
try {
|
||||
const entries = await fs.readdir(publicDir, { withFileTypes: true });
|
||||
htmlFiles = entries
|
||||
.filter((entry) => entry.isFile() && entry.name.endsWith('.html'))
|
||||
.map((entry) => entry.name);
|
||||
} catch {
|
||||
reporter.fail('问卷 HTML', 'public/ 目录不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
const surveyLike = htmlFiles.filter((name) => /survey|问卷|feature/i.test(name));
|
||||
const adminLike = htmlFiles.filter((name) => /admin|后台|manage/i.test(name));
|
||||
if (surveyLike.length === 0) {
|
||||
reporter.fail('问卷 HTML', `public/ 中未找到问卷页,现有: ${htmlFiles.join(', ') || '(空)'}`);
|
||||
} else {
|
||||
reporter.pass('问卷 HTML', surveyLike.join(', '));
|
||||
}
|
||||
if (adminLike.length === 0) {
|
||||
reporter.fail('后台 HTML', `public/ 中未找到后台页,现有: ${htmlFiles.join(', ') || '(空)'}`);
|
||||
} else {
|
||||
reporter.pass('后台 HTML', adminLike.join(', '));
|
||||
}
|
||||
|
||||
const forbidHtml = expect.forbidHtmlPatterns ?? [];
|
||||
for (const name of [...surveyLike, ...adminLike]) {
|
||||
const html = await fs.readFile(path.join(publicDir, name), 'utf8');
|
||||
if (!html.includes('page-data-client.js')) {
|
||||
reporter.fail(`${name} 脚本`, '未引用 page-data-client.js');
|
||||
}
|
||||
for (const pattern of forbidHtml) {
|
||||
if (pattern && html.includes(pattern)) {
|
||||
reporter.fail(`${name} 禁用模式`, `命中 ${pattern}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (surveyLike.length && adminLike.length) {
|
||||
reporter.pass('Page Data 客户端', '问卷/后台 HTML 已引用 page-data-client.js');
|
||||
}
|
||||
|
||||
if (expect.requirePolicy) {
|
||||
try {
|
||||
const policies = await fs.readdir(policyDir);
|
||||
const jsonPolicies = policies.filter((name) => name.endsWith('.json'));
|
||||
if (jsonPolicies.length === 0) {
|
||||
reporter.fail('Page Data 策略', 'page-data-policies/ 为空');
|
||||
} else {
|
||||
reporter.pass('Page Data 策略', `${jsonPolicies.length} 个 policy 文件`);
|
||||
}
|
||||
} catch {
|
||||
reporter.fail('Page Data 策略', '缺少 .mindspace/page-data-policies/');
|
||||
}
|
||||
}
|
||||
|
||||
if (expect.requireDataset) {
|
||||
try {
|
||||
await fs.stat(sqlitePath);
|
||||
reporter.pass('私有 SQLite', 'private-data.sqlite 存在');
|
||||
} catch {
|
||||
reporter.fail('私有 SQLite', 'private-data.sqlite 不存在');
|
||||
}
|
||||
}
|
||||
|
||||
const links = extractPublicLinks(replyText, 'http://127.0.0.1:8081');
|
||||
if (links.length >= 2) {
|
||||
reporter.pass('交付链接', `${links.length} 个链接`);
|
||||
} else if (links.length === 1) {
|
||||
reporter.fail('交付链接', '仅 1 个链接,期望问卷 + 后台');
|
||||
} else {
|
||||
reporter.fail('交付链接', '回复中未找到 MindSpace 链接');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function loadScenario(scenarioId) {
|
||||
const scenarioPath = path.join(repoRoot, 'scenarios', `${scenarioId}.json`);
|
||||
const raw = await fs.readFile(scenarioPath, 'utf8');
|
||||
|
||||
Reference in New Issue
Block a user