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 }); } });