Initial commit
This commit is contained in:
parent
eace557491
commit
d007dfd9e6
23 changed files with 10773 additions and 1 deletions
12
.babelrc
Normal file
12
.babelrc
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
"targets": {
|
||||||
|
"esmodules": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
5
.editorconfig
Normal file
5
.editorconfig
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
2
.eslintignore
Normal file
2
.eslintignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/node_modules/**
|
||||||
|
/build
|
24
.eslintrc.json
Normal file
24
.eslintrc.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"es6": true,
|
||||||
|
"node": true,
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"google",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 12,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": "error",
|
||||||
|
"dot-notation": [2, {
|
||||||
|
"allowKeywords": true
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -130,3 +130,4 @@ dist
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
reports
|
||||||
|
|
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm test
|
6
.prettierrc
Normal file
6
.prettierrc
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
17
README.md
17
README.md
|
@ -1,2 +1,17 @@
|
||||||
# express
|
# Express template
|
||||||
|
|
||||||
|
Ce repo est un simple template pour un projet Express écrit en ES6.
|
||||||
|
|
||||||
|
## Les scripts
|
||||||
|
|
||||||
|
Pour démarrer le projet en mode développement il faut utiliser la commande `npm run watch`. Cette commande permet de relancer automatiquement le server dès qu'un fichier est modifié.
|
||||||
|
|
||||||
|
En mode production il faut utiliser `npm run build` puis `npm start`. La première commande va transpiler le code en une version optimisée et la déployer dans le dossier `dist/`. La seconde va simplement lancer un serveur Node sur ce dossier.
|
||||||
|
|
||||||
|
Pour linter le code et corriger la plupart des problèmes automatiquement il y a la commande `npm run lint:fix`.
|
||||||
|
|
||||||
|
Il y a ensuite 2 commandes pour tester le code :
|
||||||
|
- `npm run test:lint` qui permet de vérifier que le code est correctement écris (tabulation, ;, const/let, etc)
|
||||||
|
- `npm run test:jest` qui permet de lancer les tests unitaires et d'intégration
|
||||||
|
|
||||||
|
Ces 2 commandes sont automatiquement lancé avec `npm test` manuellement ou lors d'un commit via Huksy.
|
24
jest.config.js
Normal file
24
jest.config.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/** @type {import('jest').Config} */
|
||||||
|
const config = {
|
||||||
|
verbose: false,
|
||||||
|
testTimeout: 5000,
|
||||||
|
reporters: [
|
||||||
|
'default',
|
||||||
|
[
|
||||||
|
'jest-junit',
|
||||||
|
{ outputDirectory: 'reports', outputName: 'report.xml' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
collectCoverage: true,
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'src/**/*.{js,jsx}',
|
||||||
|
'!**/node_modules/**',
|
||||||
|
'!**/build/**',
|
||||||
|
],
|
||||||
|
coverageReporters: ['clover', 'lcov', ['text', { skipFull: true }]],
|
||||||
|
coverageDirectory: 'reports',
|
||||||
|
showSeed: true,
|
||||||
|
slowTestThreshold: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
10243
package-lock.json
generated
Normal file
10243
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
55
package.json
Normal file
55
package.json
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"name": "express-template",
|
||||||
|
"version": "0.0.0.",
|
||||||
|
"private": false,
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./build/bin/www",
|
||||||
|
"build": "babel ./src --out-dir ./build --copy-files",
|
||||||
|
"watch": "nodemon --exec babel-node src/bin/www",
|
||||||
|
"lint:fix": "eslint . --fix",
|
||||||
|
"test": "npm run test:lint && npm run test:jest",
|
||||||
|
"test:lint": "eslint .",
|
||||||
|
"test:jest": "jest --testTimeout=10000 --collectCoverage=true --detectOpenHandles --forceExit ./test",
|
||||||
|
"prepare": "npx husky install"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.js": "eslint --cache --fix"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18.x"
|
||||||
|
},
|
||||||
|
"nodemonConfig": {
|
||||||
|
"ignore": [
|
||||||
|
"test/*",
|
||||||
|
"dist/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"ejs": "^3.1.9",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"express-session": "^1.17.3",
|
||||||
|
"joi": "^17.11.0",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
|
"rand-token": "^1.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.23.4",
|
||||||
|
"@babel/core": "^7.23.7",
|
||||||
|
"@babel/node": "^7.22.19",
|
||||||
|
"@babel/preset-env": "^7.23.7",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-config-google": "^0.14.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.1.2",
|
||||||
|
"husky": "^8.0.3",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jest-junit": "^16.0.0",
|
||||||
|
"nodemon": "^3.0.2",
|
||||||
|
"supertest": "^6.3.3"
|
||||||
|
}
|
||||||
|
}
|
67
src/app.js
Normal file
67
src/app.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import createHttpError from 'http-errors';
|
||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import cookieParser from 'cookie-parser';
|
||||||
|
import logger from 'morgan';
|
||||||
|
import passport from 'passport';
|
||||||
|
import session from 'express-session';
|
||||||
|
|
||||||
|
import { nodeEnv, secretSession } from './config';
|
||||||
|
|
||||||
|
import passportConfig from './libs/passport';
|
||||||
|
|
||||||
|
import authRouter from './routes/v1/auth';
|
||||||
|
import meRouter from './routes/v1/me';
|
||||||
|
import usersRouter from './routes/v1/users';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
passportConfig(passport);
|
||||||
|
|
||||||
|
app.use(passport.initialize());
|
||||||
|
|
||||||
|
app.set('views', path.join(__dirname, '../views'));
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
|
||||||
|
app.set('trust proxy', 1);
|
||||||
|
app.use(
|
||||||
|
session({
|
||||||
|
secret: secretSession,
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
cookie: { secure: true },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(logger('dev'));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
app.use(passport.session());
|
||||||
|
|
||||||
|
app.use('/v1/auth', authRouter);
|
||||||
|
app.use('/v1/me', meRouter);
|
||||||
|
app.use('/v1/users', usersRouter);
|
||||||
|
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
next(createHttpError(404));
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
app.use((err, req, res, next) => {
|
||||||
|
// set locals, only providing error in development
|
||||||
|
res.locals.message = err.message;
|
||||||
|
res.locals.error = nodeEnv === 'development' ? err : {};
|
||||||
|
|
||||||
|
// render the error page
|
||||||
|
res.status(err.status || 500);
|
||||||
|
if (req.xhr) {
|
||||||
|
res.json(err.message);
|
||||||
|
} else {
|
||||||
|
res.render('error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
43
src/bin/www
Normal file
43
src/bin/www
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
const debug = require('debug')('express:server');
|
||||||
|
const http = require('http');
|
||||||
|
const app = require('../app');
|
||||||
|
const { port } = require('../config');
|
||||||
|
|
||||||
|
app.set('port', port);
|
||||||
|
|
||||||
|
const server = http.createServer(app);
|
||||||
|
|
||||||
|
function onError(error) {
|
||||||
|
if (error.syscall !== 'listen') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`;
|
||||||
|
|
||||||
|
switch (error.code) {
|
||||||
|
case 'EACCES':
|
||||||
|
console.error(`${bind} requires elevated privileges`);
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
case 'EADDRINUSE':
|
||||||
|
console.error(`${bind} is already in use`);
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onListening() {
|
||||||
|
const addr = server.address();
|
||||||
|
const bind =
|
||||||
|
typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`;
|
||||||
|
debug(`Listening on ${bind}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
server.on('error', onError);
|
||||||
|
server.on('listening', onListening);
|
7
src/config/index.js
Normal file
7
src/config/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = {
|
||||||
|
nodeEnv: process.env.NODE_ENV || 'development',
|
||||||
|
port: parseInt(process.env.PORT || '3000', 10),
|
||||||
|
jwtKey: process.env.JWT_KEY || 'aekiFahshai5ooveeNuumaivoh2zohch',
|
||||||
|
secretSession:
|
||||||
|
process.env.SECRET_SESSION || 'Shie0kooz0uem9bu0aihoi3Woxoo9uxi',
|
||||||
|
};
|
13
src/helpers/object.js
Normal file
13
src/helpers/object.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Check if object is empty
|
||||||
|
* @param {Object} obj
|
||||||
|
*
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
export const isEmpty = (obj) => {
|
||||||
|
if (!obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(obj).length === 0;
|
||||||
|
};
|
40
src/libs/passport.js
Normal file
40
src/libs/passport.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
|
||||||
|
// import createHttpError from 'http-errors';
|
||||||
|
|
||||||
|
import { jwtKey } from '../config';
|
||||||
|
|
||||||
|
const jwtOptions = {
|
||||||
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
|
secretOrKey: jwtKey,
|
||||||
|
passReqToCallback: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (passport) => {
|
||||||
|
passport.serializeUser((user, done) => {
|
||||||
|
done(null, user);
|
||||||
|
});
|
||||||
|
|
||||||
|
passport.deserializeUser((user, done) => {
|
||||||
|
done(null, user);
|
||||||
|
});
|
||||||
|
|
||||||
|
passport.use(
|
||||||
|
'jwt',
|
||||||
|
new JwtStrategy(jwtOptions, (req, jwtPayload, next) => {
|
||||||
|
// INFO: C'est ici que l'on vérifie que le contenu du payload est valide
|
||||||
|
/**
|
||||||
|
* Exemple de jwtPayload:
|
||||||
|
* {
|
||||||
|
* userId: X,
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* On doit aller faire une requête sur la table Users pour vérifier
|
||||||
|
* qu'il y a un bien un user avec comme id X.
|
||||||
|
* Si ok :
|
||||||
|
* next(null, user);
|
||||||
|
* Sinon :
|
||||||
|
* throw createHttpError(401);
|
||||||
|
*/
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
124
src/middleware/Users.js
Normal file
124
src/middleware/Users.js
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
import Joi from 'joi';
|
||||||
|
import createHttpError from 'http-errors';
|
||||||
|
// import jwt from 'jsonwebtoken';
|
||||||
|
// import { uid } from 'rand-token';
|
||||||
|
|
||||||
|
// import { jwtKey } from '../config';
|
||||||
|
import { isEmpty } from '../helpers/object';
|
||||||
|
|
||||||
|
const schema = Joi.object({
|
||||||
|
username: Joi.string().min(3).max(30).required(),
|
||||||
|
password: Joi.string().required(),
|
||||||
|
}).with('username', 'password');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new user
|
||||||
|
* @param {Object} req
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
export const createUser = async (req) => {
|
||||||
|
// INFO: On commence par vérifier que l'on reçoit bien un objet dans le body
|
||||||
|
if (isEmpty(req.body)) {
|
||||||
|
throw createHttpError(406);
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On vérifie ensuite que l'objet reçu correspond au schéma autorisé
|
||||||
|
const { value, error } = schema.validate(req.body, { abortEarly: false });
|
||||||
|
|
||||||
|
// INFO: Ce n'est pas le cas, on retourne une erreur
|
||||||
|
if (error) {
|
||||||
|
throw createHttpError(406, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Valeurs reçues:', value);
|
||||||
|
|
||||||
|
// INFO: Sinon on créé l'utilisateur
|
||||||
|
// const user = await models.Users.create(value);
|
||||||
|
|
||||||
|
// INFO: Puis on le retourne
|
||||||
|
// return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth user
|
||||||
|
* @param {Object} req
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
export const authUser = async (req) => {
|
||||||
|
if (isEmpty(req.body)) {
|
||||||
|
throw createHttpError(406);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body, { abortEarly: false });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw createHttpError(406, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { username, password } = value;
|
||||||
|
|
||||||
|
console.log('Valeurs reçues:', username, password);
|
||||||
|
|
||||||
|
// const user = await models.Users.findOne({
|
||||||
|
// where: {
|
||||||
|
// username,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (!user) {
|
||||||
|
// throw createHttpError(404);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!(await user.validPassword(password, user.password))) {
|
||||||
|
// throw createHttpError(401);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const payload = {
|
||||||
|
// id: user.id,
|
||||||
|
// token: uid(32),
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const bearer = jwt.sign(payload, jwtKey, {
|
||||||
|
// expiresIn: '30d',
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const date = new Date();
|
||||||
|
// date.setDate(date.getDate() + 30);
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// bearer,
|
||||||
|
// expireAt: date,
|
||||||
|
// };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user profile (password)
|
||||||
|
* @param {Object} req
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
export const updateMyProfile = async (req) => {
|
||||||
|
if (isEmpty(req.body)) {
|
||||||
|
throw createHttpError(406);
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaMyProfile = Joi.object({
|
||||||
|
password: Joi.string().required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { value, error } = schemaMyProfile.validate(req.body, {
|
||||||
|
abortEarly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw createHttpError(406, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Valeurs reçues:', value);
|
||||||
|
|
||||||
|
// await req.user.update(value);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
};
|
17
src/routes/v1/auth.js
Normal file
17
src/routes/v1/auth.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import express from 'express';
|
||||||
|
import { authUser } from '../../middleware/Users';
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.route('/').post(async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const data = await authUser(req);
|
||||||
|
|
||||||
|
res.status(201).json(data);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
23
src/routes/v1/me.js
Normal file
23
src/routes/v1/me.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import express from 'express';
|
||||||
|
import passport from 'passport';
|
||||||
|
import { updateMyProfile } from '../../middleware/Users';
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router
|
||||||
|
.route('/')
|
||||||
|
.get(passport.authenticate('jwt'), (req, res) => {
|
||||||
|
res.status(200).json(req.user);
|
||||||
|
})
|
||||||
|
.patch(passport.authenticate('jwt'), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const data = await updateMyProfile(req);
|
||||||
|
|
||||||
|
res.status(200).json(data);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
20
src/routes/v1/users.js
Normal file
20
src/routes/v1/users.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import express from 'express';
|
||||||
|
import { createUser } from '../../middleware/Users';
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.route('/').post(async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const data = await createUser(req);
|
||||||
|
|
||||||
|
res.status(201).json(data);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name === 'SequelizeUniqueConstraintError') {
|
||||||
|
err.status = 409;
|
||||||
|
}
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
13
test/functions.test.js
Normal file
13
test/functions.test.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { isEmpty } from '../src/helpers/object';
|
||||||
|
|
||||||
|
describe('Test my profil', () => {
|
||||||
|
test('It should response true when send undefined object to isEmpty', async () => {
|
||||||
|
expect(isEmpty()).toBe(true);
|
||||||
|
});
|
||||||
|
test('It should response true when send empty object to isEmpty', async () => {
|
||||||
|
expect(isEmpty({})).toBe(true);
|
||||||
|
});
|
||||||
|
test('It should response false when send empty object to isEmpty', async () => {
|
||||||
|
expect(isEmpty({ true: true })).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
11
test/index.test.js
Normal file
11
test/index.test.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const request = require('supertest');
|
||||||
|
const app = require('../src/app');
|
||||||
|
|
||||||
|
describe('Test router', () => {
|
||||||
|
test('It should response 404 when tries to get not found route', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/api')
|
||||||
|
.set('X-Requested-With', 'XMLHttpRequest');
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
});
|
||||||
|
});
|
3
views/error.ejs
Normal file
3
views/error.ejs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<h1><%= message %></h1>
|
||||||
|
<h2><%= error.status %></h2>
|
||||||
|
<pre><%= error.stack %></pre>
|
Loading…
Reference in a new issue