55100f8945
Memind CI / Test, build, and release guards (push) Failing after 2s
Generate SVG/PNG sidecars at HTML delivery and after share-preview repair so forwarded service-account links get a raster og:image when only meta exists. Co-authored-by: Cursor <cursoragent@cursor.com>
392 lines
8.9 KiB
JavaScript
392 lines
8.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
createMindSpaceWorkspacePublicationDeliveryService,
|
|
} from './mindspace-workspace-publication-delivery-service.mjs';
|
|
|
|
function fileStat() {
|
|
return {
|
|
isFile: () => true,
|
|
};
|
|
}
|
|
|
|
function createSetup(overrides = {}) {
|
|
const calls = [];
|
|
const pool = {
|
|
async query(sql, params) {
|
|
calls.push(['query', sql, params]);
|
|
return [[{ id: 'user-1' }]];
|
|
},
|
|
};
|
|
const service =
|
|
createMindSpaceWorkspacePublicationDeliveryService({
|
|
pool,
|
|
h5Root: '/project',
|
|
storageRoot: '/storage',
|
|
async resolvePublicRequestFn(options) {
|
|
calls.push([
|
|
'resolve-workspace',
|
|
options.requestPath,
|
|
]);
|
|
return {
|
|
action: 'serve',
|
|
ownerKey: 'user-1',
|
|
filePath:
|
|
'/project/MindSpace/user-1/public/page.html',
|
|
};
|
|
},
|
|
resolveUserPublishDirFn(
|
|
_h5Root,
|
|
user,
|
|
) {
|
|
return `/project/MindSpace/${user.id}`;
|
|
},
|
|
async getDeliveryContractFn(options) {
|
|
calls.push(['contract', options]);
|
|
return { status: 'ready' };
|
|
},
|
|
async materializePrivateAssetsFn(
|
|
options,
|
|
) {
|
|
calls.push(['materialize', options]);
|
|
return {
|
|
html:
|
|
'<!doctype html><p>localized</p>',
|
|
changed: true,
|
|
};
|
|
},
|
|
async readFileFn(targetPath, encoding) {
|
|
calls.push([
|
|
'read',
|
|
targetPath,
|
|
encoding,
|
|
]);
|
|
if (encoding === 'utf8') {
|
|
return '<!doctype html><img src="/api/mindspace/v1/assets/asset-1/download">';
|
|
}
|
|
return Buffer.from('asset-body');
|
|
},
|
|
async statFn() {
|
|
return fileStat();
|
|
},
|
|
existsSyncFn(targetPath) {
|
|
return targetPath.endsWith(
|
|
'.thumbnail.svg',
|
|
);
|
|
},
|
|
async renderLongImageBufferFn({
|
|
url,
|
|
}) {
|
|
calls.push(['long-image', url]);
|
|
return Buffer.from('png-body');
|
|
},
|
|
listRecentlyModifiedPublicHtmlRelativePathsFn(
|
|
publishDir,
|
|
options,
|
|
) {
|
|
calls.push([
|
|
'recent-html',
|
|
publishDir,
|
|
options,
|
|
]);
|
|
return ['public/recent.html'];
|
|
},
|
|
evaluatePageDataHtmlContentFn() {
|
|
return {
|
|
usesPageDataApi: true,
|
|
issues: [
|
|
'forbidden_local_storage',
|
|
],
|
|
};
|
|
},
|
|
detectPageDataDatasetUsageFromHtmlFn() {
|
|
return new Map([
|
|
[
|
|
'survey',
|
|
{
|
|
read: true,
|
|
insert: false,
|
|
},
|
|
],
|
|
]);
|
|
},
|
|
readPageAccessPolicyFn() {
|
|
return { datasets: {} };
|
|
},
|
|
policyAllowsActionFn() {
|
|
return false;
|
|
},
|
|
scanWorkspaceFilesForProhibitedBrowserStorageFn(
|
|
input,
|
|
) {
|
|
calls.push([
|
|
'browser-storage-scan',
|
|
input,
|
|
]);
|
|
return [
|
|
{
|
|
relativePath:
|
|
'public/page.html',
|
|
apis: ['localStorage'],
|
|
},
|
|
];
|
|
},
|
|
...overrides,
|
|
});
|
|
return { calls, pool, service };
|
|
}
|
|
|
|
test('resolves workspace HTML into a logical delivery without exposing a physical path', async () => {
|
|
const setup = createSetup();
|
|
const result =
|
|
await setup.service
|
|
.resolveWorkspaceRequest({
|
|
requestPath:
|
|
'/user-1/public/page.html',
|
|
});
|
|
assert.deepEqual(result, {
|
|
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:
|
|
'<!doctype html><p>localized</p>',
|
|
});
|
|
assert.equal(
|
|
Object.hasOwn(result, 'filePath'),
|
|
false,
|
|
);
|
|
const materialize = setup.calls.find(
|
|
([kind]) => kind === 'materialize',
|
|
);
|
|
assert.equal(
|
|
materialize[1].htmlRelativePath,
|
|
'public/page.html',
|
|
);
|
|
assert.equal(
|
|
materialize[1].writeBack,
|
|
true,
|
|
);
|
|
});
|
|
|
|
test('enforces the page delivery contract inside MindSpace', async () => {
|
|
const setup = createSetup({
|
|
async getDeliveryContractFn() {
|
|
return { status: 'validating' };
|
|
},
|
|
});
|
|
const result =
|
|
await setup.service
|
|
.resolveWorkspaceRequest({
|
|
requestPath:
|
|
'/user-1/public/page.html',
|
|
});
|
|
assert.deepEqual(result, {
|
|
action: 'not_ready',
|
|
ownerId: 'user-1',
|
|
relativePath: 'public/page.html',
|
|
});
|
|
assert.equal(
|
|
setup.calls.some(
|
|
([kind]) => kind === 'read',
|
|
),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('ensures a thumbnail sidecar before HTML delivery when share preview exists without one', async () => {
|
|
const calls = [];
|
|
const setup = createSetup({
|
|
existsSyncFn() {
|
|
return false;
|
|
},
|
|
async ensureWorkspaceThumbnailFn(publishDir, relativePath, html) {
|
|
calls.push([
|
|
'ensure-thumbnail',
|
|
publishDir,
|
|
relativePath,
|
|
html,
|
|
]);
|
|
},
|
|
});
|
|
const result =
|
|
await setup.service.resolveWorkspaceRequest({
|
|
requestPath: '/user-1/public/page.html',
|
|
});
|
|
assert.equal(result.thumbnailName, null);
|
|
assert.equal(
|
|
calls.some(([kind]) => kind === 'ensure-thumbnail'),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test('materializes and returns thumbnail binaries through the logical contract', async () => {
|
|
const setup = createSetup({
|
|
async resolvePublicRequestFn() {
|
|
return {
|
|
action: 'serve',
|
|
ownerKey: 'user-1',
|
|
filePath:
|
|
'/project/MindSpace/user-1/public/page.thumbnail.png',
|
|
};
|
|
},
|
|
async statFn(targetPath) {
|
|
if (
|
|
targetPath.endsWith(
|
|
'.thumbnail.png',
|
|
)
|
|
) {
|
|
return fileStat();
|
|
}
|
|
return null;
|
|
},
|
|
});
|
|
const result =
|
|
await setup.service
|
|
.resolveWorkspaceRequest({
|
|
requestPath:
|
|
'/user-1/public/page.thumbnail.png',
|
|
});
|
|
assert.equal(result.action, 'deliver');
|
|
assert.equal(result.kind, 'binary');
|
|
assert.equal(
|
|
Buffer.from(
|
|
result.bodyBase64,
|
|
'base64',
|
|
).toString(),
|
|
'asset-body',
|
|
);
|
|
assert.equal(
|
|
result.cacheControl,
|
|
'public, max-age=300',
|
|
);
|
|
});
|
|
|
|
test('reads /u owner public assets without returning storage paths', async () => {
|
|
const setup = createSetup();
|
|
const result =
|
|
await setup.service
|
|
.readOwnerPublicAsset({
|
|
ownerSlug: 'Alice',
|
|
requestPath: '/assets/demo.txt',
|
|
});
|
|
assert.equal(result.action, 'deliver');
|
|
assert.equal(
|
|
result.relativePath,
|
|
'public/assets/demo.txt',
|
|
);
|
|
assert.equal(
|
|
Buffer.from(
|
|
result.bodyBase64,
|
|
'base64',
|
|
).toString(),
|
|
'asset-body',
|
|
);
|
|
assert.equal(
|
|
Object.hasOwn(result, 'filePath'),
|
|
false,
|
|
);
|
|
const query = setup.calls.find(
|
|
([kind]) => kind === 'query',
|
|
);
|
|
assert.deepEqual(query[2], [
|
|
'alice',
|
|
'alice',
|
|
]);
|
|
});
|
|
|
|
test('rejects invalid owner-public paths and renders long images in MindSpace', async () => {
|
|
const setup = createSetup();
|
|
const rejected =
|
|
await setup.service
|
|
.readOwnerPublicAsset({
|
|
ownerSlug: 'alice',
|
|
requestPath: '/%2e%2e/secret.txt',
|
|
});
|
|
assert.equal(
|
|
rejected.action,
|
|
'not_found',
|
|
);
|
|
|
|
const rendered =
|
|
await setup.service.renderLongImage({
|
|
pageUrl:
|
|
'https://portal.example/MindSpace/user-1/public/page.html',
|
|
});
|
|
assert.equal(
|
|
Buffer.from(
|
|
rendered.bodyBase64,
|
|
'base64',
|
|
).toString(),
|
|
'png-body',
|
|
);
|
|
assert.equal(
|
|
rendered.servedName,
|
|
'mindspace-public-page.long.png',
|
|
);
|
|
});
|
|
|
|
test('scopes recent workspace discovery inside MindSpace', async () => {
|
|
const setup = createSetup();
|
|
const result =
|
|
await setup.service
|
|
.listRecentlyModifiedPublicHtml({
|
|
userId: 'user-1',
|
|
sinceMs: 1234,
|
|
});
|
|
assert.deepEqual(result, {
|
|
relativePaths: ['public/recent.html'],
|
|
});
|
|
assert.deepEqual(
|
|
setup.calls.find(
|
|
([kind]) => kind === 'recent-html',
|
|
),
|
|
[
|
|
'recent-html',
|
|
'/project/MindSpace/user-1',
|
|
{ sinceMs: 1234 },
|
|
],
|
|
);
|
|
});
|
|
|
|
test('validates run deliverables without exposing workspace paths to Portal', async () => {
|
|
const setup = createSetup();
|
|
const result =
|
|
await setup.service
|
|
.validateRunDeliverables({
|
|
userId: 'user-1',
|
|
deliverables: {
|
|
pages: [
|
|
{
|
|
pageId: 'page-1',
|
|
workspaceRelativePath:
|
|
'public/page.html',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
assert.deepEqual(
|
|
result.errors.map(({ code }) => code),
|
|
[
|
|
'forbidden_local_storage',
|
|
'page_data_policy_action_missing',
|
|
'browser_storage_forbidden',
|
|
],
|
|
);
|
|
assert.deepEqual(
|
|
setup.calls.find(
|
|
([kind]) =>
|
|
kind === 'browser-storage-scan',
|
|
)[1],
|
|
{
|
|
publishDir:
|
|
'/project/MindSpace/user-1',
|
|
relativePaths: ['public/page.html'],
|
|
},
|
|
);
|
|
});
|