Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] implement api of user #2

Merged
merged 2 commits into from
Oct 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
HOST=127.0.0.1
PORT=3000
SESSION_SECRET=
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ const config = {
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['internals/**/*.js', 'client/**/stories.js'],
devDependencies: [
'internals/**/*.js',
'client/**/stories.js',
'**/__tests__/*.test.js',
],
},
],
'import/prefer-default-export': 0,
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ yarn-error.log
build
coverage
.vscode
.local

# sqlite
*.sqlite3
12 changes: 12 additions & 0 deletions .sequelizerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const path = require('path');

const base = path.resolve(__dirname, 'server/db');

const config = {
config: path.resolve(base, 'config/index.js'),
'models-path': path.resolve(base, 'models'),
'seeders-path': path.resolve(base, 'seeders'),
'migrations-path': path.resolve(base, 'migrations'),
};

module.exports = config;
12 changes: 11 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
'@babel/preset-react',
],
plugins: [
[
'module-resolver',
Expand Down
3 changes: 3 additions & 0 deletions internals/jest/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const config = {
'^.+\\.js$': 'babel-jest',
},
testRegex: '__tests__/.*\\.test\\.js$',
globalSetup: '<rootDir>/internals/jest/globalSetup.js',
globalTeardown: '<rootDir>/internals/jest/globalTeardown.js',
setupFiles: ['<rootDir>/internals/jest/setup.js'],
};

module.exports = config;
3 changes: 3 additions & 0 deletions internals/jest/globalSetup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = () => {
require('../../server'); // eslint-disable-line global-require
};
5 changes: 5 additions & 0 deletions internals/jest/globalTeardown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const server = require('../../server');

module.exports = () => {
server.close();
};
12 changes: 12 additions & 0 deletions internals/jest/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const axios = require('axios');
const { host, port } = require('../../server/constants');

const origin = `http://${host}:${port}`;

global.createApi = apiPath => {
const api = axios.create({
baseURL: `${origin}${apiPath}`,
});

return api;
};
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,29 @@
"test:watch": "yarn run test --watch",
"lint": "yarn run lint-eslint .",
"lint:eslint": "eslint --ignore-path .gitignore",
"storybook": "start-storybook -p 6006"
"storybook": "start-storybook -p 6006",
"db:migrate": "sequelize db:migrate",
"db:seed": "sequelize db:seed:all",
"db:seed:undo": "sequelize db:seed:undo:all"
},
"repository": "https://github.com/arthur791004/FullStackEngineerChallenge",
"author": "arthur <arthur791004@gmail.com>",
"license": "MIT",
"private": true,
"dependencies": {
"@hot-loader/react-dom": "~16.8.6",
"axios": "~0.19.0",
"bcrypt": "~3.0.6",
"body-parser": "~1.19.0",
"dotenv": "~8.1.0",
"express": "~4.17.1",
"express-session": "~1.16.2",
"prop-types": "~15.7.2",
"react": "~16.8.6",
"react-dom": "~16.8.6",
"react-hot-loader": "~4.12.10",
"react-router-dom": "~5.0.1",
"sequelize": "~5.19.2",
"styled-components": "~4.3.2",
"webpack-hot-middleware": "~2.25.0"
},
Expand Down Expand Up @@ -67,6 +75,8 @@
"lint-staged": "~9.2.0",
"prettier": "~1.18.2",
"rimraf": "~2.6.3",
"sequelize-cli": "~5.5.1",
"sqlite3": "~4.1.0",
"uglifyjs-webpack-plugin": "~2.1.3",
"url-loader": "~2.0.1",
"webpack": "~4.35.3",
Expand Down
219 changes: 219 additions & 0 deletions server/api/__tests__/users.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
const { ROLES } = require('../../constants/users');
const db = require('../../db/models');

const api = global.createApi('/api/v1/users');

const generateUser = (name, role = ROLES.NORMAL) => ({
email: `${name}@test.com`,
password: name,
role,
});

const userIds = {
admin: null,
normal: null,
};

const cookies = {
admin: null,
normal: null,
};

const adminUser = generateUser('admin', ROLES.ADMIN);
const normalUser = generateUser('normal', ROLES.ADMIN);

const login = async user => {
const { email, password } = user;
const res = await api.post('/login', { email, password });

return res;
};

const createUser = async (user, cookie) => {
const headers = {};
if (cookie) {
headers.Cookie = cookie;
}

const res = await api.post('/', user, { headers });

return res;
};

const updateUser = async (userId, user, cookie) => {
const headers = {};
if (cookie) {
headers.Cookie = cookie;
}

const res = await api.patch(`/${userId}`, user, { headers });

return res;
};

const getCookieByUser = async user => {
const { headers } = await login(user);

return headers['set-cookie'].join(';');
};

/**
* Clean and create data for testing
*/
beforeAll(async () => {
await db.sequelize.sync();
await db.User.destroy({ truncate: true });

userIds.admin = (await db.User.create(adminUser)).get('id');
userIds.normal = (await db.User.create(normalUser)).get('id');

cookies.admin = await getCookieByUser(adminUser);
cookies.normal = await getCookieByUser(normalUser);
});

describe('login', () => {
it('should succeed with correct email and password', async () => {
const { status, data } = await login(adminUser);

expect(status).toBe(200);
expect(data.data).toMatchObject({
email: adminUser.email,
role: adminUser.role,
});
});

it('should fail with wrong password', async () => {
const { email } = adminUser;
const password = 'wrong password';

try {
await login({ email, password });
} catch ({ response: { status } }) {
expect(status).toBe(401);
}
});
});

describe('logout', () => {
it('should succeed', async () => {
const { status } = await api.post('/logout');

expect(status).toBe(200);
});
});

describe('get user list', () => {
it('should succeed', async () => {
const { status, data } = await api.get('/');
const emails = data.data.map(user => user.email);

expect(status).toBe(200);
expect(emails).toEqual([adminUser.email, normalUser.email]);
});
});

describe('create a new user', () => {
const user = generateUser('new');

it('should succeed with admin user', async () => {
const { status, data } = await createUser(user, cookies.admin);

expect(status).toBe(200);
expect(data.data).toMatchObject({
email: user.email,
role: user.role,
});
});

it('should fail with normal user', async () => {
try {
await createUser(user, cookies.normal);
} catch ({ response: { status } }) {
expect(status).toBe(403);
}
});

it('should fail without login', async () => {
try {
await createUser(user);
} catch ({ response: { status } }) {
expect(status).toBe(401);
}
});
});

describe('get user by id', () => {
it('should succeed', async () => {
const { status, data } = await api.get(`/${userIds.admin}`);

expect(status).toBe(200);
expect(data.data).toMatchObject({
email: adminUser.email,
});
});
});

describe('update a user by id', () => {
const user = generateUser('update');
const attrsToBeUpdated = {
role: ROLES.ADMIN,
};

let userId = null;

beforeAll(async () => {
userId = (await db.User.create(user)).get('id');
});

afterAll(async () => {
await db.User.destroy({ where: { id: userId } });
});

it('should succeed with admin user', async () => {
const { status, data } = await updateUser(
userId,
attrsToBeUpdated,
cookies.admin
);

expect(status).toBe(200);
expect(data.data).toMatchObject({
role: attrsToBeUpdated.role,
});
});

it('should fail with normal user', async () => {
try {
await updateUser(userId, attrsToBeUpdated, cookies.normal);
} catch ({ response: { status } }) {
expect(status).toBe(403);
}
});

it('should fail without login', async () => {
try {
await updateUser(userId, attrsToBeUpdated);
} catch ({ response: { status } }) {
expect(status).toBe(401);
}
});
});

describe('delete a user by id', () => {
const user = generateUser('delete');
let userId = null;

beforeAll(async () => {
userId = (await db.User.create(user)).get('id');
});

it('should succeed with admin user', async () => {
const { status } = await api.delete(`/${userId}`, {
headers: {
Cookie: cookies.admin,
},
});

expect(status).toBe(200);
});
});
8 changes: 8 additions & 0 deletions server/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { Router } = require('express');
const v1 = require('./v1');

const router = Router();

router.use('/v1', v1);

module.exports = router;
Loading