import assert from 'node:assert/strict'; import test from 'node:test'; import { createPortalWorkspacePublicationDelivery, } from './portal-workspace-publication-delivery.mjs'; function createResponse() { return { statusCode: 200, body: undefined, headers: {}, redirectValue: null, status(code) { this.statusCode = code; return this; }, type(value) { this.headers['Content-Type'] = value; return this; }, set(name, value) { this.headers[name] = value; return this; }, json(body) { this.body = body; return this; }, send(body) { this.body = body; return this; }, redirect(status, location) { this.statusCode = status; this.redirectValue = location; return this; }, }; } function createRequest(overrides = {}) { return { path: '/user-1/public/page.html', originalUrl: '/MindSpace/user-1/public/page.html', url: '/user-1/public/page.html', query: {}, userSession: null, userToken: null, get(name) { return name === 'user-agent' ? 'test-agent' : ''; }, ...overrides, }; } function htmlDelivery() { return { action: 'deliver', kind: 'html', ownerId: 'user-1', relativePath: 'public/page.html', servedName: 'page.html', canonicalPath: '/MindSpace/user-1/public/page.html', thumbnailName: 'page.thumbnail.png', html: 'Demo
Demo
', }; } function createSetup(overrides = {}) { let deliveryResult = htmlDelivery(); let userAuth = null; const calls = []; const deliveryService = { async resolveWorkspaceRequest(input) { calls.push([ 'resolve-workspace', input, ]); return deliveryResult; }, async renderLongImage(input) { calls.push(['long-image', input]); return { servedName: 'mindspace-public-page.long.png', bodyBase64: Buffer.from('png-body').toString( 'base64', ), }; }, }; const service = createPortalWorkspacePublicationDelivery({ internalAgentSecret: 'test-secret', analyticsConfig: { enabled: false }, rybbitConfig: { enabled: false }, getAuthPool: () => null, getUserAuth: () => userAuth, getMindSpacePages: () => null, getWorkspacePublicationDelivery: () => deliveryService, resolveRequestOrigin: () => 'https://portal.example', removeQueryParam(url, key) { calls.push([ 'remove-query', url, key, ]); return new URL(url) .toString() .replace( new RegExp( `([?&])${key}=[^&]*&?`, ), '$1', ) .replace(/[?&]$/, ''); }, ...overrides, }); return { service, calls, deliveryService, setDeliveryResult(value) { deliveryResult = value; }, setUserAuth(value) { userAuth = value; }, }; } async function serve(setup, request) { const response = createResponse(); await setup.service.serveUserPublishFile( request ?? createRequest(), response, () => {}, ); return response; } test('maps redirects, forbidden, not-ready, and not-found delivery results', async () => { const setup = createSetup(); setup.setDeliveryResult({ action: 'redirect', status: 302, location: '/canonical', }); const redirect = await serve(setup); assert.equal(redirect.statusCode, 302); assert.equal( redirect.redirectValue, '/canonical', ); setup.setDeliveryResult({ action: 'forbidden', }); const forbidden = await serve(setup); assert.equal(forbidden.statusCode, 403); setup.setDeliveryResult({ action: 'not_ready', }); const notReady = await serve(setup); assert.equal(notReady.statusCode, 409); assert.match( notReady.body, /正在完成发布验证/, ); for (const [reason, message] of [ ['missing_owner_dir', '用户不存在'], [ 'missing_directory_index', '目录中没有 index.html', ], ['missing_file', '文件不存在'], ]) { setup.setDeliveryResult({ action: 'not_found', reason, }); const response = await serve(setup); assert.equal(response.statusCode, 404); assert.deepEqual(response.body, { message, }); } }); test('passes only the logical request path to MindSpace', async () => { const setup = createSetup(); await serve(setup); assert.deepEqual(setup.calls[0], [ 'resolve-workspace', { requestPath: '/user-1/public/page.html', }, ]); }); test('proxies binary bodies without receiving a physical path', async () => { const setup = createSetup(); setup.setDeliveryResult({ action: 'deliver', kind: 'binary', ownerId: 'user-1', relativePath: 'public/page.thumbnail.png', servedName: 'page.thumbnail.png', bodyBase64: Buffer.from('png-body').toString( 'base64', ), cacheControl: 'public, max-age=300', }); const response = await serve(setup); assert.equal( response.body.toString(), 'png-body', ); assert.equal( response.headers['Cache-Control'], 'public, max-age=300', ); assert.equal( Object.hasOwn( setup.calls[0][1], 'filePath', ), false, ); }); test('decorates HTML at the edge and preserves viewer-specific no-store delivery', async () => { const setup = createSetup(); setup.setUserAuth({ async getMe() { return { id: 'viewer-1' }; }, async getUserById() { return { id: 'user-1' }; }, }); const response = await serve(setup); assert.equal(response.statusCode, 200); assert.equal( response.headers['Cache-Control'], 'private, no-store', ); assert.equal( response.headers['Content-Type'], 'text/html; charset=utf-8', ); assert.match(String(response.body), /Demo/); assert.match( String(response.body), /page\.thumbnail\.png/, ); }); test('delegates long-image rendering to MindSpace', async () => { const setup = createSetup(); const response = await serve( setup, createRequest({ originalUrl: '/MindSpace/user-1/public/page.html?download=long-image', query: { download: 'long-image', }, }), ); assert.equal( response.headers['Content-Type'], 'image/png', ); assert.equal( response.body.toString(), 'png-body', ); assert.equal( setup.calls.some( ([kind]) => kind === 'long-image', ), true, ); });