mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Server: Moved file API tests to route
This commit is contained in:
parent
ecb6134828
commit
66a09e5068
@ -1,535 +0,0 @@
|
|||||||
import { testAssetDir, createUserAndSession, createUser, checkThrowAsync, beforeAllDb, afterAllDb, beforeEachDb, models, controllers } from '../../utils/testing/testUtils';
|
|
||||||
import * as fs from 'fs-extra';
|
|
||||||
import { ChangeType, File } from '../../db';
|
|
||||||
import { ErrorConflict, ErrorForbidden, ErrorNotFound, ErrorUnprocessableEntity } from '../../utils/errors';
|
|
||||||
import { filePathInfo } from '../../utils/routeUtils';
|
|
||||||
import { defaultPagination, Pagination, PaginationOrderDir } from '../../models/utils/pagination';
|
|
||||||
import { msleep } from '../../utils/time';
|
|
||||||
|
|
||||||
async function makeTestFile(id: number = 1, ext: string = 'jpg', parentId: string = ''): Promise<File> {
|
|
||||||
const basename = ext === 'jpg' ? 'photo' : 'poster';
|
|
||||||
|
|
||||||
const file: File = {
|
|
||||||
name: id > 1 ? `${basename}-${id}.${ext}` : `${basename}.${ext}`,
|
|
||||||
content: await fs.readFile(`${testAssetDir}/${basename}.${ext}`),
|
|
||||||
// mime_type: `image/${ext}`,
|
|
||||||
parent_id: parentId,
|
|
||||||
};
|
|
||||||
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeTestContent(ext: string = 'jpg') {
|
|
||||||
const basename = ext === 'jpg' ? 'photo' : 'poster';
|
|
||||||
return await fs.readFile(`${testAssetDir}/${basename}.${ext}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeTestDirectory(name: string = 'Docs'): Promise<File> {
|
|
||||||
const file: File = {
|
|
||||||
name: name,
|
|
||||||
parent_id: '',
|
|
||||||
is_directory: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveTestFile(sessionId: string, path: string): Promise<File> {
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
|
|
||||||
return fileController.putFileContent(
|
|
||||||
sessionId,
|
|
||||||
path,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveTestDir(sessionId: string, path: string): Promise<File> {
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
|
|
||||||
const parsed = filePathInfo(path);
|
|
||||||
|
|
||||||
return fileController.postChild(
|
|
||||||
sessionId,
|
|
||||||
parsed.dirname,
|
|
||||||
{
|
|
||||||
name: parsed.basename,
|
|
||||||
is_directory: 1,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('FileController', function() {
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await beforeAllDb('FileController');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await afterAllDb();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await beforeEachDb();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should create a file', async function() {
|
|
||||||
const { user, session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
const fileContent = await makeTestContent();
|
|
||||||
|
|
||||||
const newFile = await fileController.putFileContent(
|
|
||||||
session.id,
|
|
||||||
'root:/photo.jpg:',
|
|
||||||
fileContent
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(!!newFile.id).toBe(true);
|
|
||||||
expect(newFile.name).toBe('photo.jpg');
|
|
||||||
expect(newFile.mime_type).toBe('image/jpeg');
|
|
||||||
expect(!!newFile.parent_id).toBe(true);
|
|
||||||
expect(!newFile.content).toBe(true);
|
|
||||||
expect(newFile.size > 0).toBe(true);
|
|
||||||
|
|
||||||
const fileModel = models().file({ userId: user.id });
|
|
||||||
const newFileReload = await fileModel.loadWithContent(newFile.id);
|
|
||||||
|
|
||||||
expect(!!newFileReload).toBe(true);
|
|
||||||
|
|
||||||
const newFileHex = fileContent.toString('hex');
|
|
||||||
const newFileReloadHex = (newFileReload.content as Buffer).toString('hex');
|
|
||||||
expect(newFileReloadHex.length > 0).toBe(true);
|
|
||||||
expect(newFileReloadHex).toBe(newFileHex);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should create sub-directories', async function() {
|
|
||||||
const { session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
|
|
||||||
const newDir = await fileController.postFile_(session.id, {
|
|
||||||
is_directory: 1,
|
|
||||||
name: 'subdir',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(!!newDir.id).toBe(true);
|
|
||||||
expect(newDir.is_directory).toBe(1);
|
|
||||||
|
|
||||||
const newDir2 = await fileController.postFile_(session.id, {
|
|
||||||
is_directory: 1,
|
|
||||||
name: 'subdir2',
|
|
||||||
parent_id: newDir.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const newDirReload2 = await fileController.getFile(session.id, 'root:/subdir/subdir2');
|
|
||||||
expect(newDirReload2.id).toBe(newDir2.id);
|
|
||||||
expect(newDirReload2.name).toBe(newDir2.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should create files in sub-directory', async function() {
|
|
||||||
const { session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
|
|
||||||
await fileController.postFile_(session.id, {
|
|
||||||
is_directory: 1,
|
|
||||||
name: 'subdir',
|
|
||||||
});
|
|
||||||
|
|
||||||
const newFile = await fileController.putFileContent(
|
|
||||||
session.id,
|
|
||||||
'root:/subdir/photo.jpg:',
|
|
||||||
await makeTestContent()
|
|
||||||
);
|
|
||||||
|
|
||||||
const newFileReload = await fileController.getFile(session.id, 'root:/subdir/photo.jpg');
|
|
||||||
expect(newFileReload.id).toBe(newFile.id);
|
|
||||||
expect(newFileReload.name).toBe('photo.jpg');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not create a file with an invalid path', async function() {
|
|
||||||
const { session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
const fileContent = await makeTestContent();
|
|
||||||
|
|
||||||
const error = await checkThrowAsync(async () => fileController.putFileContent(
|
|
||||||
session.id,
|
|
||||||
'root:/does/not/exist/photo.jpg:',
|
|
||||||
fileContent
|
|
||||||
));
|
|
||||||
|
|
||||||
expect(error instanceof ErrorNotFound).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should get files', async function() {
|
|
||||||
const { session: session1, user: user1 } = await createUserAndSession(1);
|
|
||||||
const { session: session2 } = await createUserAndSession(2);
|
|
||||||
|
|
||||||
let file1: File = await makeTestFile(1);
|
|
||||||
let file2: File = await makeTestFile(2);
|
|
||||||
let file3: File = await makeTestFile(3);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
file1 = await fileController.postFile_(session1.id, file1);
|
|
||||||
file2 = await fileController.postFile_(session1.id, file2);
|
|
||||||
file3 = await fileController.postFile_(session2.id, file3);
|
|
||||||
|
|
||||||
const fileId1 = file1.id;
|
|
||||||
const fileId2 = file2.id;
|
|
||||||
|
|
||||||
// Can't get someone else file
|
|
||||||
const error = await checkThrowAsync(async () => fileController.getFile(session1.id, file3.id));
|
|
||||||
expect(error instanceof ErrorForbidden).toBe(true);
|
|
||||||
|
|
||||||
file1 = await fileController.getFile(session1.id, file1.id);
|
|
||||||
expect(file1.id).toBe(fileId1);
|
|
||||||
|
|
||||||
const fileModel = models().file({ userId: user1.id });
|
|
||||||
const paginatedResults = await fileController.getChildren(session1.id, await fileModel.userRootFileId(), defaultPagination());
|
|
||||||
const allFiles = paginatedResults.items;
|
|
||||||
expect(allFiles.length).toBe(2);
|
|
||||||
expect(JSON.stringify(allFiles.map(f => f.id).sort())).toBe(JSON.stringify([fileId1, fileId2].sort()));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not let create a file in a directory not owned by user', async function() {
|
|
||||||
const { session } = await createUserAndSession(1);
|
|
||||||
|
|
||||||
const user2 = await createUser(2);
|
|
||||||
const fileModel2 = models().file({ userId: user2.id });
|
|
||||||
const rootFile2 = await fileModel2.userRootFile();
|
|
||||||
|
|
||||||
const file: File = await makeTestFile();
|
|
||||||
file.parent_id = rootFile2.id;
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
|
|
||||||
const hasThrown = await checkThrowAsync(async () => fileController.postFile_(session.id, file));
|
|
||||||
expect(!!hasThrown).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should update file properties', async function() {
|
|
||||||
const { session, user } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const fileModel = models().file({ userId: user.id });
|
|
||||||
|
|
||||||
let file: File = await makeTestFile();
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
file = await fileController.postFile_(session.id, file);
|
|
||||||
|
|
||||||
// Can't have file with empty name
|
|
||||||
const error = await checkThrowAsync(async () => fileController.patchFile(session.id, file.id, { name: '' }));
|
|
||||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
|
||||||
|
|
||||||
await fileController.patchFile(session.id, file.id, { name: 'modified.jpg' });
|
|
||||||
file = await fileModel.load(file.id);
|
|
||||||
expect(file.name).toBe('modified.jpg');
|
|
||||||
|
|
||||||
await fileController.patchFile(session.id, file.id, { mime_type: 'image/png' });
|
|
||||||
file = await fileModel.load(file.id);
|
|
||||||
expect(file.mime_type).toBe('image/png');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not allow duplicate filenames', async function() {
|
|
||||||
const { session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
let file1: File = await makeTestFile(1);
|
|
||||||
const file2: File = await makeTestFile(1);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
file1 = await fileController.postFile_(session.id, file1);
|
|
||||||
|
|
||||||
expect(!!file1.id).toBe(true);
|
|
||||||
expect(file1.name).toBe(file2.name);
|
|
||||||
|
|
||||||
const hasThrown = await checkThrowAsync(async () => await fileController.postFile_(session.id, file2));
|
|
||||||
expect(!!hasThrown).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should change the file parent', async function() {
|
|
||||||
const { session: session1, user: user1 } = await createUserAndSession(1);
|
|
||||||
const { user: user2 } = await createUserAndSession(2);
|
|
||||||
let hasThrown: any = null;
|
|
||||||
|
|
||||||
const fileModel = models().file({ userId: user1.id });
|
|
||||||
|
|
||||||
let file: File = await makeTestFile();
|
|
||||||
let file2: File = await makeTestFile(2);
|
|
||||||
let dir: File = await makeTestDirectory();
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
file = await fileController.postFile_(session1.id, file);
|
|
||||||
file2 = await fileController.postFile_(session1.id, file2);
|
|
||||||
dir = await fileController.postFile_(session1.id, dir);
|
|
||||||
|
|
||||||
// Can't set parent to another non-directory file
|
|
||||||
hasThrown = await checkThrowAsync(async () => await fileController.patchFile(session1.id, file.id, { parent_id: file2.id }));
|
|
||||||
expect(!!hasThrown).toBe(true);
|
|
||||||
|
|
||||||
const fileModel2 = models().file({ userId: user2.id });
|
|
||||||
const userRoot2 = await fileModel2.userRootFile();
|
|
||||||
|
|
||||||
// Can't set parent to someone else directory
|
|
||||||
hasThrown = await checkThrowAsync(async () => await fileController.patchFile(session1.id, file.id, { parent_id: userRoot2.id }));
|
|
||||||
expect(!!hasThrown).toBe(true);
|
|
||||||
|
|
||||||
await fileController.patchFile(session1.id, file.id, { parent_id: dir.id });
|
|
||||||
|
|
||||||
file = await fileModel.load(file.id);
|
|
||||||
|
|
||||||
expect(!!file.parent_id).toBe(true);
|
|
||||||
expect(file.parent_id).toBe(dir.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should delete a file', async function() {
|
|
||||||
const { user, session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
const fileModel = models().file({ userId: user.id });
|
|
||||||
|
|
||||||
const file1: File = await makeTestFile(1);
|
|
||||||
let file2: File = await makeTestFile(2);
|
|
||||||
|
|
||||||
await fileController.postFile_(session.id, file1);
|
|
||||||
file2 = await fileController.postFile_(session.id, file2);
|
|
||||||
let allFiles: File[] = await fileModel.all();
|
|
||||||
const beforeCount: number = allFiles.length;
|
|
||||||
|
|
||||||
await fileController.deleteFile(session.id, file2.id);
|
|
||||||
allFiles = await fileModel.all();
|
|
||||||
expect(allFiles.length).toBe(beforeCount - 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should create and delete directories', async function() {
|
|
||||||
const { user, session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
|
|
||||||
const dir1: File = await fileController.postChild(session.id, 'root', { name: 'dir1', is_directory: 1 });
|
|
||||||
const dir2: File = await fileController.postChild(session.id, 'root:/dir1', { name: 'dir2', is_directory: 1 });
|
|
||||||
|
|
||||||
const dirReload2: File = await fileController.getFile(session.id, 'root:/dir1/dir2');
|
|
||||||
expect(dirReload2.id).toBe(dir2.id);
|
|
||||||
|
|
||||||
// Delete one directory
|
|
||||||
await fileController.deleteFile(session.id, 'root:/dir1/dir2');
|
|
||||||
const error = await checkThrowAsync(async () => fileController.getFile(session.id, 'root:/dir1/dir2'));
|
|
||||||
expect(error instanceof ErrorNotFound).toBe(true);
|
|
||||||
|
|
||||||
// Delete a directory and its sub-directories and files
|
|
||||||
const dir3: File = await fileController.postChild(session.id, 'root:/dir1', { name: 'dir3', is_directory: 1 });
|
|
||||||
const file1: File = await fileController.postFile_(session.id, { name: 'file1', parent_id: dir1.id });
|
|
||||||
const file2: File = await fileController.postFile_(session.id, { name: 'file2', parent_id: dir3.id });
|
|
||||||
await fileController.deleteFile(session.id, 'root:/dir1');
|
|
||||||
const fileModel = models().file({ userId: user.id });
|
|
||||||
expect(!(await fileModel.load(dir1.id))).toBe(true);
|
|
||||||
expect(!(await fileModel.load(dir3.id))).toBe(true);
|
|
||||||
expect(!(await fileModel.load(file1.id))).toBe(true);
|
|
||||||
expect(!(await fileModel.load(file2.id))).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not change the parent when updating a file', async function() {
|
|
||||||
const { user, session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
const fileModel = models().file({ userId: user.id });
|
|
||||||
|
|
||||||
const dir1: File = await fileController.postChild(session.id, 'root', { name: 'dir1', is_directory: 1 });
|
|
||||||
const file1: File = await fileController.putFileContent(session.id, 'root:/dir1/myfile.md', Buffer.from('testing'));
|
|
||||||
|
|
||||||
await fileController.putFileContent(session.id, 'root:/dir1/myfile.md', Buffer.from('new content'));
|
|
||||||
const fileReloaded1 = await fileModel.load(file1.id);
|
|
||||||
|
|
||||||
expect(fileReloaded1.parent_id).toBe(dir1.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not delete someone else file', async function() {
|
|
||||||
const { session: session1 } = await createUserAndSession(1);
|
|
||||||
const { session: session2 } = await createUserAndSession(2);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
|
|
||||||
const file1: File = await makeTestFile(1);
|
|
||||||
let file2: File = await makeTestFile(2);
|
|
||||||
|
|
||||||
await fileController.postFile_(session1.id, file1);
|
|
||||||
file2 = await fileController.postFile_(session2.id, file2);
|
|
||||||
|
|
||||||
const error = await checkThrowAsync(async () => await fileController.deleteFile(session1.id, file2.id));
|
|
||||||
expect(error instanceof ErrorForbidden).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should let admin change or delete files', async function() {
|
|
||||||
const { session: adminSession } = await createUserAndSession(1, true);
|
|
||||||
const { session, user } = await createUserAndSession(2);
|
|
||||||
|
|
||||||
let file: File = await makeTestFile();
|
|
||||||
|
|
||||||
const fileModel = models().file({ userId: user.id });
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
file = await fileController.postFile_(session.id, file);
|
|
||||||
|
|
||||||
await fileController.patchFile(adminSession.id, file.id, { name: 'modified.jpg' });
|
|
||||||
file = await fileModel.load(file.id);
|
|
||||||
expect(file.name).toBe('modified.jpg');
|
|
||||||
|
|
||||||
await fileController.deleteFile(adminSession.id, file.id);
|
|
||||||
expect(!(await fileModel.load(file.id))).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should update a file content', async function() {
|
|
||||||
const { session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const file: File = await makeTestFile(1);
|
|
||||||
const file2: File = await makeTestFile(2, 'png');
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
const newFile = await fileController.postFile_(session.id, file);
|
|
||||||
await fileController.putFileContent(session.id, newFile.id, file2.content);
|
|
||||||
|
|
||||||
const modFile = await fileController.getFileContent(session.id, newFile.id);
|
|
||||||
|
|
||||||
const originalFileHex = (file.content as Buffer).toString('hex');
|
|
||||||
const modFileHex = (modFile.content as Buffer).toString('hex');
|
|
||||||
expect(modFileHex.length > 0).toBe(true);
|
|
||||||
expect(modFileHex === originalFileHex).toBe(false);
|
|
||||||
expect(modFile.size).toBe(modFile.content.byteLength);
|
|
||||||
expect(newFile.size).toBe(file.content.byteLength);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should delete a file content', async function() {
|
|
||||||
const { session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const file: File = await makeTestFile(1);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
const newFile = await fileController.postFile_(session.id, file);
|
|
||||||
await fileController.putFileContent(session.id, newFile.id, file.content);
|
|
||||||
|
|
||||||
await fileController.deleteFileContent(session.id, newFile.id);
|
|
||||||
|
|
||||||
const modFile = await fileController.getFile(session.id, newFile.id);
|
|
||||||
expect(modFile.size).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not allow reserved characters', async function() {
|
|
||||||
const { session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
const filenames = [
|
|
||||||
'invalid*invalid',
|
|
||||||
'invalid#invalid',
|
|
||||||
'invalid\\invalid',
|
|
||||||
];
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
|
|
||||||
for (const filename of filenames) {
|
|
||||||
const error = await checkThrowAsync(async () => fileController.putFileContent(session.id, `root:/${filename}`, null));
|
|
||||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not allow a directory with the same name', async function() {
|
|
||||||
const { session } = await createUserAndSession(1, true);
|
|
||||||
|
|
||||||
await saveTestDir(session.id, 'root:/somedir:');
|
|
||||||
let error = await checkThrowAsync(async () => saveTestFile(session.id, 'root:/somedir:'));
|
|
||||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
|
||||||
|
|
||||||
await saveTestFile(session.id, 'root:/somefile.md:');
|
|
||||||
error = await checkThrowAsync(async () => saveTestDir(session.id, 'root:/somefile.md:'));
|
|
||||||
expect(error instanceof ErrorConflict).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not be possible to delete the root directory', async function() {
|
|
||||||
const { session } = await createUserAndSession(1, true);
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
|
|
||||||
const error = await checkThrowAsync(async () => fileController.deleteFile(session.id, 'root'));
|
|
||||||
expect(error instanceof ErrorForbidden).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should support root:/: format, which means root', async function() {
|
|
||||||
const { session, user } = await createUserAndSession(1, true);
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
const fileModel = models().file({ userId: user.id });
|
|
||||||
|
|
||||||
const root = await fileController.getFile(session.id, 'root:/:');
|
|
||||||
expect(root.id).toBe(await fileModel.userRootFileId());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should paginate results', async function() {
|
|
||||||
const { session: session1, user: user1 } = await createUserAndSession(1);
|
|
||||||
|
|
||||||
let file1: File = await makeTestFile(1);
|
|
||||||
let file2: File = await makeTestFile(2);
|
|
||||||
let file3: File = await makeTestFile(3);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
file1 = await fileController.postFile_(session1.id, file1);
|
|
||||||
await msleep(1);
|
|
||||||
file2 = await fileController.postFile_(session1.id, file2);
|
|
||||||
await msleep(1);
|
|
||||||
file3 = await fileController.postFile_(session1.id, file3);
|
|
||||||
|
|
||||||
const fileModel = models().file({ userId: user1.id });
|
|
||||||
const rootId = await fileModel.userRootFileId();
|
|
||||||
|
|
||||||
const pagination: Pagination = {
|
|
||||||
limit: 2,
|
|
||||||
order: [
|
|
||||||
{
|
|
||||||
by: 'updated_time',
|
|
||||||
dir: PaginationOrderDir.ASC,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
page: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const method of ['page', 'cursor']) {
|
|
||||||
const page1 = await fileController.getChildren(session1.id, rootId, pagination);
|
|
||||||
expect(page1.items.length).toBe(2);
|
|
||||||
expect(page1.has_more).toBe(true);
|
|
||||||
expect(page1.items[0].id).toBe(file1.id);
|
|
||||||
expect(page1.items[1].id).toBe(file2.id);
|
|
||||||
|
|
||||||
const p = method === 'page' ? { ...pagination, page: 2 } : { cursor: page1.cursor };
|
|
||||||
const page2 = await fileController.getChildren(session1.id, rootId, p);
|
|
||||||
expect(page2.items.length).toBe(1);
|
|
||||||
expect(page2.has_more).toBe(false);
|
|
||||||
expect(page2.items[0].id).toBe(file3.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should track file changes', async function() {
|
|
||||||
// We only do a basic check because most of the tests for this are in
|
|
||||||
// ChangeModel.test.ts
|
|
||||||
|
|
||||||
const { session: session1 } = await createUserAndSession(1);
|
|
||||||
|
|
||||||
let file1: File = await makeTestFile(1);
|
|
||||||
let file2: File = await makeTestFile(2);
|
|
||||||
|
|
||||||
const fileController = controllers().apiFile();
|
|
||||||
file1 = await fileController.postFile_(session1.id, file1);
|
|
||||||
await msleep(1); file2 = await fileController.postFile_(session1.id, file2);
|
|
||||||
|
|
||||||
const page1 = await fileController.getDelta(session1.id, file1.parent_id, { limit: 1 });
|
|
||||||
expect(page1.has_more).toBe(true);
|
|
||||||
expect(page1.items.length).toBe(1);
|
|
||||||
expect(page1.items[0].type).toBe(ChangeType.Create);
|
|
||||||
expect(page1.items[0].item.id).toBe(file1.id);
|
|
||||||
|
|
||||||
const page2 = await fileController.getDelta(session1.id, file1.parent_id, { cursor: page1.cursor, limit: 1 });
|
|
||||||
expect(page2.has_more).toBe(true);
|
|
||||||
expect(page2.items.length).toBe(1);
|
|
||||||
expect(page2.items[0].type).toBe(ChangeType.Create);
|
|
||||||
expect(page2.items[0].item.id).toBe(file2.id);
|
|
||||||
|
|
||||||
const page3 = await fileController.getDelta(session1.id, file1.parent_id, { cursor: page2.cursor, limit: 1 });
|
|
||||||
expect(page3.has_more).toBe(false);
|
|
||||||
expect(page3.items.length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,4 +1,4 @@
|
|||||||
import { createUser, checkThrowAsync, beforeAllDb, afterAllDb, beforeEachDb, controllers } from '../../utils/testing/testUtils';
|
import { createUser, checkThrowAsync, beforeAllDb, afterAllTests, beforeEachDb, controllers } from '../../utils/testing/testUtils';
|
||||||
import { ErrorForbidden } from '../../utils/errors';
|
import { ErrorForbidden } from '../../utils/errors';
|
||||||
|
|
||||||
describe('SessionController', function() {
|
describe('SessionController', function() {
|
||||||
@ -8,7 +8,7 @@ describe('SessionController', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await afterAllDb();
|
await afterAllTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { models, controllers, createUserAndSession, checkThrowAsync, beforeAllDb, afterAllDb, beforeEachDb } from '../../utils/testing/testUtils';
|
import { models, controllers, createUserAndSession, checkThrowAsync, beforeAllDb, afterAllTests, beforeEachDb } from '../../utils/testing/testUtils';
|
||||||
import { File, User } from '../../db';
|
import { File, User } from '../../db';
|
||||||
import { ErrorForbidden, ErrorUnprocessableEntity } from '../../utils/errors';
|
import { ErrorForbidden, ErrorUnprocessableEntity } from '../../utils/errors';
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ describe('UserController', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await afterAllDb();
|
await afterAllTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createUserAndSession, beforeAllDb, afterAllDb, beforeEachDb, models, koaAppContext, koaNext } from '../utils/testing/testUtils';
|
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models, koaAppContext, koaNext } from '../utils/testing/testUtils';
|
||||||
import { defaultAdminEmail, defaultAdminPassword, Notification } from '../db';
|
import { defaultAdminEmail, defaultAdminPassword, Notification } from '../db';
|
||||||
import notificationHandler from './notificationHandler';
|
import notificationHandler from './notificationHandler';
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ describe('notificationHandler', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await afterAllDb();
|
await afterAllTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createUserAndSession, beforeAllDb, afterAllDb, beforeEachDb, koaAppContext, koaNext } from '../utils/testing/testUtils';
|
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, koaAppContext, koaNext } from '../utils/testing/testUtils';
|
||||||
import ownerHandler from './ownerHandler';
|
import ownerHandler from './ownerHandler';
|
||||||
|
|
||||||
describe('ownerHandler', function() {
|
describe('ownerHandler', function() {
|
||||||
@ -8,7 +8,7 @@ describe('ownerHandler', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await afterAllDb();
|
await afterAllTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createUserAndSession, beforeAllDb, afterAllDb, beforeEachDb, models, expectThrow } from '../utils/testing/testUtils';
|
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models, expectThrow } from '../utils/testing/testUtils';
|
||||||
import { ChangeType, File } from '../db';
|
import { ChangeType, File } from '../db';
|
||||||
import FileModel from './FileModel';
|
import FileModel from './FileModel';
|
||||||
import { msleep } from '../utils/time';
|
import { msleep } from '../utils/time';
|
||||||
@ -18,7 +18,7 @@ describe('ChangeModel', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await afterAllDb();
|
await afterAllTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createUserAndSession, beforeAllDb, afterAllDb, beforeEachDb, models, createFileTree } from '../utils/testing/testUtils';
|
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models, createFileTree } from '../utils/testing/testUtils';
|
||||||
import { File } from '../db';
|
import { File } from '../db';
|
||||||
|
|
||||||
describe('FileModel', function() {
|
describe('FileModel', function() {
|
||||||
@ -8,7 +8,7 @@ describe('FileModel', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await afterAllDb();
|
await afterAllTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createUserAndSession, beforeAllDb, afterAllDb, beforeEachDb, models, expectThrow } from '../utils/testing/testUtils';
|
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models, expectThrow } from '../utils/testing/testUtils';
|
||||||
import { Notification, NotificationLevel } from '../db';
|
import { Notification, NotificationLevel } from '../db';
|
||||||
|
|
||||||
describe('NotificationModel', function() {
|
describe('NotificationModel', function() {
|
||||||
@ -8,7 +8,7 @@ describe('NotificationModel', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await afterAllDb();
|
await afterAllTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -20,6 +20,15 @@ export interface Pagination {
|
|||||||
cursor?: string;
|
cursor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface PaginationQueryParams {
|
||||||
|
limit?: number;
|
||||||
|
order_by?: string;
|
||||||
|
order_dir?: string;
|
||||||
|
page?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PaginatedResults {
|
export interface PaginatedResults {
|
||||||
items: any[];
|
items: any[];
|
||||||
has_more: boolean;
|
has_more: boolean;
|
||||||
@ -107,6 +116,25 @@ export function requestChangePagination(query: any): ChangePagination {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function paginationToQueryParams(pagination: Pagination): PaginationQueryParams {
|
||||||
|
const output: PaginationQueryParams = {};
|
||||||
|
if (!pagination) return {};
|
||||||
|
|
||||||
|
if ('limit' in pagination) output.limit = pagination.limit;
|
||||||
|
if ('page' in pagination) output.page = pagination.page;
|
||||||
|
if ('cursor' in pagination) output.cursor = pagination.cursor;
|
||||||
|
|
||||||
|
if ('order' in pagination) {
|
||||||
|
const o = pagination.order;
|
||||||
|
if (o.length) {
|
||||||
|
output.order_by = o[0].by;
|
||||||
|
output.order_dir = o[0].dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PageLink {
|
export interface PageLink {
|
||||||
page?: number;
|
page?: number;
|
||||||
isEllipsis?: boolean;
|
isEllipsis?: boolean;
|
||||||
|
@ -1,12 +1,36 @@
|
|||||||
import routeHandler from '../../middleware/routeHandler';
|
import { testAssetDir, beforeAllDb, randomHash, afterAllTests, beforeEachDb, createUserAndSession, models, tempDir } from '../../utils/testing/testUtils';
|
||||||
import { testAssetDir, beforeAllDb, afterAllDb, beforeEachDb, koaAppContext, createUserAndSession, models } from '../../utils/testing/testUtils';
|
import { getFileMetadataContext, getFileMetadata, deleteFileContent, deleteFileContext, deleteFile, postDirectoryContext, postDirectory, getDirectoryChildren, putFileContentContext, putFileContent, getFileContent, patchFileContext, patchFile, getDelta } from '../../utils/testing/apiUtils';
|
||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
|
import { ChangeType, File } from '../../db';
|
||||||
|
import { Pagination, PaginationOrderDir } from '../../models/utils/pagination';
|
||||||
|
import { ErrorUnprocessableEntity, ErrorForbidden, ErrorNotFound, ErrorConflict } from '../../utils/errors';
|
||||||
|
import { msleep } from '../../utils/time';
|
||||||
|
|
||||||
function testFilePath(ext: string = 'jpg') {
|
function testFilePath(ext: string = 'jpg') {
|
||||||
const basename = ext === 'jpg' ? 'photo' : 'poster';
|
const basename = ext === 'jpg' ? 'photo' : 'poster';
|
||||||
return `${testAssetDir}/${basename}.${ext}`;
|
return `${testAssetDir}/${basename}.${ext}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function makeTempFileWithContent(content: string): Promise<string> {
|
||||||
|
const d = await tempDir();
|
||||||
|
const filePath = `${d}/${randomHash()}`;
|
||||||
|
await fs.writeFile(filePath, content, 'utf8');
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeTestFile(ownerId: string, id: number = 1, ext: string = 'jpg', parentId: string = ''): Promise<File> {
|
||||||
|
const basename = ext === 'jpg' ? 'photo' : 'poster';
|
||||||
|
|
||||||
|
const file: File = {
|
||||||
|
name: id > 1 ? `${basename}-${id}.${ext}` : `${basename}.${ext}`,
|
||||||
|
content: await fs.readFile(`${testAssetDir}/${basename}.${ext}`),
|
||||||
|
// mime_type: `image/${ext}`,
|
||||||
|
parent_id: parentId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return models().file({ userId: ownerId }).save(file);
|
||||||
|
}
|
||||||
|
|
||||||
describe('api_files', function() {
|
describe('api_files', function() {
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@ -14,7 +38,7 @@ describe('api_files', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await afterAllDb();
|
await afterAllTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -25,18 +49,7 @@ describe('api_files', function() {
|
|||||||
const { user, session } = await createUserAndSession(1, true);
|
const { user, session } = await createUserAndSession(1, true);
|
||||||
const filePath = testFilePath();
|
const filePath = testFilePath();
|
||||||
|
|
||||||
const context = await koaAppContext({
|
const newFile = await putFileContent(session.id, 'root:/photo.jpg:', filePath);
|
||||||
sessionId: session.id,
|
|
||||||
request: {
|
|
||||||
method: 'PUT',
|
|
||||||
url: '/api/files/root:/photo.jpg:/content',
|
|
||||||
files: { file: { path: filePath } },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await routeHandler(context);
|
|
||||||
|
|
||||||
const newFile = context.response.body;
|
|
||||||
|
|
||||||
expect(!!newFile.id).toBe(true);
|
expect(!!newFile.id).toBe(true);
|
||||||
expect(newFile.name).toBe('photo.jpg');
|
expect(newFile.name).toBe('photo.jpg');
|
||||||
@ -60,53 +73,347 @@ describe('api_files', function() {
|
|||||||
test('should create sub-directories', async function() {
|
test('should create sub-directories', async function() {
|
||||||
const { session } = await createUserAndSession(1, true);
|
const { session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
const context1 = await koaAppContext({
|
const newDir = await postDirectory(session.id, 'root:/:', 'subdir');
|
||||||
sessionId: session.id,
|
|
||||||
request: {
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/files/root/children',
|
|
||||||
body: {
|
|
||||||
is_directory: 1,
|
|
||||||
name: 'subdir',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await routeHandler(context1);
|
|
||||||
|
|
||||||
const newDir = context1.response.body;
|
|
||||||
expect(!!newDir.id).toBe(true);
|
expect(!!newDir.id).toBe(true);
|
||||||
expect(newDir.is_directory).toBe(1);
|
expect(newDir.is_directory).toBe(1);
|
||||||
|
|
||||||
const context2 = await koaAppContext({
|
const newDir2 = await postDirectory(session.id, 'root:/subdir:', 'subdir2');
|
||||||
sessionId: session.id,
|
|
||||||
request: {
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/files/root:/subdir:/children',
|
|
||||||
body: {
|
|
||||||
is_directory: 1,
|
|
||||||
name: 'subdir2',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await routeHandler(context2);
|
const newDirReload2 = await getFileMetadata(session.id, 'root:/subdir/subdir2:');
|
||||||
|
|
||||||
const newDir2 = context2.response.body;
|
|
||||||
|
|
||||||
const context3 = await koaAppContext({
|
|
||||||
sessionId: session.id,
|
|
||||||
request: {
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/files/root:/subdir/subdir2:',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await routeHandler(context3);
|
|
||||||
|
|
||||||
const newDirReload2 = context3.response.body;
|
|
||||||
expect(newDirReload2.id).toBe(newDir2.id);
|
expect(newDirReload2.id).toBe(newDir2.id);
|
||||||
expect(newDirReload2.name).toBe(newDir2.name);
|
expect(newDirReload2.name).toBe(newDir2.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should create files in sub-directory', async function() {
|
||||||
|
const { session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
await postDirectory(session.id, 'root:/:', 'subdir');
|
||||||
|
|
||||||
|
const newFile = await putFileContent(session.id, 'root:/subdir/photo.jpg:', testFilePath());
|
||||||
|
|
||||||
|
const newFileReload = await getFileMetadata(session.id, 'root:/subdir/photo.jpg:');
|
||||||
|
expect(newFileReload.id).toBe(newFile.id);
|
||||||
|
expect(newFileReload.name).toBe('photo.jpg');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not create a file with an invalid path', async function() {
|
||||||
|
const { session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
const context = await putFileContentContext(session.id, 'root:/does/not/exist/photo.jpg:', testFilePath());
|
||||||
|
expect(context.response.status).toBe(ErrorNotFound.httpCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get files', async function() {
|
||||||
|
const { session: session1, user: user1 } = await createUserAndSession(1);
|
||||||
|
const { user: user2 } = await createUserAndSession(2);
|
||||||
|
|
||||||
|
let file1: File = await makeTestFile(user1.id, 1);
|
||||||
|
const file2: File = await makeTestFile(user1.id, 2);
|
||||||
|
const file3: File = await makeTestFile(user2.id, 3);
|
||||||
|
|
||||||
|
const fileId1 = file1.id;
|
||||||
|
const fileId2 = file2.id;
|
||||||
|
|
||||||
|
// Can't get someone else file
|
||||||
|
const context = await getFileMetadataContext(session1.id, file3.id);
|
||||||
|
expect(context.response.status).toBe(ErrorForbidden.httpCode);
|
||||||
|
|
||||||
|
file1 = await getFileMetadata(session1.id, file1.id);
|
||||||
|
expect(file1.id).toBe(fileId1);
|
||||||
|
|
||||||
|
const fileModel = models().file({ userId: user1.id });
|
||||||
|
const paginatedResults = await getDirectoryChildren(session1.id, await fileModel.userRootFileId());
|
||||||
|
const allFiles: File[] = paginatedResults.items;
|
||||||
|
expect(allFiles.length).toBe(2);
|
||||||
|
expect(JSON.stringify(allFiles.map(f => f.id).sort())).toBe(JSON.stringify([fileId1, fileId2].sort()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not let create a file in a directory not owned by user', async function() {
|
||||||
|
const { session: session1 } = await createUserAndSession(1);
|
||||||
|
const { session: session2 } = await createUserAndSession(2);
|
||||||
|
|
||||||
|
const file = await putFileContent(session2.id, 'root:/test.jpg:', testFilePath());
|
||||||
|
const context = await getFileMetadataContext(session1.id, file.id);
|
||||||
|
expect(context.response.status).toBe(ErrorForbidden.httpCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update file properties', async function() {
|
||||||
|
const { session, user } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
const fileModel = models().file({ userId: user.id });
|
||||||
|
|
||||||
|
let file = await putFileContent(session.id, 'root:/test.jpg:', testFilePath());
|
||||||
|
|
||||||
|
// Can't have file with empty name
|
||||||
|
const context = await patchFileContext(session.id, file.id, { name: '' });
|
||||||
|
expect(context.response.status).toBe(ErrorUnprocessableEntity.httpCode);
|
||||||
|
|
||||||
|
await patchFile(session.id, file.id, { name: 'modified.jpg' });
|
||||||
|
file = await fileModel.load(file.id);
|
||||||
|
expect(file.name).toBe('modified.jpg');
|
||||||
|
|
||||||
|
await patchFile(session.id, file.id, { mime_type: 'image/png' });
|
||||||
|
file = await fileModel.load(file.id);
|
||||||
|
expect(file.mime_type).toBe('image/png');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not allow duplicate filenames', async function() {
|
||||||
|
const { session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
const c1 = await postDirectoryContext(session.id, 'root:/:', 'mydir');
|
||||||
|
expect(c1.response.status).toBe(200);
|
||||||
|
|
||||||
|
const c2 = await postDirectoryContext(session.id, 'root:/:', 'mydir');
|
||||||
|
expect(c2.response.status).toBe(ErrorConflict.httpCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should change the file parent', async function() {
|
||||||
|
const { session: session1, user: user1 } = await createUserAndSession(1);
|
||||||
|
const { user: user2 } = await createUserAndSession(2);
|
||||||
|
|
||||||
|
const fileModel = models().file({ userId: user1.id });
|
||||||
|
|
||||||
|
let file: File = await makeTestFile(user1.id);
|
||||||
|
const file2: File = await makeTestFile(user1.id, 2);
|
||||||
|
const dir: File = await postDirectory(session1.id, 'root', 'mydir');
|
||||||
|
|
||||||
|
// Can't set parent to another non-directory file
|
||||||
|
const context1 = await patchFileContext(session1.id, file.id, { parent_id: file2.id });
|
||||||
|
expect(context1.response.status).toBe(ErrorForbidden.httpCode);
|
||||||
|
|
||||||
|
// Can't set parent to someone else directory
|
||||||
|
const fileModel2 = models().file({ userId: user2.id });
|
||||||
|
const userRoot2 = await fileModel2.userRootFile();
|
||||||
|
const context2 = await patchFileContext(session1.id, file.id, { parent_id: userRoot2.id });
|
||||||
|
expect(context2.response.status).toBe(ErrorForbidden.httpCode);
|
||||||
|
|
||||||
|
// Finally, change the parent
|
||||||
|
await patchFile(session1.id, file.id, { parent_id: dir.id });
|
||||||
|
file = await fileModel.load(file.id);
|
||||||
|
expect(!!file.parent_id).toBe(true);
|
||||||
|
expect(file.parent_id).toBe(dir.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should delete a file', async function() {
|
||||||
|
const { user, session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
await makeTestFile(user.id, 1);
|
||||||
|
const file2: File = await makeTestFile(user.id, 2);
|
||||||
|
|
||||||
|
const fileModel = models().file({ userId: user.id });
|
||||||
|
|
||||||
|
let allFiles: File[] = await fileModel.all();
|
||||||
|
const beforeCount: number = allFiles.length;
|
||||||
|
|
||||||
|
await deleteFile(session.id, file2.id);
|
||||||
|
allFiles = await fileModel.all();
|
||||||
|
expect(allFiles.length).toBe(beforeCount - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create and delete directories', async function() {
|
||||||
|
const { user, session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
const dir1 = await postDirectory(session.id, 'root', 'dir1');
|
||||||
|
const dir2 = await postDirectory(session.id, dir1.id, 'dir2');
|
||||||
|
|
||||||
|
const dirReload2: File = await getFileMetadata(session.id, 'root:/dir1/dir2:');
|
||||||
|
expect(dirReload2.id).toBe(dir2.id);
|
||||||
|
|
||||||
|
// Delete one directory
|
||||||
|
await deleteFile(session.id, 'root:/dir1/dir2:');
|
||||||
|
const dirNotFoundContext = await getFileMetadataContext(session.id, 'root:/dir1/dir2:');
|
||||||
|
expect(dirNotFoundContext.response.status).toBe(ErrorNotFound.httpCode);
|
||||||
|
|
||||||
|
// Delete a directory and its sub-directories and files
|
||||||
|
const dir3 = await postDirectory(session.id, 'root:/dir1:', 'dir3');
|
||||||
|
const file1 = await putFileContent(session.id, 'root:/dir1/file1:', testFilePath());
|
||||||
|
const file2 = await putFileContent(session.id, 'root:/dir1/dir3/file2:', testFilePath());
|
||||||
|
await deleteFile(session.id, 'root:/dir1:');
|
||||||
|
|
||||||
|
const fileModel = models().file({ userId: user.id });
|
||||||
|
expect(!(await fileModel.load(dir1.id))).toBe(true);
|
||||||
|
expect(!(await fileModel.load(dir3.id))).toBe(true);
|
||||||
|
expect(!(await fileModel.load(file1.id))).toBe(true);
|
||||||
|
expect(!(await fileModel.load(file2.id))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not change the parent when updating a file', async function() {
|
||||||
|
const { user, session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
const fileModel = models().file({ userId: user.id });
|
||||||
|
|
||||||
|
const dir1: File = await postDirectory(session.id, 'root', 'dir1');
|
||||||
|
const file1: File = await putFileContent(session.id, 'root:/dir1/myfile.md:', await makeTempFileWithContent('testing'));
|
||||||
|
|
||||||
|
await putFileContent(session.id, 'root:/dir1/myfile.md:', await makeTempFileWithContent('new content'));
|
||||||
|
|
||||||
|
const fileReloaded1 = await fileModel.load(file1.id);
|
||||||
|
expect(fileReloaded1.parent_id).toBe(dir1.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not delete someone else file', async function() {
|
||||||
|
const { session: session1 } = await createUserAndSession(1);
|
||||||
|
const { user: user2 } = await createUserAndSession(2);
|
||||||
|
|
||||||
|
const file2: File = await makeTestFile(user2.id, 2);
|
||||||
|
|
||||||
|
const context = await deleteFileContext(session1.id, file2.id);
|
||||||
|
expect(context.response.status).toBe(ErrorForbidden.httpCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should let admin change or delete files', async function() {
|
||||||
|
const { session: adminSession } = await createUserAndSession(1, true);
|
||||||
|
const { user } = await createUserAndSession(2);
|
||||||
|
|
||||||
|
let file: File = await makeTestFile(user.id);
|
||||||
|
|
||||||
|
const fileModel = models().file({ userId: user.id });
|
||||||
|
|
||||||
|
await patchFile(adminSession.id, file.id, { name: 'modified.jpg' });
|
||||||
|
file = await fileModel.load(file.id);
|
||||||
|
expect(file.name).toBe('modified.jpg');
|
||||||
|
|
||||||
|
await deleteFile(adminSession.id, file.id);
|
||||||
|
expect(!(await fileModel.load(file.id))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update a file content', async function() {
|
||||||
|
const { session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
const contentPath1 = await makeTempFileWithContent('test1');
|
||||||
|
const contentPath2 = await makeTempFileWithContent('test2');
|
||||||
|
|
||||||
|
await putFileContent(session.id, 'root:/file.txt:', contentPath1);
|
||||||
|
|
||||||
|
const originalContent = (await getFileContent(session.id, 'root:/file.txt:')).toString();
|
||||||
|
expect(originalContent).toBe('test1');
|
||||||
|
|
||||||
|
await putFileContent(session.id, 'root:/file.txt:', contentPath2);
|
||||||
|
const modContent = (await getFileContent(session.id, 'root:/file.txt:')).toString();
|
||||||
|
expect(modContent).toBe('test2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should delete a file content', async function() {
|
||||||
|
const { user, session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
const file: File = await makeTestFile(user.id, 1);
|
||||||
|
await putFileContent(session.id, file.id, await makeTempFileWithContent('test1'));
|
||||||
|
|
||||||
|
await deleteFileContent(session.id, file.id);
|
||||||
|
|
||||||
|
const modFile = await getFileMetadata(session.id, file.id);
|
||||||
|
expect(modFile.size).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not allow reserved characters', async function() {
|
||||||
|
const { session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
const filenames = [
|
||||||
|
'invalid*invalid',
|
||||||
|
'invalid#invalid',
|
||||||
|
'invalid\\invalid',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const filename of filenames) {
|
||||||
|
const context = await putFileContentContext(session.id, `root:/${filename}:`, testFilePath());
|
||||||
|
expect(context.response.status).toBe(ErrorUnprocessableEntity.httpCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not allow a directory with the same name', async function() {
|
||||||
|
const { session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
{
|
||||||
|
await postDirectory(session.id, 'root', 'somedir');
|
||||||
|
const context = await putFileContentContext(session.id, 'root:/somedir:', testFilePath());
|
||||||
|
expect(context.response.status).toBe(ErrorUnprocessableEntity.httpCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
await putFileContent(session.id, 'root:/somefile.md:', testFilePath());
|
||||||
|
const context = await postDirectoryContext(session.id, 'root', 'somefile.md');
|
||||||
|
expect(context.response.status).toBe(ErrorConflict.httpCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not be possible to delete the root directory', async function() {
|
||||||
|
const { session } = await createUserAndSession(1, true);
|
||||||
|
|
||||||
|
const context = await deleteFileContext(session.id, 'root');
|
||||||
|
expect(context.response.status).toBe(ErrorForbidden.httpCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support root:/: format, which means root', async function() {
|
||||||
|
const { session, user } = await createUserAndSession(1, true);
|
||||||
|
const fileModel = models().file({ userId: user.id });
|
||||||
|
|
||||||
|
const root = await getFileMetadata(session.id, 'root:/:');
|
||||||
|
expect(root.id).toBe(await fileModel.userRootFileId());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should paginate results', async function() {
|
||||||
|
const { session: session1, user: user1 } = await createUserAndSession(1);
|
||||||
|
|
||||||
|
const file1: File = await makeTestFile(user1.id, 1);
|
||||||
|
await msleep(1);
|
||||||
|
const file2: File = await makeTestFile(user1.id, 2);
|
||||||
|
await msleep(1);
|
||||||
|
const file3: File = await makeTestFile(user1.id, 3);
|
||||||
|
|
||||||
|
const fileModel = models().file({ userId: user1.id });
|
||||||
|
const rootId = await fileModel.userRootFileId();
|
||||||
|
|
||||||
|
const pagination: Pagination = {
|
||||||
|
limit: 2,
|
||||||
|
order: [
|
||||||
|
{
|
||||||
|
by: 'updated_time',
|
||||||
|
dir: PaginationOrderDir.ASC,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
page: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const method of ['page', 'cursor']) {
|
||||||
|
const page1 = await getDirectoryChildren(session1.id, rootId, pagination);
|
||||||
|
expect(page1.items.length).toBe(2);
|
||||||
|
expect(page1.has_more).toBe(true);
|
||||||
|
expect(page1.items[0].id).toBe(file1.id);
|
||||||
|
expect(page1.items[1].id).toBe(file2.id);
|
||||||
|
|
||||||
|
const p = method === 'page' ? { ...pagination, page: 2 } : { cursor: page1.cursor };
|
||||||
|
const page2 = await getDirectoryChildren(session1.id, rootId, p);
|
||||||
|
expect(page2.items.length).toBe(1);
|
||||||
|
expect(page2.has_more).toBe(false);
|
||||||
|
expect(page2.items[0].id).toBe(file3.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should track file changes', async function() {
|
||||||
|
// We only do a basic check because most of the tests for this are in
|
||||||
|
// ChangeModel.test.ts
|
||||||
|
|
||||||
|
const { user, session: session1 } = await createUserAndSession(1);
|
||||||
|
|
||||||
|
const file1: File = await makeTestFile(user.id, 1);
|
||||||
|
await msleep(1);
|
||||||
|
const file2: File = await makeTestFile(user.id, 2);
|
||||||
|
|
||||||
|
const page1 = await getDelta(session1.id, file1.parent_id, { limit: 1 });
|
||||||
|
expect(page1.has_more).toBe(true);
|
||||||
|
expect(page1.items.length).toBe(1);
|
||||||
|
expect(page1.items[0].type).toBe(ChangeType.Create);
|
||||||
|
expect(page1.items[0].item.id).toBe(file1.id);
|
||||||
|
|
||||||
|
const page2 = await getDelta(session1.id, file1.parent_id, { cursor: page1.cursor, limit: 1 });
|
||||||
|
expect(page2.has_more).toBe(true);
|
||||||
|
expect(page2.items.length).toBe(1);
|
||||||
|
expect(page2.items[0].type).toBe(ChangeType.Create);
|
||||||
|
expect(page2.items[0].item.id).toBe(file2.id);
|
||||||
|
|
||||||
|
const page3 = await getDelta(session1.id, file1.parent_id, { cursor: page2.cursor, limit: 1 });
|
||||||
|
expect(page3.has_more).toBe(false);
|
||||||
|
expect(page3.items.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import routeHandler from '../../middleware/routeHandler';
|
import routeHandler from '../../middleware/routeHandler';
|
||||||
import { beforeAllDb, afterAllDb, beforeEachDb, koaAppContext } from '../../utils/testing/testUtils';
|
import { beforeAllDb, afterAllTests, beforeEachDb, koaAppContext } from '../../utils/testing/testUtils';
|
||||||
|
|
||||||
describe('api_ping', function() {
|
describe('api_ping', function() {
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ describe('api_ping', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await afterAllDb();
|
await afterAllTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Session } from '../../db';
|
import { Session } from '../../db';
|
||||||
import routeHandler from '../../middleware/routeHandler';
|
import routeHandler from '../../middleware/routeHandler';
|
||||||
import { beforeAllDb, afterAllDb, beforeEachDb, koaAppContext, createUserAndSession, models } from '../../utils/testing/testUtils';
|
import { beforeAllDb, afterAllTests, beforeEachDb, koaAppContext, createUserAndSession, models } from '../../utils/testing/testUtils';
|
||||||
|
|
||||||
describe('api_sessions', function() {
|
describe('api_sessions', function() {
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ describe('api_sessions', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await afterAllDb();
|
await afterAllTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
// https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
||||||
|
|
||||||
class ApiError extends Error {
|
class ApiError extends Error {
|
||||||
|
public static httpCode: number = 400;
|
||||||
|
|
||||||
public httpCode: number;
|
public httpCode: number;
|
||||||
public code: string;
|
public code: string;
|
||||||
public constructor(message: string, httpCode: number = 400, code: string = undefined) {
|
public constructor(message: string, httpCode: number = 400, code: string = undefined) {
|
||||||
@ -13,51 +15,65 @@ class ApiError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ErrorMethodNotAllowed extends ApiError {
|
export class ErrorMethodNotAllowed extends ApiError {
|
||||||
|
public static httpCode: number = 400;
|
||||||
|
|
||||||
public constructor(message: string = 'Method Not Allowed') {
|
public constructor(message: string = 'Method Not Allowed') {
|
||||||
super(message, 405);
|
super(message, ErrorMethodNotAllowed.httpCode);
|
||||||
Object.setPrototypeOf(this, ErrorMethodNotAllowed.prototype);
|
Object.setPrototypeOf(this, ErrorMethodNotAllowed.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ErrorNotFound extends ApiError {
|
export class ErrorNotFound extends ApiError {
|
||||||
|
public static httpCode: number = 404;
|
||||||
|
|
||||||
public constructor(message: string = 'Not Found') {
|
public constructor(message: string = 'Not Found') {
|
||||||
super(message, 404);
|
super(message, ErrorNotFound.httpCode);
|
||||||
Object.setPrototypeOf(this, ErrorNotFound.prototype);
|
Object.setPrototypeOf(this, ErrorNotFound.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ErrorForbidden extends ApiError {
|
export class ErrorForbidden extends ApiError {
|
||||||
|
public static httpCode: number = 403;
|
||||||
|
|
||||||
public constructor(message: string = 'Forbidden') {
|
public constructor(message: string = 'Forbidden') {
|
||||||
super(message, 403);
|
super(message, ErrorForbidden.httpCode);
|
||||||
Object.setPrototypeOf(this, ErrorForbidden.prototype);
|
Object.setPrototypeOf(this, ErrorForbidden.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ErrorBadRequest extends ApiError {
|
export class ErrorBadRequest extends ApiError {
|
||||||
|
public static httpCode: number = 400;
|
||||||
|
|
||||||
public constructor(message: string = 'Bad Request') {
|
public constructor(message: string = 'Bad Request') {
|
||||||
super(message, 400);
|
super(message, ErrorBadRequest.httpCode);
|
||||||
Object.setPrototypeOf(this, ErrorBadRequest.prototype);
|
Object.setPrototypeOf(this, ErrorBadRequest.prototype);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ErrorUnprocessableEntity extends ApiError {
|
export class ErrorUnprocessableEntity extends ApiError {
|
||||||
|
public static httpCode: number = 422;
|
||||||
|
|
||||||
public constructor(message: string = 'Unprocessable Entity') {
|
public constructor(message: string = 'Unprocessable Entity') {
|
||||||
super(message, 422);
|
super(message, ErrorUnprocessableEntity.httpCode);
|
||||||
Object.setPrototypeOf(this, ErrorUnprocessableEntity.prototype);
|
Object.setPrototypeOf(this, ErrorUnprocessableEntity.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ErrorConflict extends ApiError {
|
export class ErrorConflict extends ApiError {
|
||||||
|
public static httpCode: number = 409;
|
||||||
|
|
||||||
public constructor(message: string = 'Conflict') {
|
public constructor(message: string = 'Conflict') {
|
||||||
super(message, 409);
|
super(message, ErrorConflict.httpCode);
|
||||||
Object.setPrototypeOf(this, ErrorConflict.prototype);
|
Object.setPrototypeOf(this, ErrorConflict.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ErrorResyncRequired extends ApiError {
|
export class ErrorResyncRequired extends ApiError {
|
||||||
|
public static httpCode: number = 400;
|
||||||
|
|
||||||
public constructor(message: string = 'Delta cursor is invalid and the complete data should be resynced') {
|
public constructor(message: string = 'Delta cursor is invalid and the complete data should be resynced') {
|
||||||
super(message, 400, 'resyncRequired');
|
super(message, ErrorResyncRequired.httpCode, 'resyncRequired');
|
||||||
Object.setPrototypeOf(this, ErrorResyncRequired.prototype);
|
Object.setPrototypeOf(this, ErrorResyncRequired.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
194
packages/server/src/utils/testing/apiUtils.ts
Normal file
194
packages/server/src/utils/testing/apiUtils.ts
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
// These utility functions allow making API calls easily from test units.
|
||||||
|
// There's two versions of each function:
|
||||||
|
//
|
||||||
|
// - A regular one, eg. "postDirectory", which returns whatever would have
|
||||||
|
// normally return the API call. It also checks for error.
|
||||||
|
//
|
||||||
|
// - The other function is suffixed with "Context", eg "postDirectoryContext".
|
||||||
|
// In that case, it returns the complete Koa context, which can be used in
|
||||||
|
// particular to access the response object and test for errors.
|
||||||
|
|
||||||
|
import { File } from '../../db';
|
||||||
|
import routeHandler from '../../middleware/routeHandler';
|
||||||
|
import { PaginatedResults, Pagination, paginationToQueryParams } from '../../models/utils/pagination';
|
||||||
|
import { AppContext } from '../types';
|
||||||
|
import { koaAppContext } from './testUtils';
|
||||||
|
|
||||||
|
function checkContextError(context: AppContext) {
|
||||||
|
if (context.response.status >= 400) throw new Error(`Cannot create directory: ${JSON.stringify(context.response)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFileMetadataContext(sessionId: string, path: string): Promise<AppContext> {
|
||||||
|
const context = await koaAppContext({
|
||||||
|
sessionId: sessionId,
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/files/${path}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await routeHandler(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFileMetadata(sessionId: string, path: string): Promise<File> {
|
||||||
|
const context = await getFileMetadataContext(sessionId, path);
|
||||||
|
checkContextError(context);
|
||||||
|
return context.response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteFileContentContext(sessionId: string, path: string): Promise<AppContext> {
|
||||||
|
const context = await koaAppContext({
|
||||||
|
sessionId: sessionId,
|
||||||
|
request: {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `/api/files/${path}/content`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await routeHandler(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteFileContent(sessionId: string, path: string): Promise<void> {
|
||||||
|
const context = await deleteFileContentContext(sessionId, path);
|
||||||
|
checkContextError(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteFileContext(sessionId: string, path: string): Promise<AppContext> {
|
||||||
|
const context = await koaAppContext({
|
||||||
|
sessionId: sessionId,
|
||||||
|
request: {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `/api/files/${path}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await routeHandler(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteFile(sessionId: string, path: string): Promise<void> {
|
||||||
|
const context = await deleteFileContext(sessionId, path);
|
||||||
|
checkContextError(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postDirectoryContext(sessionId: string, parentPath: string, name: string): Promise<AppContext> {
|
||||||
|
const context = await koaAppContext({
|
||||||
|
sessionId: sessionId,
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: `/api/files/${parentPath}/children`,
|
||||||
|
body: {
|
||||||
|
is_directory: 1,
|
||||||
|
name: name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await routeHandler(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postDirectory(sessionId: string, parentPath: string, name: string): Promise<File> {
|
||||||
|
const context = await postDirectoryContext(sessionId, parentPath, name);
|
||||||
|
checkContextError(context);
|
||||||
|
return context.response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDirectoryChildrenContext(sessionId: string, path: string, pagination: Pagination = null): Promise<AppContext> {
|
||||||
|
const context = await koaAppContext({
|
||||||
|
sessionId: sessionId,
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/files/${path}/children`,
|
||||||
|
query: paginationToQueryParams(pagination),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await routeHandler(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDirectoryChildren(sessionId: string, path: string, pagination: Pagination = null): Promise<PaginatedResults> {
|
||||||
|
const context = await getDirectoryChildrenContext(sessionId, path, pagination);
|
||||||
|
checkContextError(context);
|
||||||
|
return context.response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putFileContentContext(sessionId: string, path: string, filePath: string): Promise<AppContext> {
|
||||||
|
const context = await koaAppContext({
|
||||||
|
sessionId: sessionId,
|
||||||
|
request: {
|
||||||
|
method: 'PUT',
|
||||||
|
url: `/api/files/${path}/content`,
|
||||||
|
files: { file: { path: filePath } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await routeHandler(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putFileContent(sessionId: string, path: string, filePath: string): Promise<File> {
|
||||||
|
const context = await putFileContentContext(sessionId, path, filePath);
|
||||||
|
checkContextError(context);
|
||||||
|
return context.response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFileContentContext(sessionId: string, path: string): Promise<AppContext> {
|
||||||
|
const context = await koaAppContext({
|
||||||
|
sessionId: sessionId,
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/files/${path}/content`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await routeHandler(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFileContent(sessionId: string, path: string): Promise<Buffer> {
|
||||||
|
const context = await getFileContentContext(sessionId, path);
|
||||||
|
checkContextError(context);
|
||||||
|
return context.response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patchFileContext(sessionId: string, path: string, file: File): Promise<AppContext> {
|
||||||
|
const context = await koaAppContext({
|
||||||
|
sessionId: sessionId,
|
||||||
|
request: {
|
||||||
|
method: 'PATCH',
|
||||||
|
url: `/api/files/${path}`,
|
||||||
|
body: file,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await routeHandler(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patchFile(sessionId: string, path: string, file: File): Promise<File> {
|
||||||
|
const context = await patchFileContext(sessionId, path, file);
|
||||||
|
checkContextError(context);
|
||||||
|
return context.response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDeltaContext(sessionId: string, path: string, pagination: Pagination): Promise<AppContext> {
|
||||||
|
const context = await koaAppContext({
|
||||||
|
sessionId: sessionId,
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/files/${path}/delta`,
|
||||||
|
query: paginationToQueryParams(pagination),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await routeHandler(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDelta(sessionId: string, path: string, pagination: Pagination): Promise<PaginatedResults> {
|
||||||
|
const context = await getDeltaContext(sessionId, path, pagination);
|
||||||
|
checkContextError(context);
|
||||||
|
return context.response.body;
|
||||||
|
}
|
@ -11,6 +11,8 @@ import FakeCookies from './koa/FakeCookies';
|
|||||||
import FakeRequest from './koa/FakeRequest';
|
import FakeRequest from './koa/FakeRequest';
|
||||||
import FakeResponse from './koa/FakeResponse';
|
import FakeResponse from './koa/FakeResponse';
|
||||||
import * as httpMocks from 'node-mocks-http';
|
import * as httpMocks from 'node-mocks-http';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
import * as fs from 'fs-extra';
|
||||||
|
|
||||||
// Takes into account the fact that this file will be inside the /dist directory
|
// Takes into account the fact that this file will be inside the /dist directory
|
||||||
// when it runs.
|
// when it runs.
|
||||||
@ -20,6 +22,18 @@ let db_: DbConnection = null;
|
|||||||
|
|
||||||
// require('source-map-support').install();
|
// require('source-map-support').install();
|
||||||
|
|
||||||
|
export function randomHash(): string {
|
||||||
|
return crypto.createHash('md5').update(`${Date.now()}-${Math.random()}`).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
let tempDir_: string = null;
|
||||||
|
export async function tempDir(): Promise<string> {
|
||||||
|
if (tempDir_) return tempDir_;
|
||||||
|
tempDir_ = `${packageRootDir}/temp/${randomHash()}`;
|
||||||
|
await fs.mkdirp(tempDir_);
|
||||||
|
return tempDir_;
|
||||||
|
}
|
||||||
|
|
||||||
export async function beforeAllDb(unitName: string) {
|
export async function beforeAllDb(unitName: string) {
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
@ -34,9 +48,16 @@ export async function beforeAllDb(unitName: string) {
|
|||||||
db_ = await connectDb(config.database);
|
db_ = await connectDb(config.database);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function afterAllDb() {
|
export async function afterAllTests() {
|
||||||
await disconnectDb(db_);
|
if (db_) {
|
||||||
db_ = null;
|
await disconnectDb(db_);
|
||||||
|
db_ = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempDir_) {
|
||||||
|
await fs.remove(tempDir_);
|
||||||
|
tempDir_ = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function beforeEachDb() {
|
export async function beforeEachDb() {
|
||||||
@ -98,6 +119,7 @@ export async function koaAppContext(options: AppContextTestOptions = null): Prom
|
|||||||
appContext.response = new FakeResponse();
|
appContext.response = new FakeResponse();
|
||||||
appContext.headers = { ...reqOptions.headers };
|
appContext.headers = { ...reqOptions.headers };
|
||||||
appContext.req = req;
|
appContext.req = req;
|
||||||
|
appContext.query = req.query;
|
||||||
appContext.method = req.method;
|
appContext.method = req.method;
|
||||||
|
|
||||||
if (options.sessionId) {
|
if (options.sessionId) {
|
||||||
|
Loading…
Reference in New Issue
Block a user