Files
memind/release-gate/image-page-replay.test.mjs
john b1577a16e9
Memind CI / Test, build, and release guards (push) Failing after 12m3s
fix: harden release gate and page delivery
2026-07-26 14:32:01 +08:00

160 lines
6.9 KiB
JavaScript

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 { injectOgTags } from '../mindspace-og-tags.mjs';
import { materializeMissingPublicHtmlWrites } from '../mindspace-public-finish-sync.mjs';
import { buildVisionPayload } from '../tkmind-proxy.mjs';
function toolMessage(name, args) {
return {
role: 'assistant',
content: [{ type: 'toolRequest', toolCall: { value: { name, arguments: args } } }],
metadata: { userVisible: true, agentVisible: true },
};
}
function imagePageHtml() {
return `<!doctype html>
<html lang="zh-CN"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="根据上传图片生成的城市风景故事">
<meta name="mindspace-cover" content='{"tag":"旅行","cover":"images/first.jpg","subtitle":"晚霞城市"}'>
<title>晚霞城市</title>
<style>
img{max-width:100%;height:auto}.gallery{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}
@media(max-width:640px){.gallery{grid-template-columns:1fr}}
</style></head><body><main>
<h1>晚霞城市</h1><p data-vision="ocr">城市艺术节 · 2026</p>
<div class="gallery">
<figure data-order="1"><img src="images/first.jpg" alt="宝塔"></figure>
<figure data-order="2"><img src="images/second.jpg" alt="晚霞"></figure>
</div><p>暖金色、安静、适合移动端阅读。</p>
</main></body></html>`;
}
test('IMGPG-01..04 recorded vision/tool replay creates responsive OCR-aware ordered image page', async () => {
const urls = [
'https://m.tkmind.cn/MindSpace/user-fixture/public/images/first.jpg',
'https://m.tkmind.cn/MindSpace/user-fixture/public/images/second.jpg',
];
let observedOrder = [];
const vision = await buildVisionPayload({
userId: 'user-fixture',
publishLayout: { publicUrl: 'https://m.tkmind.cn/MindSpace/user-fixture/' },
userMessage: {
content: [{ type: 'text', text: '按上传顺序做成暖金色响应式页面,并保留图片文字' }],
metadata: { imageUrls: urls },
},
fetchImpl: async (url) => {
observedOrder.push(String(url));
return new Response(Buffer.from('fixture'), {
status: 200,
headers: { 'Content-Type': 'image/jpeg' },
});
},
llmProviderService: {
analyzeImagesWithVision: async () => '图片1是宝塔,图片2是晚霞;OCR:城市艺术节 · 2026。',
},
});
assert.deepEqual(observedOrder, urls);
assert.match(vision.userMessage.content[0].text, /图片1是宝塔,图片2是晚霞/);
assert.match(vision.userMessage.content[0].text, /城市艺术节/);
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'image-page-replay-'));
try {
const replay = materializeMissingPublicHtmlWrites({
publishDir: root,
messages: [toolMessage('write_file', {
path: 'public/image-story.html',
content: imagePageHtml(),
})],
});
assert.deepEqual(replay.materialized, ['public/image-story.html']);
const html = fs.readFileSync(path.join(root, 'public/image-story.html'), 'utf8');
assert.match(html, /name="viewport"/);
assert.match(html, /@media\(max-width:640px\)/);
assert.ok(html.indexOf('images/first.jpg') < html.indexOf('images/second.jpg'));
assert.match(html, /data-vision="ocr">城市艺术节 · 2026/);
assert.match(html, /暖金色、安静/);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('IMGPG-05..07 delivery keeps safe assets, OG/share metadata, image identity and page address', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'image-page-delivery-'));
try {
const pagePath = path.join(root, 'public/image-story.html');
fs.mkdirSync(path.dirname(pagePath), { recursive: true });
fs.writeFileSync(pagePath, imagePageHtml());
const first = injectOgTags(fs.readFileSync(pagePath, 'utf8'), {
origin: 'https://m.tkmind.cn',
pageUrl: 'https://m.tkmind.cn/MindSpace/user-fixture/public/image-story.html',
pageDirUrl: 'https://m.tkmind.cn/MindSpace/user-fixture/public/',
});
fs.writeFileSync(pagePath, first);
assert.match(first, /og:image/);
assert.match(first, /images\/first\.jpg/);
assert.doesNotMatch(first, /(?:\/Users\/|\/home\/|localhost|127\.0\.0\.1)/);
const patch = materializeMissingPublicHtmlWrites({
publishDir: root,
messages: [toolMessage('edit_file', {
path: 'public/image-story.html',
old_str: '暖金色、安静、适合移动端阅读。',
new_str: '暖金色、充满活力、适合移动端阅读。',
})],
});
assert.deepEqual(patch.materialized, ['public/image-story.html']);
const updated = fs.readFileSync(pagePath, 'utf8');
assert.match(updated, /images\/first\.jpg/);
assert.match(updated, /images\/second\.jpg/);
assert.match(updated, /充满活力/);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('IMGPG-08 unreadable or unauthorized image fails closed without invented vision text', async () => {
let visionCalled = false;
const result = await buildVisionPayload({
userId: 'user-fixture',
publishLayout: { publicUrl: 'https://m.tkmind.cn/MindSpace/user-fixture/' },
userMessage: {
content: [{ type: 'text', text: '识别这张无权限图片' }],
metadata: { imageUrls: ['/api/mindspace/v1/assets/denied/download?inline=1'] },
},
fetchImpl: async () => new Response('forbidden', { status: 403 }),
llmProviderService: {
analyzeImagesWithVision: async () => {
visionCalled = true;
return '不应出现的伪造识别';
},
},
});
assert.equal(visionCalled, false);
assert.doesNotMatch(result?.userMessage?.content?.[0]?.text ?? '', /不应出现的伪造识别/);
assert.equal(result?.billableImageCount ?? 0, 0);
});
test('PAGE-11 recorded daily composite page requires every section, citations, disclosure and artifact', () => {
const html = `<!doctype html><html lang="zh-CN"><head>
<meta name="viewport" content="width=device-width,initial-scale=1"><title>每日资讯</title>
</head><body><main>
<section data-kind="news"><h2>当日新闻</h2><a href="https://www.gov.cn/">国务院来源</a></section>
<section data-kind="weather"><h2>天气</h2><a href="https://www.nmc.cn/">中央气象台</a></section>
<section data-kind="market"><h2>市场 / 股票</h2><a href="https://www.sse.com.cn/">上交所</a></section>
<section data-kind="knowledge"><h2>知识卡片</h2><p>市盈率基础知识</p></section>
<aside data-kind="compliance">信息仅供参考,不构成投资建议;数据时间 2026-07-26。</aside>
</main></body></html>`;
const required = ['news', 'weather', 'market', 'knowledge', 'compliance'];
for (const kind of required) assert.match(html, new RegExp(`data-kind="${kind}"`));
assert.equal((html.match(/href="https:\/\//g) ?? []).length, 3);
assert.match(html, /不构成投资建议/);
assert.match(html, /name="viewport"/);
});