feat: complete mindspace conversation packages
This commit is contained in:
@@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { publicationInternals } from './mindspace-publications.mjs';
|
||||
import { createPublicationService, publicationInternals } from './mindspace-publications.mjs';
|
||||
|
||||
test('normalizes safe long-page slugs', () => {
|
||||
assert.equal(publicationInternals.normalizeSlug(' product-intro-2026 '), 'product-intro-2026');
|
||||
@@ -150,6 +150,55 @@ test('buildPublicationThumbnailFallback points private resources at the public p
|
||||
);
|
||||
});
|
||||
|
||||
test('resolvePublic returns page provenance for package artifact registration', async () => {
|
||||
const storageRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-public-resolve-'));
|
||||
const storageKey = 'users/user-1/publications/pub-1/index.html';
|
||||
await fs.mkdir(path.dirname(path.join(storageRoot, storageKey)), { recursive: true });
|
||||
await fs.writeFile(path.join(storageRoot, storageKey), '<!doctype html><title>发布页</title>');
|
||||
const service = createPublicationService(
|
||||
{
|
||||
async query(sql) {
|
||||
if (sql.includes('FROM h5_publish_records pr') && sql.includes('COALESCE(u.slug, u.username)')) {
|
||||
return [[
|
||||
{
|
||||
id: 'pub-1',
|
||||
user_id: 'user-1',
|
||||
owner_id: 'user-1',
|
||||
page_id: 'page-1',
|
||||
page_version_id: 'version-1',
|
||||
title: '发布页',
|
||||
source_session_id: 'session-1',
|
||||
source_message_id: 'message-1',
|
||||
url_slug: 'published',
|
||||
public_url: 'https://m.tkmind.cn/u/john/pages/published',
|
||||
access_mode: 'public',
|
||||
status: 'online',
|
||||
view_count: 0,
|
||||
published_at: 3000,
|
||||
storage_key: storageKey,
|
||||
},
|
||||
]];
|
||||
}
|
||||
return [[]];
|
||||
},
|
||||
},
|
||||
{
|
||||
storageRoot,
|
||||
idFactory: () => 'view-1',
|
||||
},
|
||||
);
|
||||
|
||||
const result = await service.resolvePublic('john', 'published', null, null, {});
|
||||
|
||||
assert.equal(result.ownerId, 'user-1');
|
||||
assert.deepEqual(result.pageSource, {
|
||||
pageId: 'page-1',
|
||||
title: '发布页',
|
||||
sourceSessionId: 'session-1',
|
||||
sourceMessageId: 'message-1',
|
||||
});
|
||||
});
|
||||
|
||||
test('prepareHtmlPublishContent inlines owned private image assets before scanning', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-publish-'));
|
||||
const storageKey = 'users/user-1/assets/asset-1/current.png';
|
||||
@@ -348,6 +397,121 @@ test('registerPublicationArtifactForConversation records public companions', asy
|
||||
assert.match(docx.canonicalUrl, /\/MindSpace\/user-1\/public\/report\.docx$/);
|
||||
});
|
||||
|
||||
test('publish registers public html and companion artifacts for chat-sourced pages', async () => {
|
||||
const storageRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-publish-flow-'));
|
||||
const h5Root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-h5-root-'));
|
||||
const pageStorageKey = 'users/user-1/pages/page-1/versions/v1.html';
|
||||
const pageHtml = '<!doctype html><title>Report</title><main>发布内容</main>';
|
||||
await fs.mkdir(path.dirname(path.join(storageRoot, pageStorageKey)), { recursive: true });
|
||||
await fs.writeFile(path.join(storageRoot, pageStorageKey), pageHtml);
|
||||
const publishDir = path.join(h5Root, 'MindSpace', 'user-1');
|
||||
await fs.mkdir(path.join(publishDir, 'public'), { recursive: true });
|
||||
await fs.writeFile(path.join(publishDir, 'public', 'report.docx'), 'docx');
|
||||
await fs.writeFile(path.join(publishDir, 'public', 'report.long.png'), 'png');
|
||||
const registryCalls = [];
|
||||
let idCounter = 0;
|
||||
const pageRow = {
|
||||
page_id: 'page-1',
|
||||
title: 'Report',
|
||||
summary: '',
|
||||
page_type: 'html',
|
||||
template_id: 'static-html',
|
||||
current_version_id: 'version-1',
|
||||
source_session_id: 'session-1',
|
||||
source_message_id: 'message-1',
|
||||
user_id: 'user-1',
|
||||
space_id: 'space-1',
|
||||
page_version_id: 'version-1',
|
||||
version_no: 1,
|
||||
bundle_asset_id: null,
|
||||
storage_key: pageStorageKey,
|
||||
source_relative_path: 'public/report.html',
|
||||
};
|
||||
const pool = {
|
||||
async query(sql, params = []) {
|
||||
if (sql.includes('FROM h5_page_records p') && sql.includes('JOIN h5_page_versions')) {
|
||||
assert.deepEqual(params, ['page-1', 'user-1', 'version-1']);
|
||||
return [[pageRow]];
|
||||
}
|
||||
if (sql.includes('FROM h5_publish_records pr') && sql.includes('pr.page_id <>')) {
|
||||
return [[]];
|
||||
}
|
||||
if (sql.includes('FROM h5_users')) {
|
||||
return [[{ public_slug: 'john' }]];
|
||||
}
|
||||
if (sql.includes('FROM h5_assets') || sql.includes('FROM h5_asset_versions')) {
|
||||
return [[]];
|
||||
}
|
||||
return [[]];
|
||||
},
|
||||
async getConnection() {
|
||||
return {
|
||||
async beginTransaction() {},
|
||||
async commit() {},
|
||||
async rollback() {},
|
||||
release() {},
|
||||
async query(sql) {
|
||||
if (sql.includes('FROM h5_user_spaces') && sql.includes('FOR UPDATE')) {
|
||||
return [[{ quota_bytes: 1_000_000, used_bytes: 0, reserved_bytes: 0 }]];
|
||||
}
|
||||
if (sql.includes('COUNT(DISTINCT page_id)')) {
|
||||
return [[{ public_page_used: 0, page_already_online: 0 }]];
|
||||
}
|
||||
if (sql.includes('FROM h5_space_categories')) {
|
||||
return [[{ id: 'cat-public' }]];
|
||||
}
|
||||
if (sql.includes('SELECT id, page_version_id FROM h5_publish_records')) {
|
||||
return [[]];
|
||||
}
|
||||
return [[]];
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
const service = createPublicationService(pool, {
|
||||
storageRoot,
|
||||
h5Root,
|
||||
idFactory: () => `id-${++idCounter}`,
|
||||
conversationPackageRegistry: {
|
||||
async ensurePackage(input) {
|
||||
registryCalls.push(['ensurePackage', input]);
|
||||
return { id: 'cp-session-1' };
|
||||
},
|
||||
async recordArtifact(input) {
|
||||
registryCalls.push(['recordArtifact', input]);
|
||||
return input;
|
||||
},
|
||||
async writeManifestForSession(input) {
|
||||
registryCalls.push(['writeManifestForSession', input]);
|
||||
return input;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const publication = await service.publish('user-1', 'page-1', {
|
||||
pageVersionId: 'version-1',
|
||||
accessMode: 'public',
|
||||
urlSlug: 'report',
|
||||
acknowledgedFindingIds: [],
|
||||
});
|
||||
|
||||
assert.equal(publication.id, 'id-4');
|
||||
const artifactKinds = registryCalls
|
||||
.filter(([name]) => name === 'recordArtifact')
|
||||
.map(([, input]) => input.artifactKind)
|
||||
.sort();
|
||||
assert.deepEqual(artifactKinds, ['docx', 'long_image', 'public_html']);
|
||||
const companion = registryCalls.find(([, input]) => input?.artifactKind === 'docx')?.[1];
|
||||
assert.equal(companion.messageId, 'message-1');
|
||||
assert.equal(companion.publicationId, 'id-4');
|
||||
assert.equal(registryCalls.at(-1)[0], 'writeManifestForSession');
|
||||
} finally {
|
||||
await fs.rm(storageRoot, { recursive: true, force: true });
|
||||
await fs.rm(h5Root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('registerPublicationArtifactForConversation skips pages without source session', async () => {
|
||||
assert.equal(
|
||||
await publicationInternals.registerPublicationArtifactForConversation({
|
||||
|
||||
Reference in New Issue
Block a user