mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Server: Moved controller tests to route and model
This commit is contained in:
parent
247bd9bfd9
commit
f14ea46f0b
@ -1484,18 +1484,12 @@ packages/server/src/controllers/BaseController.js.map
|
||||
packages/server/src/controllers/api/FileController.d.ts
|
||||
packages/server/src/controllers/api/FileController.js
|
||||
packages/server/src/controllers/api/FileController.js.map
|
||||
packages/server/src/controllers/api/FileController.test.d.ts
|
||||
packages/server/src/controllers/api/FileController.test.js
|
||||
packages/server/src/controllers/api/FileController.test.js.map
|
||||
packages/server/src/controllers/api/OAuthController.d.ts
|
||||
packages/server/src/controllers/api/OAuthController.js
|
||||
packages/server/src/controllers/api/OAuthController.js.map
|
||||
packages/server/src/controllers/api/SessionController.d.ts
|
||||
packages/server/src/controllers/api/SessionController.js
|
||||
packages/server/src/controllers/api/SessionController.js.map
|
||||
packages/server/src/controllers/api/SessionController.test.d.ts
|
||||
packages/server/src/controllers/api/SessionController.test.js
|
||||
packages/server/src/controllers/api/SessionController.test.js.map
|
||||
packages/server/src/controllers/api/UserController.d.ts
|
||||
packages/server/src/controllers/api/UserController.js
|
||||
packages/server/src/controllers/api/UserController.js.map
|
||||
@ -1592,6 +1586,9 @@ packages/server/src/models/utils/pagination.test.js.map
|
||||
packages/server/src/routes/api/files.d.ts
|
||||
packages/server/src/routes/api/files.js
|
||||
packages/server/src/routes/api/files.js.map
|
||||
packages/server/src/routes/api/files.test.d.ts
|
||||
packages/server/src/routes/api/files.test.js
|
||||
packages/server/src/routes/api/files.test.js.map
|
||||
packages/server/src/routes/api/index.d.ts
|
||||
packages/server/src/routes/api/index.js
|
||||
packages/server/src/routes/api/index.js.map
|
||||
@ -1604,6 +1601,9 @@ packages/server/src/routes/api/ping.test.js.map
|
||||
packages/server/src/routes/api/sessions.d.ts
|
||||
packages/server/src/routes/api/sessions.js
|
||||
packages/server/src/routes/api/sessions.js.map
|
||||
packages/server/src/routes/api/sessions.test.d.ts
|
||||
packages/server/src/routes/api/sessions.test.js
|
||||
packages/server/src/routes/api/sessions.test.js.map
|
||||
packages/server/src/routes/default.d.ts
|
||||
packages/server/src/routes/default.js
|
||||
packages/server/src/routes/default.js.map
|
||||
@ -1631,6 +1631,9 @@ packages/server/src/routes/index/user.js.map
|
||||
packages/server/src/routes/index/users.d.ts
|
||||
packages/server/src/routes/index/users.js
|
||||
packages/server/src/routes/index/users.js.map
|
||||
packages/server/src/routes/index/users.test.d.ts
|
||||
packages/server/src/routes/index/users.test.js
|
||||
packages/server/src/routes/index/users.test.js.map
|
||||
packages/server/src/routes/oauth2/authorize.d.ts
|
||||
packages/server/src/routes/oauth2/authorize.js
|
||||
packages/server/src/routes/oauth2/authorize.js.map
|
||||
@ -1682,6 +1685,9 @@ packages/server/src/utils/routeUtils.js.map
|
||||
packages/server/src/utils/routeUtils.test.d.ts
|
||||
packages/server/src/utils/routeUtils.test.js
|
||||
packages/server/src/utils/routeUtils.test.js.map
|
||||
packages/server/src/utils/testing/apiUtils.d.ts
|
||||
packages/server/src/utils/testing/apiUtils.js
|
||||
packages/server/src/utils/testing/apiUtils.js.map
|
||||
packages/server/src/utils/testing/koa/FakeCookies.d.ts
|
||||
packages/server/src/utils/testing/koa/FakeCookies.js
|
||||
packages/server/src/utils/testing/koa/FakeCookies.js.map
|
||||
|
18
.gitignore
vendored
18
.gitignore
vendored
@ -1473,18 +1473,12 @@ packages/server/src/controllers/BaseController.js.map
|
||||
packages/server/src/controllers/api/FileController.d.ts
|
||||
packages/server/src/controllers/api/FileController.js
|
||||
packages/server/src/controllers/api/FileController.js.map
|
||||
packages/server/src/controllers/api/FileController.test.d.ts
|
||||
packages/server/src/controllers/api/FileController.test.js
|
||||
packages/server/src/controllers/api/FileController.test.js.map
|
||||
packages/server/src/controllers/api/OAuthController.d.ts
|
||||
packages/server/src/controllers/api/OAuthController.js
|
||||
packages/server/src/controllers/api/OAuthController.js.map
|
||||
packages/server/src/controllers/api/SessionController.d.ts
|
||||
packages/server/src/controllers/api/SessionController.js
|
||||
packages/server/src/controllers/api/SessionController.js.map
|
||||
packages/server/src/controllers/api/SessionController.test.d.ts
|
||||
packages/server/src/controllers/api/SessionController.test.js
|
||||
packages/server/src/controllers/api/SessionController.test.js.map
|
||||
packages/server/src/controllers/api/UserController.d.ts
|
||||
packages/server/src/controllers/api/UserController.js
|
||||
packages/server/src/controllers/api/UserController.js.map
|
||||
@ -1581,6 +1575,9 @@ packages/server/src/models/utils/pagination.test.js.map
|
||||
packages/server/src/routes/api/files.d.ts
|
||||
packages/server/src/routes/api/files.js
|
||||
packages/server/src/routes/api/files.js.map
|
||||
packages/server/src/routes/api/files.test.d.ts
|
||||
packages/server/src/routes/api/files.test.js
|
||||
packages/server/src/routes/api/files.test.js.map
|
||||
packages/server/src/routes/api/index.d.ts
|
||||
packages/server/src/routes/api/index.js
|
||||
packages/server/src/routes/api/index.js.map
|
||||
@ -1593,6 +1590,9 @@ packages/server/src/routes/api/ping.test.js.map
|
||||
packages/server/src/routes/api/sessions.d.ts
|
||||
packages/server/src/routes/api/sessions.js
|
||||
packages/server/src/routes/api/sessions.js.map
|
||||
packages/server/src/routes/api/sessions.test.d.ts
|
||||
packages/server/src/routes/api/sessions.test.js
|
||||
packages/server/src/routes/api/sessions.test.js.map
|
||||
packages/server/src/routes/default.d.ts
|
||||
packages/server/src/routes/default.js
|
||||
packages/server/src/routes/default.js.map
|
||||
@ -1620,6 +1620,9 @@ packages/server/src/routes/index/user.js.map
|
||||
packages/server/src/routes/index/users.d.ts
|
||||
packages/server/src/routes/index/users.js
|
||||
packages/server/src/routes/index/users.js.map
|
||||
packages/server/src/routes/index/users.test.d.ts
|
||||
packages/server/src/routes/index/users.test.js
|
||||
packages/server/src/routes/index/users.test.js.map
|
||||
packages/server/src/routes/oauth2/authorize.d.ts
|
||||
packages/server/src/routes/oauth2/authorize.js
|
||||
packages/server/src/routes/oauth2/authorize.js.map
|
||||
@ -1671,6 +1674,9 @@ packages/server/src/utils/routeUtils.js.map
|
||||
packages/server/src/utils/routeUtils.test.d.ts
|
||||
packages/server/src/utils/routeUtils.test.js
|
||||
packages/server/src/utils/routeUtils.test.js.map
|
||||
packages/server/src/utils/testing/apiUtils.d.ts
|
||||
packages/server/src/utils/testing/apiUtils.js
|
||||
packages/server/src/utils/testing/apiUtils.js.map
|
||||
packages/server/src/utils/testing/koa/FakeCookies.d.ts
|
||||
packages/server/src/utils/testing/koa/FakeCookies.js
|
||||
packages/server/src/utils/testing/koa/FakeCookies.js.map
|
||||
|
2
packages/app-desktop/package-lock.json
generated
2
packages/app-desktop/package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "1.6.7",
|
||||
"version": "1.7.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
25
packages/server/package-lock.json
generated
25
packages/server/package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "1.6.4",
|
||||
"version": "1.7.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -1308,6 +1308,17 @@
|
||||
"pretty-format": "^26.0.0"
|
||||
}
|
||||
},
|
||||
"@types/jsdom": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.6.tgz",
|
||||
"integrity": "sha512-yQA+HxknGtW9AkRTNyiSH3OKW5V+WzO8OPTdne99XwJkYC+KYxfNIcoJjeiSqP3V00PUUpFP6Myoo9wdIu78DQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/parse5": "*",
|
||||
"@types/tough-cookie": "*"
|
||||
}
|
||||
},
|
||||
"@types/keygrip": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.1.tgz",
|
||||
@ -1384,6 +1395,12 @@
|
||||
"integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/parse5": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.0.tgz",
|
||||
"integrity": "sha512-oPwPSj4a1wu9rsXTEGIJz91ISU725t0BmSnUhb57sI+M8XEmvUop84lzuiYdq0Y5M6xLY8DBPg0C2xEQKLyvBA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prettier": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.5.tgz",
|
||||
@ -1412,6 +1429,12 @@
|
||||
"integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/tough-cookie": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz",
|
||||
"integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "13.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.2.tgz",
|
||||
|
@ -37,11 +37,13 @@
|
||||
"@rmp135/sql-ts": "^1.7.0",
|
||||
"@types/fs-extra": "^8.0.0",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/jsdom": "^16.2.6",
|
||||
"@types/koa": "^2.0.49",
|
||||
"@types/markdown-it": "^12.0.0",
|
||||
"@types/mustache": "^0.8.32",
|
||||
"@types/yargs": "^13.0.2",
|
||||
"jest": "^26.6.3",
|
||||
"jsdom": "^16.4.0",
|
||||
"node-mocks-http": "^1.10.0",
|
||||
"source-map-support": "^0.5.13",
|
||||
"typescript": "^4.1.2"
|
||||
|
@ -1,192 +0,0 @@
|
||||
import { models, controllers, createUserAndSession, checkThrowAsync, beforeAllDb, afterAllTests, beforeEachDb } from '../../utils/testing/testUtils';
|
||||
import { File, User } from '../../db';
|
||||
import { ErrorForbidden, ErrorUnprocessableEntity } from '../../utils/errors';
|
||||
|
||||
describe('UserController', function() {
|
||||
|
||||
beforeAll(async () => {
|
||||
await beforeAllDb('UserController');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await afterAllTests();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEachDb();
|
||||
});
|
||||
|
||||
it('should create a new user along with his root file', async function() {
|
||||
const { session } = await createUserAndSession(1, true);
|
||||
|
||||
const controller = controllers().apiUser();
|
||||
|
||||
const newUser = await controller.postUser(session.id, { email: 'test@example.com', password: '123456' });
|
||||
|
||||
expect(!!newUser).toBe(true);
|
||||
expect(!!newUser.id).toBe(true);
|
||||
expect(!!newUser.is_admin).toBe(false);
|
||||
expect(!!newUser.email).toBe(true);
|
||||
expect(!newUser.password).toBe(true);
|
||||
|
||||
const userModel = models().user({ userId: newUser.id });
|
||||
const userFromModel: User = await userModel.load(newUser.id);
|
||||
|
||||
expect(!!userFromModel.password).toBe(true);
|
||||
expect(userFromModel.password === '123456').toBe(false); // Password has been hashed
|
||||
|
||||
const fileModel = models().file({ userId: newUser.id });
|
||||
const rootFile: File = await fileModel.userRootFile();
|
||||
|
||||
expect(!!rootFile).toBe(true);
|
||||
expect(!!rootFile.id).toBe(true);
|
||||
});
|
||||
|
||||
it('should not create anything, neither user, root file nor permissions, if user creation fail', async function() {
|
||||
const { user, session } = await createUserAndSession(1, true);
|
||||
|
||||
const controller = controllers().apiUser();
|
||||
const fileModel = models().file({ userId: user.id });
|
||||
const permissionModel = models().permission();
|
||||
const userModel = models().user({ userId: user.id });
|
||||
|
||||
await controller.postUser(session.id, { email: 'test@example.com', password: '123456' });
|
||||
|
||||
const beforeFileCount = (await fileModel.all()).length;
|
||||
const beforeUserCount = (await userModel.all()).length;
|
||||
const beforePermissionCount = (await permissionModel.all()).length;
|
||||
|
||||
expect(beforeFileCount).toBe(2);
|
||||
expect(beforeUserCount).toBe(2);
|
||||
|
||||
let hasThrown = false;
|
||||
try {
|
||||
await controller.postUser(session.id, { email: 'test@example.com', password: '123456' });
|
||||
} catch (error) {
|
||||
hasThrown = true;
|
||||
}
|
||||
|
||||
expect(hasThrown).toBe(true);
|
||||
|
||||
const afterFileCount = (await fileModel.all()).length;
|
||||
const afterUserCount = (await userModel.all()).length;
|
||||
const afterPermissionCount = (await permissionModel.all()).length;
|
||||
|
||||
expect(beforeFileCount).toBe(afterFileCount);
|
||||
expect(beforeUserCount).toBe(afterUserCount);
|
||||
expect(beforePermissionCount).toBe(afterPermissionCount);
|
||||
});
|
||||
|
||||
it('should change user properties', async function() {
|
||||
const { user, session } = await createUserAndSession(1, true);
|
||||
|
||||
const controller = controllers().apiUser();
|
||||
const userModel = models().user({ userId: user.id });
|
||||
|
||||
await controller.patchUser(session.id, { id: user.id, email: 'test2@example.com' });
|
||||
let modUser: User = await userModel.load(user.id);
|
||||
expect(modUser.email).toBe('test2@example.com');
|
||||
|
||||
const previousPassword = modUser.password;
|
||||
await controller.patchUser(session.id, { id: user.id, password: 'abcdefgh' });
|
||||
modUser = await userModel.load(user.id);
|
||||
expect(!!modUser.password).toBe(true);
|
||||
expect(modUser.password === previousPassword).toBe(false);
|
||||
});
|
||||
|
||||
it('should get a user', async function() {
|
||||
const { user, session } = await createUserAndSession();
|
||||
|
||||
const controller = controllers().apiUser();
|
||||
const gotUser = await controller.getUser(session.id, user.id);
|
||||
|
||||
expect(gotUser.id).toBe(user.id);
|
||||
expect(gotUser.email).toBe(user.email);
|
||||
});
|
||||
|
||||
it('should validate user objects', async function() {
|
||||
const { user: admin, session: adminSession } = await createUserAndSession(1, true);
|
||||
const { user: user1, session: userSession1 } = await createUserAndSession(2, false);
|
||||
const { user: user2 } = await createUserAndSession(3, false);
|
||||
|
||||
let error = null;
|
||||
const controller = controllers().apiUser();
|
||||
|
||||
// Non-admin user can't create a user
|
||||
error = await checkThrowAsync(async () => await controller.postUser(userSession1.id, { email: 'newone@example.com', password: '1234546' }));
|
||||
expect(error instanceof ErrorForbidden).toBe(true);
|
||||
|
||||
// Email must be set
|
||||
error = await checkThrowAsync(async () => await controller.postUser(adminSession.id, { email: '', password: '1234546' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// Password must be set
|
||||
error = await checkThrowAsync(async () => await controller.postUser(adminSession.id, { email: 'newone@example.com', password: '' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// ID must be set when updating a user
|
||||
error = await checkThrowAsync(async () => await controller.patchUser(userSession1.id, { email: 'newone@example.com' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// non-admin user cannot modify another user
|
||||
error = await checkThrowAsync(async () => await controller.patchUser(userSession1.id, { id: user2.id, email: 'newone@example.com' }));
|
||||
expect(error instanceof ErrorForbidden).toBe(true);
|
||||
|
||||
// email must be set
|
||||
error = await checkThrowAsync(async () => await controller.patchUser(userSession1.id, { id: user1.id, email: '' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// password must be set
|
||||
error = await checkThrowAsync(async () => await controller.patchUser(userSession1.id, { id: user1.id, password: '' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// non-admin user cannot make a user an admin
|
||||
error = await checkThrowAsync(async () => await controller.patchUser(userSession1.id, { id: user1.id, is_admin: 1 }));
|
||||
expect(error instanceof ErrorForbidden).toBe(true);
|
||||
|
||||
// non-admin user cannot remove admin bit from themselves
|
||||
error = await checkThrowAsync(async () => await controller.patchUser(adminSession.id, { id: admin.id, is_admin: 0 }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// there is already a user with this email
|
||||
error = await checkThrowAsync(async () => await controller.patchUser(userSession1.id, { id: user1.id, email: user2.email }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// check that the email is valid
|
||||
error = await checkThrowAsync(async () => await controller.patchUser(userSession1.id, { id: user1.id, email: 'ohno' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
});
|
||||
|
||||
it('should delete a user', async function() {
|
||||
const { user: admin, session: adminSession } = await createUserAndSession(1, true);
|
||||
const { user: user1, session: session1 } = await createUserAndSession(2, false);
|
||||
const { user: user2, session: session2 } = await createUserAndSession(3, false);
|
||||
|
||||
const controller = controllers().apiUser();
|
||||
const userModel = models().user({ userId: admin.id });
|
||||
|
||||
const allUsers: File[] = await userModel.all();
|
||||
const beforeCount: number = allUsers.length;
|
||||
|
||||
// Can't delete someone else user
|
||||
const error = await checkThrowAsync(async () => await controller.deleteUser(session1.id, user2.id));
|
||||
expect(error instanceof ErrorForbidden).toBe(true);
|
||||
expect((await userModel.all()).length).toBe(beforeCount);
|
||||
|
||||
// Admin can delete any user
|
||||
await controller.deleteUser(adminSession.id, user1.id);
|
||||
expect((await userModel.all()).length).toBe(beforeCount - 1);
|
||||
const allFiles = await models().file().all() as File[];
|
||||
expect(allFiles.length).toBe(2);
|
||||
expect(!!allFiles.find(f => f.owner_id === admin.id)).toBe(true);
|
||||
expect(!!allFiles.find(f => f.owner_id === user2.id)).toBe(true);
|
||||
|
||||
// Can delete own user
|
||||
const fileModel = models().file({ userId: user2.id });
|
||||
expect(!!(await fileModel.userRootFile())).toBe(true);
|
||||
await controller.deleteUser(session2.id, user2.id);
|
||||
expect((await userModel.all()).length).toBe(beforeCount - 2);
|
||||
expect(!!(await fileModel.userRootFile())).toBe(false);
|
||||
});
|
||||
|
||||
});
|
@ -56,14 +56,14 @@ describe('notificationHandler', function() {
|
||||
});
|
||||
|
||||
test('should not check admin password for non-admin', async function() {
|
||||
const { user } = await createUserAndSession(1, false);
|
||||
const { session } = await createUserAndSession(1, false);
|
||||
|
||||
await createUserAndSession(2, true, {
|
||||
email: defaultAdminEmail,
|
||||
password: defaultAdminPassword,
|
||||
});
|
||||
|
||||
const context = await koaAppContext({ owner: user });
|
||||
const context = await koaAppContext({ sessionId: session.id });
|
||||
await notificationHandler(context, koaNext);
|
||||
|
||||
const notifications: Notification[] = await models().notification().all();
|
||||
|
@ -39,7 +39,7 @@ export default async function(ctx: AppContext) {
|
||||
|
||||
ctx.response.status = error.httpCode ? error.httpCode : 500;
|
||||
|
||||
const responseFormat = routeResponseFormat(match, ctx.path);
|
||||
const responseFormat = routeResponseFormat(match, ctx);
|
||||
|
||||
if (responseFormat === RouteResponseFormat.Html) {
|
||||
ctx.response.set('Content-Type', 'text/html');
|
||||
|
98
packages/server/src/models/UserModel.test.ts
Normal file
98
packages/server/src/models/UserModel.test.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models, checkThrowAsync } from '../utils/testing/testUtils';
|
||||
import { File } from '../db';
|
||||
import { ErrorForbidden, ErrorUnprocessableEntity } from '../utils/errors';
|
||||
|
||||
describe('NotificationModel', function() {
|
||||
|
||||
beforeAll(async () => {
|
||||
await beforeAllDb('NotificationModel');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await afterAllTests();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEachDb();
|
||||
});
|
||||
|
||||
test('should validate user objects', async function() {
|
||||
const { user: admin } = await createUserAndSession(1, true);
|
||||
const { user: user1 } = await createUserAndSession(2, false);
|
||||
const { user: user2 } = await createUserAndSession(3, false);
|
||||
|
||||
let error = null;
|
||||
|
||||
// Non-admin user can't create a user
|
||||
error = await checkThrowAsync(async () => await models().user({ userId: user1.id }).save({ email: 'newone@example.com', password: '1234546' }));
|
||||
expect(error instanceof ErrorForbidden).toBe(true);
|
||||
|
||||
// Email must be set
|
||||
error = await checkThrowAsync(async () => await models().user({ userId: admin.id }).save({ email: '', password: '1234546' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// Password must be set
|
||||
error = await checkThrowAsync(async () => await models().user({ userId: admin.id }).save({ email: 'newone@example.com', password: '' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// non-admin user cannot modify another user
|
||||
error = await checkThrowAsync(async () => await models().user({ userId: user1.id }).save({ id: user2.id, email: 'newone@example.com' }));
|
||||
expect(error instanceof ErrorForbidden).toBe(true);
|
||||
|
||||
// email must be set
|
||||
error = await checkThrowAsync(async () => await models().user({ userId: user1.id }).save({ id: user1.id, email: '' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// password must be set
|
||||
error = await checkThrowAsync(async () => await models().user({ userId: user1.id }).save({ id: user1.id, password: '' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// non-admin user cannot make a user an admin
|
||||
error = await checkThrowAsync(async () => await models().user({ userId: user1.id }).save({ id: user1.id, is_admin: 1 }));
|
||||
expect(error instanceof ErrorForbidden).toBe(true);
|
||||
|
||||
// non-admin user cannot remove admin bit from themselves
|
||||
error = await checkThrowAsync(async () => await models().user({ userId: admin.id }).save({ id: admin.id, is_admin: 0 }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// there is already a user with this email
|
||||
error = await checkThrowAsync(async () => await models().user({ userId: user1.id }).save({ id: user1.id, email: user2.email }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
|
||||
// check that the email is valid
|
||||
error = await checkThrowAsync(async () => await models().user({ userId: user1.id }).save({ id: user1.id, email: 'ohno' }));
|
||||
expect(error instanceof ErrorUnprocessableEntity).toBe(true);
|
||||
});
|
||||
|
||||
test('should delete a user', async function() {
|
||||
const { user: admin } = await createUserAndSession(1, true);
|
||||
const { user: user1 } = await createUserAndSession(2, false);
|
||||
const { user: user2 } = await createUserAndSession(3, false);
|
||||
|
||||
const userModel = models().user({ userId: admin.id });
|
||||
|
||||
const allUsers: File[] = await userModel.all();
|
||||
const beforeCount: number = allUsers.length;
|
||||
|
||||
// Can't delete someone else user
|
||||
const error = await checkThrowAsync(async () => await models().user({ userId: user1.id }).delete(user2.id));
|
||||
expect(error instanceof ErrorForbidden).toBe(true);
|
||||
expect((await userModel.all()).length).toBe(beforeCount);
|
||||
|
||||
// Admin can delete any user
|
||||
await models().user({ userId: admin.id }).delete(user1.id);
|
||||
expect((await userModel.all()).length).toBe(beforeCount - 1);
|
||||
const allFiles = await models().file().all() as File[];
|
||||
expect(allFiles.length).toBe(2);
|
||||
expect(!!allFiles.find(f => f.owner_id === admin.id)).toBe(true);
|
||||
expect(!!allFiles.find(f => f.owner_id === user2.id)).toBe(true);
|
||||
|
||||
// Can delete own user
|
||||
const fileModel = models().file({ userId: user2.id });
|
||||
expect(!!(await fileModel.userRootFile())).toBe(true);
|
||||
await models().user({ userId: user2.id }).delete(user2.id);
|
||||
expect((await userModel.all()).length).toBe(beforeCount - 2);
|
||||
expect(!!(await fileModel.userRootFile())).toBe(false);
|
||||
});
|
||||
|
||||
});
|
@ -1,59 +1,61 @@
|
||||
import { SubPath, Route, redirect } from '../../utils/routeUtils';
|
||||
import { AppContext } from '../../utils/types';
|
||||
import { contextSessionId, formParse } from '../../utils/requestUtils';
|
||||
import { ErrorMethodNotAllowed, ErrorUnprocessableEntity } from '../../utils/errors';
|
||||
import { User } from '../../db';
|
||||
import { baseUrl } from '../../config';
|
||||
// Not used??
|
||||
|
||||
function makeUser(isNew: boolean, fields: any): User {
|
||||
const user: User = {
|
||||
email: fields.email,
|
||||
full_name: fields.full_name,
|
||||
};
|
||||
// import { SubPath, Route, redirect } from '../../utils/routeUtils';
|
||||
// import { AppContext } from '../../utils/types';
|
||||
// import { contextSessionId, formParse } from '../../utils/requestUtils';
|
||||
// import { ErrorMethodNotAllowed, ErrorUnprocessableEntity } from '../../utils/errors';
|
||||
// import { User } from '../../db';
|
||||
// import { baseUrl } from '../../config';
|
||||
|
||||
if (fields.password) {
|
||||
if (fields.password !== fields.password2) throw new ErrorUnprocessableEntity('Passwords do not match');
|
||||
user.password = fields.password;
|
||||
}
|
||||
// function makeUser(isNew: boolean, fields: any): User {
|
||||
// const user: User = {
|
||||
// email: fields.email,
|
||||
// full_name: fields.full_name,
|
||||
// };
|
||||
|
||||
if (!isNew) user.id = fields.id;
|
||||
// if (fields.password) {
|
||||
// if (fields.password !== fields.password2) throw new ErrorUnprocessableEntity('Passwords do not match');
|
||||
// user.password = fields.password;
|
||||
// }
|
||||
|
||||
return user;
|
||||
}
|
||||
// if (!isNew) user.id = fields.id;
|
||||
|
||||
const route: Route = {
|
||||
// return user;
|
||||
// }
|
||||
|
||||
exec: async function(_path: SubPath, ctx: AppContext) {
|
||||
const sessionId = contextSessionId(ctx);
|
||||
// const route: Route = {
|
||||
|
||||
// if (ctx.method === 'GET') {
|
||||
// return ctx.controllers.indexUser().getOne(sessionId);
|
||||
// }
|
||||
// exec: async function(_path: SubPath, ctx: AppContext) {
|
||||
// const sessionId = contextSessionId(ctx);
|
||||
|
||||
if (ctx.method === 'POST') {
|
||||
const user: User = {};
|
||||
// // if (ctx.method === 'GET') {
|
||||
// // return ctx.controllers.indexUser().getOne(sessionId);
|
||||
// // }
|
||||
|
||||
try {
|
||||
const body = await formParse(ctx.req);
|
||||
const fields = body.fields;
|
||||
const isNew = !!Number(fields.is_new);
|
||||
const user = makeUser(isNew, fields);
|
||||
// if (ctx.method === 'POST') {
|
||||
// const user: User = {};
|
||||
|
||||
if (isNew) {
|
||||
await ctx.controllers.apiUser().postUser(sessionId, user);
|
||||
} else {
|
||||
await ctx.controllers.apiUser().patchUser(sessionId, user);
|
||||
}
|
||||
// try {
|
||||
// const body = await formParse(ctx.req);
|
||||
// const fields = body.fields;
|
||||
// const isNew = !!Number(fields.is_new);
|
||||
// const user = makeUser(isNew, fields);
|
||||
|
||||
return redirect(ctx, `${baseUrl()}/users`);
|
||||
} catch (error) {
|
||||
return ctx.controllers.indexProfile().getIndex(sessionId, user, error);
|
||||
}
|
||||
}
|
||||
// if (isNew) {
|
||||
// await ctx.controllers.apiUser().postUser(sessionId, user);
|
||||
// } else {
|
||||
// await ctx.controllers.apiUser().patchUser(sessionId, user);
|
||||
// }
|
||||
|
||||
throw new ErrorMethodNotAllowed();
|
||||
},
|
||||
// return redirect(ctx, `${baseUrl()}/users`);
|
||||
// } catch (error) {
|
||||
// return ctx.controllers.indexProfile().getIndex(sessionId, user, error);
|
||||
// }
|
||||
// }
|
||||
|
||||
};
|
||||
// throw new ErrorMethodNotAllowed();
|
||||
// },
|
||||
|
||||
export default route;
|
||||
// };
|
||||
|
||||
// export default route;
|
||||
|
149
packages/server/src/routes/index/users.test.ts
Normal file
149
packages/server/src/routes/index/users.test.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import { File, User } from '../../db';
|
||||
import routeHandler from '../../middleware/routeHandler';
|
||||
import { checkContextError } from '../../utils/testing/apiUtils';
|
||||
import { beforeAllDb, afterAllTests, beforeEachDb, koaAppContext, createUserAndSession, models, parseHtml } from '../../utils/testing/testUtils';
|
||||
|
||||
export async function postUser(sessionId: string, email: string, password: string): Promise<User> {
|
||||
const context = await koaAppContext({
|
||||
sessionId: sessionId,
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: '/users/new',
|
||||
body: {
|
||||
email: email,
|
||||
password: password,
|
||||
password2: password,
|
||||
post_button: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await routeHandler(context);
|
||||
checkContextError(context);
|
||||
return context.response.body;
|
||||
}
|
||||
|
||||
export async function patchUser(sessionId: string, user: any): Promise<User> {
|
||||
const context = await koaAppContext({
|
||||
sessionId: sessionId,
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: '/users',
|
||||
body: {
|
||||
...user,
|
||||
post_button: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await routeHandler(context);
|
||||
checkContextError(context);
|
||||
return context.response.body;
|
||||
}
|
||||
|
||||
export async function getUserHtml(sessionId: string, userId: string): Promise<string> {
|
||||
const context = await koaAppContext({
|
||||
sessionId: sessionId,
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: `/users/${userId}`,
|
||||
},
|
||||
});
|
||||
|
||||
await routeHandler(context);
|
||||
checkContextError(context);
|
||||
return context.response.body;
|
||||
}
|
||||
|
||||
describe('index_users', function() {
|
||||
|
||||
beforeAll(async () => {
|
||||
await beforeAllDb('index_users');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await afterAllTests();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEachDb();
|
||||
});
|
||||
|
||||
test('should create a new user along with his root file', async function() {
|
||||
const { user: admin, session } = await createUserAndSession(1, true);
|
||||
|
||||
await postUser(session.id, 'test@example.com', '123456');
|
||||
const newUser = await models().user({ userId: admin.id }).loadByEmail('test@example.com');
|
||||
|
||||
expect(!!newUser).toBe(true);
|
||||
expect(!!newUser.id).toBe(true);
|
||||
expect(!!newUser.is_admin).toBe(false);
|
||||
expect(!!newUser.email).toBe(true);
|
||||
|
||||
const userModel = models().user({ userId: newUser.id });
|
||||
const userFromModel: User = await userModel.load(newUser.id);
|
||||
|
||||
expect(!!userFromModel.password).toBe(true);
|
||||
expect(userFromModel.password === '123456').toBe(false); // Password has been hashed
|
||||
|
||||
const fileModel = models().file({ userId: newUser.id });
|
||||
const rootFile: File = await fileModel.userRootFile();
|
||||
|
||||
expect(!!rootFile).toBe(true);
|
||||
expect(!!rootFile.id).toBe(true);
|
||||
});
|
||||
|
||||
test('should not create anything, neither user, root file nor permissions, if user creation fail', async function() {
|
||||
const { user, session } = await createUserAndSession(1, true);
|
||||
|
||||
const fileModel = models().file({ userId: user.id });
|
||||
const permissionModel = models().permission();
|
||||
const userModel = models().user({ userId: user.id });
|
||||
|
||||
await postUser(session.id, 'test@example.com', '123456');
|
||||
|
||||
const beforeFileCount = (await fileModel.all()).length;
|
||||
const beforeUserCount = (await userModel.all()).length;
|
||||
const beforePermissionCount = (await permissionModel.all()).length;
|
||||
|
||||
expect(beforeFileCount).toBe(2);
|
||||
expect(beforeUserCount).toBe(2);
|
||||
|
||||
await postUser(session.id, 'test@example.com', '123456');
|
||||
|
||||
const afterFileCount = (await fileModel.all()).length;
|
||||
const afterUserCount = (await userModel.all()).length;
|
||||
const afterPermissionCount = (await permissionModel.all()).length;
|
||||
|
||||
expect(beforeFileCount).toBe(afterFileCount);
|
||||
expect(beforeUserCount).toBe(afterUserCount);
|
||||
expect(beforePermissionCount).toBe(afterPermissionCount);
|
||||
});
|
||||
|
||||
test('should change user properties', async function() {
|
||||
const { user, session } = await createUserAndSession(1, true);
|
||||
|
||||
const userModel = models().user({ userId: user.id });
|
||||
|
||||
await patchUser(session.id, { id: user.id, email: 'test2@example.com' });
|
||||
let modUser: User = await userModel.load(user.id);
|
||||
expect(modUser.email).toBe('test2@example.com');
|
||||
|
||||
const previousPassword = modUser.password;
|
||||
await patchUser(session.id, { id: user.id, password: 'abcdefgh', password2: 'abcdefgh' });
|
||||
modUser = await userModel.load(user.id);
|
||||
expect(!!modUser.password).toBe(true);
|
||||
expect(modUser.password === previousPassword).toBe(false);
|
||||
});
|
||||
|
||||
test('should get a user', async function() {
|
||||
const { user, session } = await createUserAndSession();
|
||||
|
||||
const userHtml = await getUserHtml(session.id, user.id);
|
||||
const doc = parseHtml(userHtml);
|
||||
|
||||
// <input class="input" type="email" name="email" value="user1@localhost"/>
|
||||
expect((doc.querySelector('input[name=email]') as any).value).toBe('user1@localhost');
|
||||
});
|
||||
|
||||
});
|
@ -6,10 +6,10 @@ import { User } from '../../db';
|
||||
import { baseUrl } from '../../config';
|
||||
|
||||
function makeUser(isNew: boolean, fields: any): User {
|
||||
const user: User = {
|
||||
email: fields.email,
|
||||
full_name: fields.full_name,
|
||||
};
|
||||
const user: User = {};
|
||||
|
||||
if ('email' in fields) user.email = fields.email;
|
||||
if ('full_name' in fields) user.full_name = fields.full_name;
|
||||
|
||||
if (fields.password) {
|
||||
if (fields.password !== fields.password2) throw new ErrorUnprocessableEntity('Passwords do not match');
|
||||
|
@ -8,7 +8,7 @@ import indexLogoutRoute from './index/logout';
|
||||
import indexHomeRoute from './index/home';
|
||||
import indexProfileRoute from './index/profile';
|
||||
import indexUsersRoute from './index/users';
|
||||
import indexUserRoute from './index/user';
|
||||
// import indexUserRoute from './index/user';
|
||||
import indexFilesRoute from './index/files';
|
||||
import indexNotificationsRoute from './index/notifications';
|
||||
import defaultRoute from './default';
|
||||
@ -23,7 +23,7 @@ const routes: Routes = {
|
||||
'home': indexHomeRoute,
|
||||
'profile': indexProfileRoute,
|
||||
'users': indexUsersRoute,
|
||||
'user': indexUserRoute,
|
||||
// 'user': indexUserRoute,
|
||||
'files': indexFilesRoute,
|
||||
'notifications': indexNotificationsRoute,
|
||||
|
||||
|
@ -144,7 +144,10 @@ export function parseSubPath(p: string): SubPath {
|
||||
return output;
|
||||
}
|
||||
|
||||
export function routeResponseFormat(match: MatchedRoute, rawPath: string): RouteResponseFormat {
|
||||
export function routeResponseFormat(match: MatchedRoute, context: AppContext): RouteResponseFormat {
|
||||
// if (context.query && context.query.response_format === 'json') return RouteResponseFormat.Json;
|
||||
|
||||
const rawPath = context.path;
|
||||
if (match && match.route.responseFormat) return match.route.responseFormat;
|
||||
|
||||
let path = rawPath;
|
||||
|
@ -14,7 +14,7 @@ import { PaginatedResults, Pagination, paginationToQueryParams } from '../../mod
|
||||
import { AppContext } from '../types';
|
||||
import { koaAppContext } from './testUtils';
|
||||
|
||||
function checkContextError(context: AppContext) {
|
||||
export function checkContextError(context: AppContext) {
|
||||
if (context.response.status >= 400) throw new Error(`Cannot create directory: ${JSON.stringify(context.response)}`);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import FakeResponse from './koa/FakeResponse';
|
||||
import * as httpMocks from 'node-mocks-http';
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as jsdom from 'jsdom';
|
||||
|
||||
// Takes into account the fact that this file will be inside the /dist directory
|
||||
// when it runs.
|
||||
@ -157,6 +158,11 @@ export function controllers() {
|
||||
return controllerFactory(models());
|
||||
}
|
||||
|
||||
export function parseHtml(html: string): Document {
|
||||
const dom = new jsdom.JSDOM(html);
|
||||
return dom.window.document;
|
||||
}
|
||||
|
||||
interface CreateUserAndSessionOptions {
|
||||
email?: string;
|
||||
password?: string;
|
||||
|
Loading…
Reference in New Issue
Block a user