Files
memind/mindspace-thumbnails.test.mjs
Your Name b0f5d6a51c Extract memind_adm admin server, add local dev tooling, and remove image-generation.
Split platform admin and ops APIs into standalone admin-server.mjs with network guards; simplify billing to RMB token pricing, refactor user auth, and add rsync deploy plus local-test scripts and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 16:39:39 -07:00

166 lines
6.4 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
buildFeedThumbnailSvg,
buildThumbnailSvg,
ensureHtmlThumbnail,
extractCoverSignals,
generateHtmlThumbnail,
isModernFeedThumbnail,
resolveCoverDataUri,
shouldUseScenicBackground,
} from './mindspace-thumbnails.mjs';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
test('buildThumbnailSvg escapes title text', () => {
const svg = buildThumbnailSvg({
title: '<script>alert(1)</script>',
subtitle: '测试',
accent: '#2f6f57',
});
assert.doesNotMatch(svg, /<script>alert/);
assert.match(svg, /&lt;script&gt;alert/);
});
test('generateHtmlThumbnail writes svg file', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-thumb-'));
const key = 'users/u1/assets/a1/thumbnail.svg';
await generateHtmlThumbnail(
root,
key,
'<!doctype html><html><head><title>测试页面</title></head><body></body></html>',
{ subtitle: 'HTML' },
);
const written = await fs.readFile(path.join(root, key), 'utf8');
assert.match(written, /测试页面/);
});
test('extractCoverSignals reads mindspace-cover meta and html colors', () => {
const html = `<!doctype html><html><head>
<title>🇲🇾 马来西亚旅游攻略</title>
<meta name="mindspace-cover" content='{"tag":"旅行","accent":"#ff6b35","emoji":"🇲🇾"}' />
<style>body { background: linear-gradient(135deg, #0f0c29, #24243e); }</style>
</head><body><h1>马来西亚旅游攻略</h1><p>沙滩、美食与城市漫步。</p></body></html>`;
const signals = extractCoverSignals(html);
assert.equal(signals.tag, '旅行');
assert.equal(signals.emoji, '🇲🇾');
assert.equal(signals.accent, '#ff6b35');
assert.match(signals.subtitle, /沙滩/);
});
test('buildFeedThumbnailSvg uses portrait 3:4 canvas', () => {
const svg = buildFeedThumbnailSvg({
title: '2026 市场趋势分析',
subtitle: 'AI 生成的研究报告',
tag: '报告',
emoji: '📊',
accent: '#2f6f57',
accent2: '#18211d',
mood: 'report',
});
assert.match(svg, /width="540" height="720"/);
assert.match(svg, /2026 市场趋势分析/);
assert.match(svg, /filter id="grain"/);
});
test('extractCoverSignals reads cover image from mindspace-cover meta', () => {
const html = `<!doctype html><html><head>
<meta name="mindspace-cover" content='{"cover":"assets/hero.png","tag":"旅行"}' />
</head><body></body></html>`;
const signals = extractCoverSignals(html);
assert.equal(signals.image, 'assets/hero.png');
assert.equal(signals.tag, '旅行');
});
test('buildFeedThumbnailSvg embeds photo cover layer', () => {
const dataUri =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==';
const svg = buildFeedThumbnailSvg(
{
title: '实拍封面',
subtitle: '底图测试',
tag: '旅行',
accent: '#2f6f57',
accent2: '#18211d',
},
{ coverDataUri: dataUri },
);
assert.match(svg, /preserveAspectRatio="xMidYMid slice"/);
assert.match(svg, /href="data:image\/png;base64,/);
assert.doesNotMatch(svg, /M0 470 L90 404/);
});
test('generateHtmlThumbnail resolves relative cover image', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-thumb-cover-'));
const png = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mNk+M9Qz0AEYBxVSF+FABJADveWkH6oAAAAAElFTkSuQmCC',
'base64',
);
await fs.mkdir(path.join(root, 'users', 'u1', 'pages', 'p1', 'versions'), { recursive: true });
await fs.writeFile(path.join(root, 'users', 'u1', 'pages', 'p1', 'versions', 'hero.png'), png);
const html = `<!doctype html><html><head>
<title>封面页</title>
<meta name="mindspace-cover" content='{"cover":"hero.png","tag":"旅行"}' />
</head><body></body></html>`;
const key = 'users/u1/pages/p1/thumbnail.svg';
await generateHtmlThumbnail(root, key, html, {
contentStorageKey: 'users/u1/pages/p1/versions/page.html',
});
const written = await fs.readFile(path.join(root, key), 'utf8');
assert.match(written, /preserveAspectRatio="xMidYMid slice"/);
assert.match(written, /href="data:image\/png;base64,/);
});
test('extractCoverSignals infers sport tag for pilates pages', () => {
const html = '<!doctype html><html><head><title>普拉提618</title></head><body></body></html>';
const signals = extractCoverSignals(html);
assert.equal(signals.tag, '运动');
});
test('buildFeedThumbnailSvg uses theme gradient for sport pages', () => {
const signals = extractCoverSignals(
'<!doctype html><html><head><title>普拉提618</title><style>body{color:#eeb04e;background:#1a0a2e}</style></head><body></body></html>',
);
assert.equal(shouldUseScenicBackground(signals), false);
const svg = buildFeedThumbnailSvg(signals);
assert.match(svg, /id="theme"/);
assert.doesNotMatch(svg, /M0 470 L90 404/);
});
test('isModernFeedThumbnail detects legacy landscape thumbnails', () => {
const legacy = `<?xml version="1.0"?><svg width="640" height="400"></svg>`;
const modern = buildFeedThumbnailSvg({
title: '新封面',
subtitle: '副标题',
tag: '网页',
accent: '#2f6f57',
accent2: '#18211d',
});
assert.equal(isModernFeedThumbnail(legacy), false);
assert.equal(isModernFeedThumbnail(modern), true);
});
test('ensureHtmlThumbnail upgrades legacy thumbnail file', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-thumb-upgrade-'));
const key = 'users/u1/pages/p1/thumbnail.svg';
const legacy = `<?xml version="1.0"?><svg width="640" height="400"><text>旧版</text></svg>`;
await fs.mkdir(path.dirname(path.join(root, key)), { recursive: true });
await fs.writeFile(path.join(root, key), legacy, 'utf8');
const html = '<!doctype html><html><head><title>升级测试</title></head><body></body></html>';
const svg = await ensureHtmlThumbnail(root, key, html, { title: '升级测试', tag: '网页' });
assert.equal(isModernFeedThumbnail(svg), true);
assert.match(svg, /升级测试/);
});
test('resolveCoverDataUri rejects paths outside content base', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-thumb-guard-'));
const dataUri = await resolveCoverDataUri({
storageRoot: root,
contentStorageKey: 'users/u1/pages/p1/versions/page.html',
imageUrl: '../../../secret.png',
});
assert.equal(dataUri, null);
});