Initial commit: Memind H5 portal with MindSpace, Plaza, and agent jobs.
Track application source and tests; exclude local env, user workspaces, and runtime data via .gitignore. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { createAssetService, validateUploadRequest } from './mindspace-assets.mjs';
|
||||
|
||||
function createMockPool(state) {
|
||||
return {
|
||||
async getConnection() {
|
||||
return {
|
||||
async beginTransaction() {},
|
||||
async commit() {},
|
||||
async rollback() {},
|
||||
release() {},
|
||||
async query(sql, params = []) {
|
||||
if (sql.includes('FROM h5_space_categories') && sql.includes('FOR UPDATE')) {
|
||||
const category = state.categories.find(
|
||||
(item) => item.id === params[0] && item.user_id === params[1],
|
||||
);
|
||||
return [category ? [category] : []];
|
||||
}
|
||||
if (sql.includes('FROM h5_user_spaces') && sql.includes('FOR UPDATE')) {
|
||||
const space = state.spaces.find(
|
||||
(item) => item.id === params[0] && item.user_id === params[1],
|
||||
);
|
||||
return [space ? [space] : []];
|
||||
}
|
||||
if (sql.includes('INSERT INTO h5_upload_sessions')) {
|
||||
state.uploads.push({
|
||||
id: params[0],
|
||||
user_id: params[1],
|
||||
space_id: params[2],
|
||||
category_id: params[3],
|
||||
filename: params[4],
|
||||
expected_size: params[5],
|
||||
reserved_bytes: params[7],
|
||||
temporary_storage_key: params[8],
|
||||
status: 'reserved',
|
||||
expires_at: params[9],
|
||||
});
|
||||
return [[]];
|
||||
}
|
||||
if (sql.includes('UPDATE h5_user_spaces') && sql.includes('reserved_bytes = reserved_bytes +')) {
|
||||
const space = state.spaces.find((item) => item.id === params[2]);
|
||||
space.reserved_bytes += params[0];
|
||||
return [[]];
|
||||
}
|
||||
if (sql.includes('FROM h5_upload_sessions') && sql.includes('WHERE id = ?')) {
|
||||
const upload = state.uploads.find(
|
||||
(item) => item.id === params[0] && item.user_id === params[1],
|
||||
);
|
||||
return [upload ? [upload] : []];
|
||||
}
|
||||
if (sql.includes('UPDATE h5_upload_sessions') && sql.includes("status = 'uploaded'")) {
|
||||
const upload = state.uploads.find(
|
||||
(item) => item.id === params[3] && item.user_id === params[4],
|
||||
);
|
||||
upload.actual_size = params[0];
|
||||
upload.detected_mime_type = params[1];
|
||||
upload.checksum = params[2];
|
||||
upload.status = 'uploaded';
|
||||
return [[]];
|
||||
}
|
||||
if (sql.includes('FROM h5_upload_sessions u') && sql.includes('FOR UPDATE')) {
|
||||
const upload = state.uploads.find(
|
||||
(item) => item.id === params[0] && item.user_id === params[1],
|
||||
);
|
||||
if (!upload) return [[]];
|
||||
const category = state.categories.find((item) => item.id === upload.category_id);
|
||||
return [[{ ...upload, category_code: category.category_code }]];
|
||||
}
|
||||
if (sql.includes('INSERT INTO h5_assets')) {
|
||||
state.assets.push({
|
||||
id: params[0],
|
||||
user_id: params[1],
|
||||
status: params[13],
|
||||
risk_level: params[11],
|
||||
current_version_id: params[8],
|
||||
original_filename: params[6],
|
||||
mime_type: params[5],
|
||||
size_bytes: params[9],
|
||||
});
|
||||
return [[]];
|
||||
}
|
||||
if (sql.includes('INSERT INTO h5_asset_versions')) {
|
||||
state.versions.push({
|
||||
id: params[0],
|
||||
asset_id: params[1],
|
||||
storage_key: params[3],
|
||||
scan_status: params[8],
|
||||
});
|
||||
return [[]];
|
||||
}
|
||||
if (sql.includes('UPDATE h5_user_spaces') && sql.includes('used_bytes = used_bytes +')) {
|
||||
const space = state.spaces.find((item) => item.id === params[3]);
|
||||
space.reserved_bytes -= params[0];
|
||||
space.used_bytes += params[1];
|
||||
return [[]];
|
||||
}
|
||||
if (sql.includes("status = 'completed'")) {
|
||||
const upload = state.uploads.find(
|
||||
(item) => item.id === params[2] && item.user_id === params[3],
|
||||
);
|
||||
upload.status = 'completed';
|
||||
upload.completed_asset_id = params[1];
|
||||
return [[]];
|
||||
}
|
||||
if (sql.includes('JOIN h5_asset_versions v')) {
|
||||
const asset = state.assets.find(
|
||||
(item) => item.id === params[0] && item.user_id === params[1],
|
||||
);
|
||||
if (!asset) return [[]];
|
||||
const version = state.versions.find((item) => item.id === asset.current_version_id);
|
||||
const category = state.categories.find((item) => item.id === asset.category_id);
|
||||
return [[
|
||||
{
|
||||
...asset,
|
||||
category_code: category?.category_code ?? 'oa',
|
||||
storage_key: version.storage_key,
|
||||
scan_status: version.scan_status,
|
||||
category_id: category?.id,
|
||||
display_name: asset.original_filename,
|
||||
asset_type: 'file',
|
||||
visibility: 'private',
|
||||
source_type: 'upload',
|
||||
checksum: 'abc',
|
||||
created_at: 1,
|
||||
updated_at: 1,
|
||||
},
|
||||
]];
|
||||
}
|
||||
if (sql.includes("status IN ('reserved', 'uploaded') AND expires_at")) {
|
||||
return [state.uploads.filter((item) => item.expires_at <= params[0])];
|
||||
}
|
||||
return [[]];
|
||||
},
|
||||
};
|
||||
},
|
||||
async query(sql, params = []) {
|
||||
if (sql.includes('FROM h5_upload_sessions') && sql.includes('WHERE id = ?')) {
|
||||
const upload = state.uploads.find(
|
||||
(item) => item.id === params[0] && item.user_id === params[1],
|
||||
);
|
||||
return [upload ? [upload] : []];
|
||||
}
|
||||
if (sql.includes('UPDATE h5_upload_sessions') && sql.includes("status = 'uploaded'")) {
|
||||
const upload = state.uploads.find(
|
||||
(item) => item.id === params[3] && item.user_id === params[4],
|
||||
);
|
||||
upload.actual_size = params[0];
|
||||
upload.detected_mime_type = params[1];
|
||||
upload.checksum = params[2];
|
||||
upload.status = 'uploaded';
|
||||
return [[]];
|
||||
}
|
||||
return [[]];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('validateUploadRequest rejects traversal filenames', () => {
|
||||
assert.throws(
|
||||
() => validateUploadRequest({ filename: '../secret.pdf', sizeBytes: 10 }),
|
||||
(error) => error.code === 'invalid_filename',
|
||||
);
|
||||
});
|
||||
|
||||
test('completeUpload marks safe files ready and blocks script payloads', async () => {
|
||||
const storageRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-assets-'));
|
||||
const state = {
|
||||
categories: [
|
||||
{ id: 'cat-1', user_id: 'user-1', space_id: 'space-1', category_code: 'oa' },
|
||||
{ id: 'cat-2', user_id: 'user-2', space_id: 'space-2', category_code: 'oa' },
|
||||
],
|
||||
spaces: [
|
||||
{
|
||||
id: 'space-1',
|
||||
user_id: 'user-1',
|
||||
quota_bytes: 5 * 1024 * 1024,
|
||||
used_bytes: 0,
|
||||
reserved_bytes: 0,
|
||||
status: 'active',
|
||||
},
|
||||
],
|
||||
uploads: [],
|
||||
assets: [],
|
||||
versions: [],
|
||||
};
|
||||
let nextId = 0;
|
||||
const service = createAssetService(createMockPool(state), {
|
||||
storageRoot,
|
||||
idFactory: () => `id-${++nextId}`,
|
||||
});
|
||||
|
||||
const payload = Buffer.from('<script>alert(1)</script>');
|
||||
const upload = await service.createUpload('user-1', {
|
||||
categoryId: 'cat-1',
|
||||
filename: 'note.txt',
|
||||
sizeBytes: payload.length,
|
||||
});
|
||||
await service.writeUploadContent('user-1', upload.id, payload);
|
||||
const asset = await service.completeUpload('user-1', upload.id);
|
||||
assert.equal(asset.status, 'quarantined');
|
||||
assert.equal(asset.scanStatus, 'blocked');
|
||||
});
|
||||
|
||||
test('createUpload rejects another users category id', async () => {
|
||||
const state = {
|
||||
categories: [
|
||||
{ id: 'cat-1', user_id: 'user-1', space_id: 'space-1', category_code: 'oa' },
|
||||
{ id: 'cat-2', user_id: 'user-2', space_id: 'space-2', category_code: 'oa' },
|
||||
],
|
||||
spaces: [
|
||||
{
|
||||
id: 'space-1',
|
||||
user_id: 'user-1',
|
||||
quota_bytes: 5 * 1024 * 1024,
|
||||
used_bytes: 0,
|
||||
reserved_bytes: 0,
|
||||
status: 'active',
|
||||
},
|
||||
],
|
||||
uploads: [],
|
||||
assets: [],
|
||||
versions: [],
|
||||
};
|
||||
const service = createAssetService(createMockPool(state), {
|
||||
storageRoot: await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-assets-')),
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
service.createUpload('user-1', {
|
||||
categoryId: 'cat-2',
|
||||
filename: 'note.txt',
|
||||
sizeBytes: 4,
|
||||
}),
|
||||
(error) => error.code === 'category_not_found',
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user