160 lines
6.9 KiB
JavaScript
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"/);
|
|
});
|