|
|
|
@@ -0,0 +1,158 @@
|
|
|
|
|
import assert from 'node:assert/strict';
|
|
|
|
|
import fs from 'node:fs';
|
|
|
|
|
import fsp from 'node:fs/promises';
|
|
|
|
|
import os from 'node:os';
|
|
|
|
|
import path from 'node:path';
|
|
|
|
|
import test from 'node:test';
|
|
|
|
|
import { createMindSpaceOaDriveService } from './drive-service.mjs';
|
|
|
|
|
|
|
|
|
|
function createMockRes() {
|
|
|
|
|
return {
|
|
|
|
|
statusCode: 200,
|
|
|
|
|
headers: {},
|
|
|
|
|
set(fields) {
|
|
|
|
|
Object.assign(this.headers, fields);
|
|
|
|
|
},
|
|
|
|
|
status(code) {
|
|
|
|
|
this.statusCode = code;
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
json(payload) {
|
|
|
|
|
this.payload = payload;
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
send(body) {
|
|
|
|
|
this.body = body;
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
write() {},
|
|
|
|
|
end() {},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createJsonReq(userId, method, subPath, body) {
|
|
|
|
|
const payload = JSON.stringify(body ?? {});
|
|
|
|
|
return {
|
|
|
|
|
method,
|
|
|
|
|
path: `/mindspace/v1/oa/drive${subPath}`,
|
|
|
|
|
url: `/mindspace/v1/oa/drive${subPath}`,
|
|
|
|
|
headers: { 'content-type': 'application/json' },
|
|
|
|
|
currentUser: { id: userId },
|
|
|
|
|
body: body ?? {},
|
|
|
|
|
on() {},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function callJson(service, userId, method, subPath, body) {
|
|
|
|
|
const req = createJsonReq(userId, method, subPath, body);
|
|
|
|
|
const res = createMockRes();
|
|
|
|
|
await service.handlers.handle(req, res);
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function callMultipart(service, userId, filename, content, prefix = '') {
|
|
|
|
|
const boundary = '----oa-drive-test';
|
|
|
|
|
const chunks = [
|
|
|
|
|
`--${boundary}\r\n`,
|
|
|
|
|
`Content-Disposition: form-data; name="prefix"\r\n\r\n`,
|
|
|
|
|
`${prefix}\r\n`,
|
|
|
|
|
`--${boundary}\r\n`,
|
|
|
|
|
`Content-Disposition: form-data; name="file"; filename="${filename}"\r\n`,
|
|
|
|
|
'Content-Type: application/octet-stream\r\n\r\n',
|
|
|
|
|
content,
|
|
|
|
|
`\r\n--${boundary}--\r\n`,
|
|
|
|
|
];
|
|
|
|
|
const buffer = Buffer.concat(chunks.map((part) => Buffer.from(part)));
|
|
|
|
|
const req = {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
path: '/mindspace/v1/oa/drive/upload',
|
|
|
|
|
url: '/mindspace/v1/oa/drive/upload?onConflict=rename',
|
|
|
|
|
headers: { 'content-type': `multipart/form-data; boundary=${boundary}` },
|
|
|
|
|
currentUser: { id: userId },
|
|
|
|
|
body: buffer,
|
|
|
|
|
on() {},
|
|
|
|
|
};
|
|
|
|
|
const res = createMockRes();
|
|
|
|
|
await service.handlers.handle(req, res);
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('oa drive API parity with file-drop-sync core flows', async () => {
|
|
|
|
|
const tmpRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'memind-oa-drive-api-'));
|
|
|
|
|
const userId = 'user-oa-drive-api';
|
|
|
|
|
const oaRoot = path.join(tmpRoot, 'MindSpace', userId, 'oa');
|
|
|
|
|
await fsp.mkdir(oaRoot, { recursive: true });
|
|
|
|
|
let runtime = null;
|
|
|
|
|
|
|
|
|
|
const mindSpace = {
|
|
|
|
|
async getQuota() {
|
|
|
|
|
return {
|
|
|
|
|
quotaBytes: 1024 * 1024 * 1024,
|
|
|
|
|
usedBytes: 0,
|
|
|
|
|
reservedBytes: 0,
|
|
|
|
|
availableBytes: 1024 * 1024 * 1024,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const syncCalls = [];
|
|
|
|
|
const service = createMindSpaceOaDriveService({
|
|
|
|
|
h5Root: tmpRoot,
|
|
|
|
|
getMindSpace: () => mindSpace,
|
|
|
|
|
getMindSpaceAssets: () => ({
|
|
|
|
|
async syncWorkspaceAssets(uid, options) {
|
|
|
|
|
syncCalls.push({ uid, options });
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
runtime = service.getRuntime(userId);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let res = await callMultipart(service, userId, 'hello.txt', 'hello');
|
|
|
|
|
assert.equal(res.statusCode, 200);
|
|
|
|
|
assert.deepEqual(res.payload.saved, ['hello.txt']);
|
|
|
|
|
|
|
|
|
|
res = await callJson(service, userId, 'GET', '/files');
|
|
|
|
|
assert.ok(res.payload.files.some((file) => file.name === 'hello.txt'));
|
|
|
|
|
|
|
|
|
|
res = await callJson(service, userId, 'POST', '/folders', { name: 'box', parent: '' });
|
|
|
|
|
assert.equal(res.statusCode, 200);
|
|
|
|
|
assert.equal(res.payload.path, 'box');
|
|
|
|
|
|
|
|
|
|
res = await callJson(service, userId, 'POST', '/move', {
|
|
|
|
|
items: ['hello.txt'],
|
|
|
|
|
dest: 'box',
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.statusCode, 200);
|
|
|
|
|
assert.equal(res.payload.moved[0].to, 'box/hello.txt');
|
|
|
|
|
|
|
|
|
|
res = await callJson(service, userId, 'POST', '/rename', {
|
|
|
|
|
from: 'box/hello.txt',
|
|
|
|
|
name: 'world.txt',
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.statusCode, 200);
|
|
|
|
|
|
|
|
|
|
res = await callJson(service, userId, 'POST', '/copy', {
|
|
|
|
|
items: ['box/world.txt'],
|
|
|
|
|
dest: '',
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.statusCode, 200);
|
|
|
|
|
assert.ok(res.payload.copied.some((item) => item.to === 'world.txt'));
|
|
|
|
|
|
|
|
|
|
res = await callJson(service, userId, 'POST', '/trash', { items: ['box/world.txt'] });
|
|
|
|
|
assert.equal(res.statusCode, 200);
|
|
|
|
|
assert.equal(res.payload.trashed.length, 1);
|
|
|
|
|
|
|
|
|
|
res = await callJson(service, userId, 'GET', '/trash');
|
|
|
|
|
assert.ok(res.payload.items.some((item) => item.original === 'box/world.txt'));
|
|
|
|
|
|
|
|
|
|
res = await callJson(service, userId, 'GET', '/info');
|
|
|
|
|
assert.equal(res.statusCode, 200);
|
|
|
|
|
assert.equal(res.payload.pageSize, 100);
|
|
|
|
|
assert.ok(res.payload.quota);
|
|
|
|
|
|
|
|
|
|
assert.ok(syncCalls.length >= 5);
|
|
|
|
|
} finally {
|
|
|
|
|
runtime?.dispose();
|
|
|
|
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
|
|
|
}
|
|
|
|
|
});
|