Files
memind/mindspace-workspace-sync.test.mjs
T
John 2e14873f2d 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>
2026-06-15 15:04:43 -07:00

151 lines
5.3 KiB
JavaScript

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 {
createWorkspaceAssetSync,
listWorkspaceZoneFiles,
shouldSyncWorkspaceFilename,
} from './mindspace-workspace-sync.mjs';
import { resolveUserWorkspaceRoot } from './user-space.mjs';
test('shouldSyncWorkspaceFilename skips helper files', () => {
assert.equal(shouldSyncWorkspaceFilename('端午感怀.docx'), true);
assert.equal(shouldSyncWorkspaceFilename('index.html'), false);
assert.equal(shouldSyncWorkspaceFilename('note.thumbnail.svg'), false);
});
test('listWorkspaceZoneFiles returns only supported top-level zone files', async () => {
const h5Root = await fs.mkdtemp(path.join(os.tmpdir(), 'h5-sync-'));
const workspace = resolveUserWorkspaceRoot(h5Root, { id: 'user-1', username: 'john' });
await fs.mkdir(path.join(workspace, 'oa'), { recursive: true });
await fs.writeFile(path.join(workspace, 'oa', 'report.csv'), 'a,b\n1,2\n');
await fs.writeFile(path.join(workspace, 'oa', 'index.html'), '<html></html>');
await fs.writeFile(path.join(workspace, 'oa', 'note.txt'), 'hello');
const files = await listWorkspaceZoneFiles(workspace, 'oa');
assert.deepEqual(
files.map((item) => item.filename).sort(),
['note.txt', 'report.csv'],
);
});
test('syncUserWorkspace imports new workspace files into asset library', async () => {
const h5Root = await fs.mkdtemp(path.join(os.tmpdir(), 'h5-sync-'));
const storageRoot = path.join(h5Root, 'data', 'mindspace');
const workspace = resolveUserWorkspaceRoot(h5Root, { id: 'user-1', username: 'john' });
await fs.mkdir(path.join(workspace, 'oa'), { recursive: true });
await fs.writeFile(path.join(workspace, 'oa', 'memo.txt'), 'hello workspace\n');
const state = {
users: [{ id: 'user-1', username: 'john' }],
categories: [
{ id: 'cat-1', user_id: 'user-1', space_id: 'space-1', category_code: 'oa' },
],
spaces: [
{
id: 'space-1',
user_id: 'user-1',
quota_bytes: 5 * 1024 * 1024,
used_bytes: 0,
reserved_bytes: 0,
status: 'active',
},
],
assets: [],
versions: [],
};
let nextId = 0;
const pool = {
async query(sql, params = []) {
if (sql.includes('SELECT username FROM h5_users')) {
return [[{ username: 'john' }]];
}
if (sql.includes('FROM h5_space_categories c') && sql.includes('category_code = ?')) {
const category = state.categories.find(
(item) => item.user_id === params[0] && item.category_code === params[1],
);
return [category ? [category] : []];
}
if (sql.includes('FROM h5_assets')) {
const assets = state.assets.filter((item) => {
if (item.user_id !== params[0] || item.category_id !== params[1]) return false;
if (sql.includes("status <> 'deleted'") && item.status === 'deleted') return false;
if (sql.includes("status = 'deleted'") && item.status !== 'deleted') return false;
if (sql.includes("source_type = 'workspace'") && item.source_type !== 'workspace') {
return false;
}
return true;
});
return [assets];
}
return [[]];
},
async getConnection() {
return {
async beginTransaction() {},
async commit() {},
async rollback() {},
release() {},
async query(sql, params = []) {
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_assets')) {
state.assets.push({
id: params[0],
user_id: params[1],
category_id: params[3],
original_filename: params[6],
checksum: params[10],
size_bytes: params[9],
current_version_id: params[8],
status: params[13],
source_type: 'workspace',
});
return [[]];
}
if (sql.includes('INSERT INTO h5_asset_versions')) {
state.versions.push({
id: params[0],
asset_id: params[1],
storage_key: params[2],
scan_status: params[7],
});
return [[]];
}
if (sql.includes('used_bytes = used_bytes +')) {
state.spaces[0].used_bytes += params[0];
return [[]];
}
if (sql.includes('COALESCE(MAX(version_no)')) {
return [[{ max_version: 0 }]];
}
return pool.query(sql, params);
},
};
},
};
const sync = createWorkspaceAssetSync({
pool,
storageRoot,
h5Root,
maxFileBytes: 1024 * 1024,
idFactory: () => `id-${++nextId}`,
});
const result = await sync.syncUserWorkspace('user-1', { categoryCode: 'oa' });
assert.equal(result.imported, 1);
assert.equal(state.assets.length, 1);
assert.equal(state.assets[0].original_filename, 'memo.txt');
assert.ok(state.versions.length === 1);
const stored = path.join(storageRoot, state.versions[0].storage_key);
assert.equal(await fs.readFile(stored, 'utf8'), 'hello workspace\n');
});