Add MindSpace decoupling foundation
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
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 {
|
||||
createLocalMindSpaceStorageAdapter,
|
||||
normalizeMindSpaceStorageKey,
|
||||
resolveStoragePath,
|
||||
} from './mindspace-storage-adapter.mjs';
|
||||
|
||||
test('normalizeMindSpaceStorageKey rejects absolute paths and traversal', () => {
|
||||
assert.equal(normalizeMindSpaceStorageKey('users/u1/file.txt'), 'users/u1/file.txt');
|
||||
assert.equal(normalizeMindSpaceStorageKey('users/u1/./file.txt'), 'users/u1/file.txt');
|
||||
|
||||
for (const key of ['/tmp/file.txt', '../file.txt', 'users/../file.txt', 'C:/tmp/file.txt', 'a\\b']) {
|
||||
assert.throws(
|
||||
() => normalizeMindSpaceStorageKey(key),
|
||||
(error) => error.code === 'invalid_storage_key',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('resolveStoragePath keeps object keys inside storage root', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-storage-root-'));
|
||||
const target = resolveStoragePath(root, 'users/u1/file.txt');
|
||||
|
||||
assert.equal(target, path.join(root, 'users', 'u1', 'file.txt'));
|
||||
assert.throws(
|
||||
() => resolveStoragePath(root, '../outside.txt'),
|
||||
(error) => error.code === 'invalid_storage_key',
|
||||
);
|
||||
});
|
||||
|
||||
test('local storage adapter writes, reads, stats, lists, copies, and deletes objects', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-storage-adapter-'));
|
||||
const adapter = createLocalMindSpaceStorageAdapter(root);
|
||||
|
||||
const writeResult = await adapter.putObject('users/u1/conversations/s1/public/report.html', '<h1>Hi</h1>');
|
||||
assert.equal(writeResult.key, 'users/u1/conversations/s1/public/report.html');
|
||||
assert.equal(writeResult.sizeBytes, Buffer.byteLength('<h1>Hi</h1>'));
|
||||
|
||||
const body = await adapter.getObject('users/u1/conversations/s1/public/report.html');
|
||||
assert.equal(body.toString('utf8'), '<h1>Hi</h1>');
|
||||
|
||||
const stat = await adapter.statObject('users/u1/conversations/s1/public/report.html');
|
||||
assert.equal(stat.exists, true);
|
||||
assert.equal(stat.isDirectory, false);
|
||||
|
||||
await adapter.copyObject(
|
||||
'users/u1/conversations/s1/public/report.html',
|
||||
'users/u1/conversations/s1/generated/report-copy.html',
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
(await adapter.listObjects('users/u1/conversations/s1')).map((item) => item.key),
|
||||
[
|
||||
'users/u1/conversations/s1/generated/report-copy.html',
|
||||
'users/u1/conversations/s1/public/report.html',
|
||||
],
|
||||
);
|
||||
|
||||
await adapter.deleteObject('users/u1/conversations/s1/public/report.html');
|
||||
assert.equal((await adapter.statObject('users/u1/conversations/s1/public/report.html')).exists, false);
|
||||
});
|
||||
|
||||
test('local storage adapter rejects signed URLs explicitly', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-storage-adapter-'));
|
||||
const adapter = createLocalMindSpaceStorageAdapter(root);
|
||||
|
||||
await assert.rejects(
|
||||
() => adapter.getSignedUrl('users/u1/file.txt'),
|
||||
(error) => error.code === 'signed_url_not_supported',
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user