fix(page-data): deliver MindSpace workspace URLs instead of /u/.../pages/ routes.
Page Data questionnaires were returning publication slugs to users; password-mode pages always fell back to /u/ routes. Prefer MindSpace paths at publish time and rewrite mistaken delivery links in WeChat and Portal chat. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
buildPageDataCollectFailureText,
|
||||
buildPageDataCollectRepairPrompt,
|
||||
collectPageDataDeliveryArtifacts,
|
||||
evaluatePageDataFinishGuard,
|
||||
evaluatePageDataHtmlContent,
|
||||
inferPageDataBindAccessMode,
|
||||
maybeAutoBindPageDataHtmlPages,
|
||||
rewritePageDataDeliveryLinks,
|
||||
shouldRetryPageDataCollectReply,
|
||||
} from './mindspace-page-data-finish-guard.mjs';
|
||||
import { writePageAccessPolicy } from './page-data-policy-store.mjs';
|
||||
|
||||
const SURVEY_HTML = `<!doctype html><html><head><title>问卷</title></head><body>
|
||||
<script src="/assets/page-data-client.js"></script>
|
||||
<script>
|
||||
MindSpacePageData.createClient({ apiBase: '/api' }).insertRow('diet_survey', {});
|
||||
</script></body></html>`;
|
||||
|
||||
const BAD_SURVEY_HTML = `<!doctype html><html><body><script>
|
||||
async function save(data) {
|
||||
if (typeof MindSpacePageData !== 'undefined') {
|
||||
await MindSpacePageData.createClient({ apiBase: '/api' }).insertRow('diet_survey', data);
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('diet_survey_data', JSON.stringify([data]));
|
||||
}
|
||||
</script></body></html>`;
|
||||
|
||||
test('evaluatePageDataHtmlContent flags missing client script and localStorage fallback', () => {
|
||||
const ok = evaluatePageDataHtmlContent(SURVEY_HTML, { relativePath: 'public/diet-survey.html' });
|
||||
assert.deepEqual(ok.issues, []);
|
||||
|
||||
const bad = evaluatePageDataHtmlContent(BAD_SURVEY_HTML, { relativePath: 'public/children-diet-survey.html' });
|
||||
assert.ok(bad.issues.includes('missing_page_data_client_script'));
|
||||
assert.ok(bad.issues.includes('forbidden_local_storage'));
|
||||
});
|
||||
|
||||
test('inferPageDataBindAccessMode chooses password for admin html', () => {
|
||||
assert.equal(
|
||||
inferPageDataBindAccessMode('public/diet-survey-admin.html', SURVEY_HTML),
|
||||
'password',
|
||||
);
|
||||
assert.equal(inferPageDataBindAccessMode('public/diet-survey.html', SURVEY_HTML), 'public');
|
||||
});
|
||||
|
||||
test('evaluatePageDataFinishGuard detects unbound page data html', () => {
|
||||
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'page-data-guard-'));
|
||||
try {
|
||||
fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
|
||||
fs.writeFileSync(path.join(publishDir, 'public', 'diet-survey.html'), SURVEY_HTML, 'utf8');
|
||||
const evaluation = evaluatePageDataFinishGuard({
|
||||
publishDir,
|
||||
agentText: '帮我设计一个调查问卷,要加一个后台',
|
||||
messages: [],
|
||||
});
|
||||
assert.equal(evaluation.pageDataIntent, true);
|
||||
assert.equal(evaluation.unboundFiles.length, 1);
|
||||
assert.equal(evaluation.needsRepair, true);
|
||||
} finally {
|
||||
fs.rmSync(publishDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('shouldRetryPageDataCollectReply retries when localStorage fallback exists', () => {
|
||||
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'page-data-guard-retry-'));
|
||||
try {
|
||||
fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
|
||||
fs.writeFileSync(path.join(publishDir, 'public', 'children-diet-survey.html'), BAD_SURVEY_HTML, 'utf8');
|
||||
assert.equal(
|
||||
shouldRetryPageDataCollectReply({
|
||||
reply: { text: '问卷已发布', messages: [] },
|
||||
intent: { agentText: '帮我设计一个调查问卷,要加一个后台' },
|
||||
publishDir,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(publishDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('buildPageDataCollectRepairPrompt mentions bind and forbids localStorage', () => {
|
||||
const prompt = buildPageDataCollectRepairPrompt({
|
||||
htmlIssues: [{ relativePath: 'public/x.html', issue: 'forbidden_local_storage' }],
|
||||
unboundFiles: [{ relativePath: 'public/x.html' }],
|
||||
});
|
||||
assert.match(prompt, /private_data_bind_workspace_page/);
|
||||
assert.match(prompt, /localStorage/);
|
||||
assert.match(buildPageDataCollectFailureText(), /page-data-collect/);
|
||||
});
|
||||
|
||||
test('maybeAutoBindPageDataHtmlPages skips invalid html', async () => {
|
||||
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'page-data-guard-bind-'));
|
||||
try {
|
||||
fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
|
||||
fs.writeFileSync(path.join(publishDir, 'public', 'bad.html'), BAD_SURVEY_HTML, 'utf8');
|
||||
const result = await maybeAutoBindPageDataHtmlPages({
|
||||
pool: null,
|
||||
userId: 'user-1',
|
||||
publishDir,
|
||||
h5Root: publishDir,
|
||||
storageRoot: publishDir,
|
||||
});
|
||||
assert.equal(result.bound.length, 0);
|
||||
assert.equal(result.errors[0]?.code, 'database_unconfigured');
|
||||
} finally {
|
||||
fs.rmSync(publishDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('evaluatePageDataFinishGuard passes when policy already exists', () => {
|
||||
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'page-data-guard-bound-'));
|
||||
try {
|
||||
fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
|
||||
fs.writeFileSync(path.join(publishDir, 'public', 'diet-survey.html'), SURVEY_HTML, 'utf8');
|
||||
writePageAccessPolicy(publishDir, {
|
||||
pageId: 'page-1',
|
||||
ownerUserId: 'user-1',
|
||||
accessMode: 'public',
|
||||
datasets: {
|
||||
diet_survey: {
|
||||
insert: true,
|
||||
read: false,
|
||||
columns: { insert: ['child_age'] },
|
||||
},
|
||||
},
|
||||
});
|
||||
const evaluation = evaluatePageDataFinishGuard({
|
||||
publishDir,
|
||||
agentText: '调查问卷和后台',
|
||||
messages: [],
|
||||
});
|
||||
assert.equal(evaluation.unboundFiles.length, 0);
|
||||
assert.equal(evaluation.htmlIssues.length, 0);
|
||||
} finally {
|
||||
fs.rmSync(publishDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('rewritePageDataDeliveryLinks rewrites publication route urls to MindSpace workspace urls', () => {
|
||||
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'page-data-guard-urls-'));
|
||||
const previousBase = process.env.H5_PUBLIC_BASE_URL;
|
||||
process.env.H5_PUBLIC_BASE_URL = 'https://m.tkmind.cn';
|
||||
try {
|
||||
fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
|
||||
fs.writeFileSync(path.join(publishDir, 'public', 'zhiqu-survey.html'), SURVEY_HTML, 'utf8');
|
||||
fs.writeFileSync(path.join(publishDir, 'public', 'zhiqu-survey-admin.html'), SURVEY_HTML, 'utf8');
|
||||
const artifacts = collectPageDataDeliveryArtifacts(publishDir);
|
||||
assert.equal(artifacts.length, 2);
|
||||
const text = [
|
||||
'问卷:https://m.tkmind.cn/u/john/pages/zhiqu-58bccceb',
|
||||
'后台:https://m.tkmind.cn/u/john/pages/zhiqu-admin-98f11681',
|
||||
].join('\n');
|
||||
const next = rewritePageDataDeliveryLinks(text, artifacts);
|
||||
assert.doesNotMatch(next, /\/u\/john\/pages\//);
|
||||
assert.match(next, /\/MindSpace\/.+\/public\/zhiqu-survey\.html/);
|
||||
assert.match(next, /\/MindSpace\/.+\/public\/zhiqu-survey-admin\.html/);
|
||||
} finally {
|
||||
if (previousBase == null) delete process.env.H5_PUBLIC_BASE_URL;
|
||||
else process.env.H5_PUBLIC_BASE_URL = previousBase;
|
||||
fs.rmSync(publishDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user