fde6503bdf
Memind CI / Test, build, and release guards (push) Has been cancelled
Enable optional SEO/GEO injection and discovery routes for confirmed public pages while keeping private pages noindex. Add premium page template skills, portal catalog API, template shop UI, and Baidu push gated by memind_adm config. Co-authored-by: Cursor <cursoragent@cursor.com>
246 lines
8.9 KiB
JavaScript
246 lines
8.9 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
collectInlineScriptHashes,
|
|
decorateMindSpacePublishedHtml,
|
|
handleMindSpaceLongImageDownload,
|
|
} from './mindspace-public-delivery.mjs';
|
|
|
|
test('collectInlineScriptHashes returns sha256 hashes for inline scripts only', () => {
|
|
const html = `<html><body>
|
|
<script>console.log('page')</script>
|
|
<script src="https://cdn.example/app.js"></script>
|
|
<script>console.log('share')</script>
|
|
</body></html>`;
|
|
const hashes = collectInlineScriptHashes(html);
|
|
assert.equal(hashes.length, 2);
|
|
assert.match(hashes[0], /^[A-Za-z0-9+/]+=*$/);
|
|
assert.notEqual(hashes[0], hashes[1]);
|
|
});
|
|
|
|
test('handleMindSpaceLongImageDownload renders and downloads when requested', async () => {
|
|
const calls = [];
|
|
const res = {
|
|
headersSent: false,
|
|
set(key, value) {
|
|
calls.push(['set', key, value]);
|
|
return this;
|
|
},
|
|
download(filePath, filename, cb) {
|
|
calls.push(['download', filePath, filename]);
|
|
cb?.();
|
|
},
|
|
status(code) {
|
|
calls.push(['status', code]);
|
|
return this;
|
|
},
|
|
type(value) {
|
|
calls.push(['type', value]);
|
|
return this;
|
|
},
|
|
send(value) {
|
|
calls.push(['send', value]);
|
|
return this;
|
|
},
|
|
json(value) {
|
|
calls.push(['json', value]);
|
|
return this;
|
|
},
|
|
};
|
|
|
|
const handled = await handleMindSpaceLongImageDownload({
|
|
query: { download: 'long-image' },
|
|
filePath: '/tmp/report.html',
|
|
pageUrl: 'https://m.tkmind.cn/MindSpace/u/public/report.html',
|
|
res,
|
|
isLongImageDownloadRequest: () => true,
|
|
longImagePathForHtml: () => '/tmp/report.long.png',
|
|
renderLongImage: async (...args) => {
|
|
calls.push(['render', ...args]);
|
|
},
|
|
});
|
|
|
|
assert.equal(handled, true);
|
|
assert.deepEqual(calls[0], ['render', {
|
|
url: 'https://m.tkmind.cn/MindSpace/u/public/report.html',
|
|
outputPath: '/tmp/report.long.png',
|
|
}]);
|
|
assert.deepEqual(calls[1], ['set', 'Cache-Control', 'no-store']);
|
|
assert.deepEqual(calls[2], ['download', '/tmp/report.long.png', 'report.long.png']);
|
|
});
|
|
|
|
test('handleMindSpaceLongImageDownload falls back to local html path without page url', async () => {
|
|
const calls = [];
|
|
const res = {
|
|
headersSent: false,
|
|
set() { return this; },
|
|
download() {},
|
|
status() { return this; },
|
|
type() { return this; },
|
|
send() { return this; },
|
|
};
|
|
|
|
await handleMindSpaceLongImageDownload({
|
|
query: { download: 'long-image' },
|
|
filePath: '/tmp/report.html',
|
|
res,
|
|
isLongImageDownloadRequest: () => true,
|
|
longImagePathForHtml: () => '/tmp/report.long.png',
|
|
renderLongImage: async (...args) => {
|
|
calls.push(['render', ...args]);
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(calls[0], ['render', { htmlPath: '/tmp/report.html', outputPath: '/tmp/report.long.png' }]);
|
|
});
|
|
|
|
test('decorateMindSpacePublishedHtml returns decorated html and csp', () => {
|
|
const pageScript = "document.body.dataset.ready='1'";
|
|
const shareScript = "document.body.dataset.share='1'";
|
|
let sharedIsOwner;
|
|
const result = decorateMindSpacePublishedHtml({
|
|
html: `<html><body><script>${pageScript}</script>demo</body></html>`,
|
|
embed: false,
|
|
pageDataContext: { pageId: 'page-abc', accessMode: 'password' },
|
|
context: {
|
|
origin: 'https://m.tkmind.cn',
|
|
pageUrl: 'https://m.tkmind.cn/MindSpace/u/public/report.html',
|
|
pageDirUrl: 'https://m.tkmind.cn/MindSpace/u/public/',
|
|
fallbackImageUrl: 'https://m.tkmind.cn/MindSpace/u/public/report.thumbnail.png',
|
|
},
|
|
userAgent: 'MicroMessenger',
|
|
preparePublicationHtmlForEmbed: (value) => value,
|
|
injectOgTags: (value, meta) => `${value}<!--og:${meta.pageUrl}-->`,
|
|
injectWechatShareBridge: (value, meta) => `${value}<!--wechat:${meta.pageUrl}-->`,
|
|
injectPublicFileShareButton: (value, options) => {
|
|
sharedIsOwner = options?.isOwner;
|
|
return {
|
|
html: value.replace('</body>', `<script>${shareScript}</script></body>`),
|
|
scriptHashes: ['ignored'],
|
|
};
|
|
},
|
|
publishedPageCsp: (_value, options) => JSON.stringify(options),
|
|
isWechatUserAgent: () => true,
|
|
});
|
|
|
|
assert.match(result.html, /og:/);
|
|
assert.match(result.html, /wechat:/);
|
|
assert.match(result.html, /dataset\.share/);
|
|
assert.match(result.html, /data-mindspace-image-retry="1"/);
|
|
assert.match(result.html, /__MINDSPACE_PAGE_DATA__/);
|
|
assert.equal(result.allowEmbedFrame, false);
|
|
assert.equal(sharedIsOwner, true);
|
|
const options = JSON.parse(result.csp);
|
|
assert.equal(options.embed, false);
|
|
assert.equal(options.wechatShare, true);
|
|
assert.deepEqual(options.scriptHashes, collectInlineScriptHashes(result.html));
|
|
assert.equal(options.scriptHashes.length, 4);
|
|
});
|
|
|
|
test('decorateMindSpacePublishedHtml removes preview CSP before published delivery', () => {
|
|
const result = decorateMindSpacePublishedHtml({
|
|
html: '<html><head><meta http-equiv="Content-Security-Policy" content="script-src \'none\'"></head><body><script>window.ready=1</script></body></html>',
|
|
context: { origin: '', pageUrl: '', pageDirUrl: '', fallbackImageUrl: '' },
|
|
preparePublicationHtmlForEmbed: (value) => value,
|
|
injectOgTags: (value) => value,
|
|
injectWechatShareBridge: (value) => value,
|
|
injectPublicFileShareButton: (value) => ({ html: value, scriptHashes: [] }),
|
|
publishedPageCsp: (value) => value,
|
|
isWechatUserAgent: () => false,
|
|
});
|
|
|
|
assert.doesNotMatch(result.html, /Content-Security-Policy/i);
|
|
assert.match(result.html, /window\.ready=1/);
|
|
});
|
|
|
|
test('decorateMindSpacePublishedHtml forwards isOwner=false to the share button injector', () => {
|
|
let sharedIsOwner;
|
|
decorateMindSpacePublishedHtml({
|
|
html: '<html><body>demo</body></html>',
|
|
embed: false,
|
|
isOwner: false,
|
|
context: { origin: '', pageUrl: '', pageDirUrl: '', fallbackImageUrl: '' },
|
|
preparePublicationHtmlForEmbed: (value) => value,
|
|
injectOgTags: (value) => value,
|
|
injectWechatShareBridge: (value) => value,
|
|
injectPublicFileShareButton: (value, options) => {
|
|
sharedIsOwner = options?.isOwner;
|
|
return { html: value, scriptHashes: [] };
|
|
},
|
|
publishedPageCsp: (_value, options) => JSON.stringify(options),
|
|
isWechatUserAgent: () => false,
|
|
});
|
|
assert.equal(sharedIsOwner, false);
|
|
});
|
|
|
|
test('decorateMindSpacePublishedHtml supports embed mode without share injection', () => {
|
|
const result = decorateMindSpacePublishedHtml({
|
|
html: '<html></html>',
|
|
embed: true,
|
|
context: { origin: '', pageUrl: '', pageDirUrl: '', fallbackImageUrl: '' },
|
|
preparePublicationHtmlForEmbed: (value) => `${value}<!--embed-->`,
|
|
injectOgTags: (value) => value,
|
|
injectWechatShareBridge: (value) => value,
|
|
injectPublicFileShareButton: (value) => ({ html: value, scriptHashes: [] }),
|
|
publishedPageCsp: (_value, options) => JSON.stringify(options),
|
|
isWechatUserAgent: () => false,
|
|
});
|
|
|
|
assert.match(result.html, /embed/);
|
|
assert.equal(result.allowEmbedFrame, true);
|
|
assert.equal(result.csp, JSON.stringify({ embed: true, wechatShare: false, scriptHashes: [] }));
|
|
});
|
|
|
|
test('decorateMindSpacePublishedHtml injects noindex when seo geo enabled for private publication', () => {
|
|
const result = decorateMindSpacePublishedHtml({
|
|
html: '<html><head><title>Demo</title></head><body></body></html>',
|
|
embed: false,
|
|
seoGeoConfig: {
|
|
enabled: true,
|
|
seo: { enabled: true, canonical: true },
|
|
geo: { enabled: true, jsonLd: true },
|
|
},
|
|
publicationSnapshot: {
|
|
status: 'online',
|
|
accessMode: 'password',
|
|
userConfirmedAt: Date.now(),
|
|
},
|
|
context: {
|
|
origin: 'https://m.tkmind.cn',
|
|
pageUrl: 'https://m.tkmind.cn/u/john/pages/demo',
|
|
pageDirUrl: 'https://m.tkmind.cn/u/john/pages/',
|
|
fallbackImageUrl: '',
|
|
},
|
|
preparePublicationHtmlForEmbed: (value) => value,
|
|
injectOgTags: (value) => value,
|
|
injectWechatShareBridge: (value) => value,
|
|
injectPublicFileShareButton: (value) => ({ html: value, scriptHashes: [] }),
|
|
publishedPageCsp: () => 'csp',
|
|
isWechatUserAgent: () => false,
|
|
});
|
|
|
|
assert.match(result.html, /noindex, nofollow/);
|
|
assert.equal(result.robotsHeader, 'noindex, nofollow');
|
|
});
|
|
|
|
test('decorateMindSpacePublishedHtml does not break inline scripts containing </body>', () => {
|
|
const pageScript = "var x='<html><body>'+tb+'</body></html>';";
|
|
const result = decorateMindSpacePublishedHtml({
|
|
html: `<html><body><script>${pageScript}</script><p>demo</p></body></html>`,
|
|
embed: false,
|
|
isOwner: true,
|
|
context: { origin: '', pageUrl: '', pageDirUrl: '', fallbackImageUrl: '' },
|
|
preparePublicationHtmlForEmbed: (value) => value,
|
|
injectOgTags: (value) => value,
|
|
injectWechatShareBridge: (value) => value,
|
|
injectPublicFileShareButton: (value) => ({ html: value, scriptHashes: [] }),
|
|
publishedPageCsp: () => '',
|
|
isWechatUserAgent: () => false,
|
|
});
|
|
assert.ok(result.html.includes(pageScript));
|
|
assert.doesNotThrow(() => {
|
|
const inline = [...result.html.matchAll(/<script(?![^>]*\bsrc)[^>]*>([\s\S]*?)<\/script>/gi)].pop()[1];
|
|
new Function(inline);
|
|
});
|
|
});
|