extracts out tests into separate files to establish a better testing pattern
This commit is contained in:
@@ -12,14 +12,12 @@ const payload = new Payload({
|
|||||||
exports.payload = payload;
|
exports.payload = payload;
|
||||||
|
|
||||||
exports.start = (cb) => {
|
exports.start = (cb) => {
|
||||||
expressApp.listen(config.port, () => {
|
const server = expressApp.listen(config.port, () => {
|
||||||
console.log(`listening on ${config.port}...`);
|
console.log(`listening on ${config.port}...`);
|
||||||
if (cb) cb();
|
if (cb) cb();
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
exports.close = (cb) => {
|
return server;
|
||||||
if (expressApp) expressApp.close(cb);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// when app.js is launched directly
|
// when app.js is launched directly
|
||||||
|
|||||||
6
jest.config.js
Normal file
6
jest.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
verbose: true,
|
||||||
|
testEnvironment: 'node',
|
||||||
|
globalSetup: '<rootDir>/src/tests/globalSetup.js',
|
||||||
|
globalTeardown: '<rootDir>/src/tests/globalTeardown.js',
|
||||||
|
};
|
||||||
@@ -3,15 +3,9 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"nodemonConfig": {
|
|
||||||
"ignore": [
|
|
||||||
"src/*",
|
|
||||||
"demo/client/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test:unit": "cross-env NODE_ENV=test jest --config src/tests/jest.config.js",
|
"test:unit": "cross-env NODE_ENV=test jest --config src/tests/jest.config.js",
|
||||||
"test:int": "cross-env NODE_ENV=test jest src/tests/integration/api.spec.js --forceExit --detectOpenHandles",
|
"test:int": "cross-env NODE_ENV=test jest --forceExit",
|
||||||
"cov": "npm run core:build && node ./node_modules/jest/bin/jest.js src/tests --coverage",
|
"cov": "npm run core:build && node ./node_modules/jest/bin/jest.js src/tests --coverage",
|
||||||
"dev": "nodemon demo/server.js",
|
"dev": "nodemon demo/server.js",
|
||||||
"server": "node demo/server.js",
|
"server": "node demo/server.js",
|
||||||
|
|||||||
@@ -4,42 +4,19 @@
|
|||||||
|
|
||||||
const faker = require('faker');
|
const faker = require('faker');
|
||||||
const server = require('../../../demo/server');
|
const server = require('../../../demo/server');
|
||||||
|
const config = require('../../../demo/payload.config');
|
||||||
|
const { email, password } = require('../../tests/credentials');
|
||||||
|
|
||||||
describe('API', () => {
|
const url = config.serverURL;
|
||||||
const url = 'http://localhost:3000';
|
|
||||||
let token = null;
|
|
||||||
const email = 'test@test.com';
|
|
||||||
const password = 'test123';
|
|
||||||
|
|
||||||
let localizedPostID;
|
let token = null;
|
||||||
const englishPostDesc = faker.lorem.lines(20);
|
|
||||||
const spanishPostDesc = faker.lorem.lines(20);
|
|
||||||
|
|
||||||
beforeAll((done) => {
|
let localizedPostID;
|
||||||
server.start(done);
|
const englishPostDesc = faker.lorem.lines(20);
|
||||||
});
|
const spanishPostDesc = faker.lorem.lines(20);
|
||||||
|
|
||||||
it('should register a first user', async () => {
|
describe('Collection CRUD', () => {
|
||||||
const response = await fetch(`${url}/api/first-register`, {
|
beforeAll(async () => {
|
||||||
body: JSON.stringify({
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
method: 'post',
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(201);
|
|
||||||
expect(data).toHaveProperty('email');
|
|
||||||
expect(data).toHaveProperty('role');
|
|
||||||
expect(data).toHaveProperty('createdAt');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should login a user successfully', async () => {
|
|
||||||
const response = await fetch(`${url}/api/login`, {
|
const response = await fetch(`${url}/api/login`, {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email,
|
email,
|
||||||
@@ -53,33 +30,9 @@ describe('API', () => {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(data.token).not.toBeNull();
|
|
||||||
|
|
||||||
({ token } = data);
|
({ token } = data);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow a user to be created', async () => {
|
|
||||||
const response = await fetch(`${url}/api/users/register`, {
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: `${faker.name.firstName()}@test.com`,
|
|
||||||
password,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
Authorization: `JWT ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
method: 'post',
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(201);
|
|
||||||
expect(data).toHaveProperty('email');
|
|
||||||
expect(data).toHaveProperty('role');
|
|
||||||
expect(data).toHaveProperty('createdAt');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow a post to be created in English', async () => {
|
it('should allow a post to be created in English', async () => {
|
||||||
const response = await fetch(`${url}/api/posts`, {
|
const response = await fetch(`${url}/api/posts`, {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -23,6 +23,7 @@ const updateHandler = async (req, res) => {
|
|||||||
doc,
|
doc,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json(formatErrorResponse(err, 'mongoose'));
|
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json(formatErrorResponse(err, 'mongoose'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
2
src/tests/credentials.js
Normal file
2
src/tests/credentials.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
exports.email = 'test@test.com';
|
||||||
|
exports.password = 'test123';
|
||||||
26
src/tests/globalSetup.js
Normal file
26
src/tests/globalSetup.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
const server = require('../../demo/server');
|
||||||
|
const config = require('../../demo/payload.config');
|
||||||
|
const { email, password } = require('./credentials');
|
||||||
|
|
||||||
|
const url = config.serverURL;
|
||||||
|
const usernameField = config.user.auth.useAsUsername;
|
||||||
|
|
||||||
|
const globalSetup = async () => {
|
||||||
|
global.PAYLOAD_SERVER = await server.start();
|
||||||
|
|
||||||
|
const response = await fetch(`${url}/api/first-register`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
[usernameField]: email,
|
||||||
|
password,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method: 'post',
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
global.AUTH_TOKEN = data.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = globalSetup;
|
||||||
6
src/tests/globalTeardown.js
Normal file
6
src/tests/globalTeardown.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const globalTeardown = async () => {
|
||||||
|
const serverClosePromise = new Promise(resolve => global.PAYLOAD_SERVER.close(resolve));
|
||||||
|
await serverClosePromise;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = globalTeardown;
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
const defaults = require('./jest.config');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
...defaults,
|
|
||||||
roots: [
|
|
||||||
'./integration'
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
verbose: true,
|
|
||||||
testURL: 'http://localhost/',
|
|
||||||
roots: [
|
|
||||||
'./unit'
|
|
||||||
],
|
|
||||||
transform: {
|
|
||||||
'^.+\\.(j|t)s$': 'babel-jest'
|
|
||||||
},
|
|
||||||
testEnvironment: 'node',
|
|
||||||
};
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
const mockExpress = require('jest-mock-express');
|
|
||||||
const localizationMiddleware = require('../../localization/middleware');
|
|
||||||
|
|
||||||
let res = null;
|
|
||||||
let next = null;
|
|
||||||
describe('Payload Middleware', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
res = mockExpress.response();
|
|
||||||
next = jest.fn();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Payload Locale Middleware', () => {
|
|
||||||
let req, localization;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
req = {
|
|
||||||
query: {},
|
|
||||||
headers: {},
|
|
||||||
body: {}
|
|
||||||
};
|
|
||||||
localization = {
|
|
||||||
locales: ['en', 'es'],
|
|
||||||
defaultLocale: 'en'
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Supports query params', () => {
|
|
||||||
req.query.locale = 'es';
|
|
||||||
|
|
||||||
localizationMiddleware(localization)(req, res, next);
|
|
||||||
|
|
||||||
expect(next).toHaveBeenCalledTimes(1);
|
|
||||||
expect(req.locale).toEqual(req.query.locale);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Supports query param fallback to default', () => {
|
|
||||||
req.query.locale = 'pt';
|
|
||||||
|
|
||||||
localizationMiddleware(localization)(req, res, next);
|
|
||||||
|
|
||||||
expect(next).toHaveBeenCalledTimes(1);
|
|
||||||
expect(req.locale).toEqual(localization.defaultLocale);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Supports accept-language header', () => {
|
|
||||||
req.headers['accept-language'] = 'es,fr;';
|
|
||||||
|
|
||||||
localizationMiddleware(localization)(req, res, next);
|
|
||||||
|
|
||||||
expect(next).toHaveBeenCalledTimes(1);
|
|
||||||
expect(req.locale).toEqual('es');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Supports accept-language header fallback', () => {
|
|
||||||
req.query.locale = 'pt';
|
|
||||||
req.headers['accept-language'] = 'fr;';
|
|
||||||
|
|
||||||
localizationMiddleware(localization)(req, res, next);
|
|
||||||
|
|
||||||
expect(next).toHaveBeenCalledTimes(1);
|
|
||||||
expect(req.locale).toEqual(localization.defaultLocale);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Query param takes precedence over header', () => {
|
|
||||||
req.query.locale = 'es';
|
|
||||||
req.headers['accept-language'] = 'en;';
|
|
||||||
|
|
||||||
localizationMiddleware(localization)(req, res, next);
|
|
||||||
|
|
||||||
expect(next).toHaveBeenCalledTimes(1);
|
|
||||||
expect(req.locale).toEqual('es');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Supports default locale', () => {
|
|
||||||
localizationMiddleware(localization)(req, res, next);
|
|
||||||
|
|
||||||
expect(next).toHaveBeenCalledTimes(1);
|
|
||||||
expect(req.locale).toEqual(localization.defaultLocale);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Supports locale all', () => {
|
|
||||||
req.query.locale = '*';
|
|
||||||
localizationMiddleware(localization)(req, res, next);
|
|
||||||
|
|
||||||
expect(next).toHaveBeenCalledTimes(1);
|
|
||||||
expect(req.locale).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Supports locale in body on post', () => {
|
|
||||||
req.body = {locale: 'es'};
|
|
||||||
req.method = 'post';
|
|
||||||
localizationMiddleware(localization)(req, res, next);
|
|
||||||
|
|
||||||
expect(next).toHaveBeenCalledTimes(1);
|
|
||||||
expect(req.locale).toEqual('es');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import mongoose from 'mongoose';
|
|
||||||
const Schema = mongoose.Schema;
|
|
||||||
import mongooseApiQuery from '../../mongoose/buildQuery.plugin';
|
|
||||||
|
|
||||||
const TestUserSchema = new Schema({
|
|
||||||
name: {type: String},
|
|
||||||
description: {type: String},
|
|
||||||
married: {type: Boolean},
|
|
||||||
age: {type: Number}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
TestUserSchema.plugin(mongooseApiQuery);
|
|
||||||
const TestUser = mongoose.model('TestUser', TestUserSchema);
|
|
||||||
|
|
||||||
describe('mongooseApiQuery', () => {
|
|
||||||
|
|
||||||
it('Should not blow up', () => {
|
|
||||||
expect(mongooseApiQuery).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import SchemaLoader from '../../mongoose/schema/schemaLoader';
|
|
||||||
import config from '../../../demo/payload.config';
|
|
||||||
|
|
||||||
let schemaLoader;
|
|
||||||
|
|
||||||
describe('schemaLoader', () => {
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
schemaLoader = new SchemaLoader(config);
|
|
||||||
console.log('before done');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('load collections', async () => {
|
|
||||||
expect(schemaLoader.collections).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('load globals', async () => {
|
|
||||||
expect(schemaLoader.globalModel).not.toBeNull();
|
|
||||||
expect(schemaLoader.globals).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('load blocks', async () => {
|
|
||||||
expect(schemaLoader.blockSchema).not.toBeNull();
|
|
||||||
expect(schemaLoader.contentBlocks).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
/* eslint-disable camelcase */
|
|
||||||
import mongoose from 'mongoose';
|
|
||||||
|
|
||||||
const Schema = mongoose.Schema;
|
|
||||||
import { intlModel } from './testModels/IntlModel';
|
|
||||||
import { paramParser } from '../../mongoose/buildQuery.plugin';
|
|
||||||
|
|
||||||
const AuthorSchema = new Schema({
|
|
||||||
name: String,
|
|
||||||
publish_count: Number
|
|
||||||
});
|
|
||||||
|
|
||||||
const PageSchema = new Schema({
|
|
||||||
title: { type: String, unique: true },
|
|
||||||
author: AuthorSchema,
|
|
||||||
content: { type: String },
|
|
||||||
metaTitle: String,
|
|
||||||
likes: { type: Number }
|
|
||||||
});
|
|
||||||
const Page = mongoose.model('Page', PageSchema);
|
|
||||||
|
|
||||||
describe('Param Parser', () => {
|
|
||||||
|
|
||||||
describe('Parameter Parsing', () => {
|
|
||||||
it('No params', () => {
|
|
||||||
let parsed = paramParser(Page, {});
|
|
||||||
expect(parsed.searchParams).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Property Equals - Object property', () => {
|
|
||||||
let parsed = paramParser(Page, { title: 'This is my title' });
|
|
||||||
expect(parsed.searchParams).toEqual({ title: 'This is my title' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Property Equals - String property', () => {
|
|
||||||
let parsed = paramParser(Page, { metaTitle: 'This is my title' });
|
|
||||||
expect(parsed.searchParams).toEqual({ metaTitle: 'This is my title' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Multiple params', () => {
|
|
||||||
let parsed = paramParser(Page, { title: 'This is my title', metaTitle: 'this-is-my-title' });
|
|
||||||
expect(parsed.searchParams).toEqual({ title: 'This is my title', metaTitle: 'this-is-my-title' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Greater than or equal', () => {
|
|
||||||
let parsed = paramParser(Page, { likes: '{gte}50' });
|
|
||||||
expect(parsed.searchParams).toEqual({ likes: { '$gte': '50' } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Greater than, less than', () => {
|
|
||||||
let parsed = paramParser(Page, { likes: '{gte}50{lt}100' });
|
|
||||||
expect(parsed.searchParams).toEqual({ likes: { '$gte': '50', '$lt': '100' } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Like', () => {
|
|
||||||
let parsed = paramParser(Page, { title: '{like}This' });
|
|
||||||
expect(parsed.searchParams).toEqual({ title: { '$regex': 'This', '$options': '-i' } });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('SubSchemas', () => {
|
|
||||||
it('Parse subschema for String', () => {
|
|
||||||
let parsed = paramParser(Page, { 'author.name': 'Jane' });
|
|
||||||
expect(parsed.searchParams).toEqual({ 'author.name': 'Jane' })
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Parse subschema for Number', () => {
|
|
||||||
let parsed = paramParser(Page, { 'author.publish_count': '7' });
|
|
||||||
expect(parsed.searchParams).toEqual({ 'author.publish_count': '7' })
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Locale handling', () => {
|
|
||||||
it('should handle intl string property', () => {
|
|
||||||
let parsed = paramParser(intlModel, { title: 'This is my title' }, 'en');
|
|
||||||
expect(parsed.searchParams).toEqual({ 'title.en': 'This is my title'});
|
|
||||||
});
|
|
||||||
it('should handle intl string property', () => {
|
|
||||||
let parsed = paramParser(intlModel, { title: 'This is my title' }, 'en');
|
|
||||||
expect(parsed.searchParams).toEqual({ 'title.en': 'This is my title'});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Include', () => {
|
|
||||||
it('Include Single', () => {
|
|
||||||
let parsed = paramParser(Page, { include: 'SomeId' });
|
|
||||||
expect(parsed.searchParams).toEqual({ _id: 'SomeId' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Include Multiple', () => {
|
|
||||||
let parsed = paramParser(Page, { include: 'SomeId,SomeSecondId' });
|
|
||||||
expect(parsed.searchParams)
|
|
||||||
.toEqual({ '$or': [{ _id: 'SomeId' }, { _id: 'SomeSecondId' }] });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Exclude', () => {
|
|
||||||
it('Exclude Single', () => {
|
|
||||||
let parsed = paramParser(Page, { exclude: 'SomeId' });
|
|
||||||
expect(parsed.searchParams).toEqual({ _id: { '$ne': 'SomeId' } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Exclude Multiple', () => {
|
|
||||||
let parsed = paramParser(Page, { exclude: 'SomeId,SomeSecondId' });
|
|
||||||
expect(parsed.searchParams)
|
|
||||||
.toEqual({ '$and': [{ _id: { '$ne': 'SomeId' } }, { _id: { '$ne': 'SomeSecondId' } }] });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Ordering/Sorting', () => {
|
|
||||||
it('Order by ascending (default)', () => {
|
|
||||||
let parsed = paramParser(Page, { sort_by: 'title' });
|
|
||||||
expect(parsed).toEqual({ searchParams: {}, sort: { title: 1 } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Order by ascending', () => {
|
|
||||||
let parsed = paramParser(Page, { sort_by: 'title,asc' });
|
|
||||||
expect(parsed).toEqual({ searchParams: {}, sort: { title: 1 } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Order by descending', () => {
|
|
||||||
let parsed = paramParser(Page, { sort_by: 'title,desc' });
|
|
||||||
expect(parsed).toEqual({ searchParams: {}, sort: { title: 'desc' } });
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import mongoose from 'mongoose';
|
|
||||||
import { schemaBaseFields } from '../../../mongoose/schema/schemaBaseFields';
|
|
||||||
import paginate from '../../../mongoose/paginate.plugin';
|
|
||||||
import buildQueryPlugin from '../../../mongoose/buildQuery.plugin';
|
|
||||||
import localizationPlugin from '../../../localization/localization.plugin';
|
|
||||||
|
|
||||||
const IntlSchema = new mongoose.Schema({
|
|
||||||
...schemaBaseFields,
|
|
||||||
title: { type: String, localized: true, unique: true },
|
|
||||||
content: { type: String, localized: true },
|
|
||||||
metaTitle: String,
|
|
||||||
metaDesc: String
|
|
||||||
},
|
|
||||||
{ timestamps: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
IntlSchema.plugin(paginate);
|
|
||||||
IntlSchema.plugin(buildQueryPlugin);
|
|
||||||
IntlSchema.plugin(localizationPlugin, {
|
|
||||||
locales: [
|
|
||||||
'en',
|
|
||||||
'es'
|
|
||||||
],
|
|
||||||
defaultLocale: 'en',
|
|
||||||
fallback: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const intlModel = mongoose.model('IntlModel', IntlSchema);
|
|
||||||
export { intlModel };
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import express from 'express';
|
|
||||||
|
|
||||||
export function getConfiguredExpress() {
|
|
||||||
let expressApp = express();
|
|
||||||
expressApp.set('view engine', 'pug');
|
|
||||||
return expressApp;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
describe('troubleshooting', () => {
|
|
||||||
describe('plugins', () => {
|
|
||||||
it('should return all records', () => {
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
const checkIfInitialized = require('./checkIfInitialized');
|
const checkIfInitialized = require('./checkIfInitialized');
|
||||||
const login = require('./login');
|
const login = require('./login');
|
||||||
const me = require('./me');
|
|
||||||
const refresh = require('./refresh');
|
const refresh = require('./refresh');
|
||||||
const register = require('./register');
|
const register = require('./register');
|
||||||
const init = require('./init');
|
const init = require('./init');
|
||||||
@@ -10,7 +9,6 @@ const resetPassword = require('./resetPassword');
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
checkIfInitialized,
|
checkIfInitialized,
|
||||||
login,
|
login,
|
||||||
me,
|
|
||||||
refresh,
|
refresh,
|
||||||
init,
|
init,
|
||||||
register,
|
register,
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
|
const refresh = async (args) => {
|
||||||
|
try {
|
||||||
|
// Await validation here
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
config: args.config,
|
||||||
|
api: args.api,
|
||||||
|
authorization: args.authorization,
|
||||||
|
};
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// 1. Execute before refresh hook
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
const beforeRefreshHook = args.config.user && args.config.user.hooks && args.config.user.hooks.beforeRefresh;
|
||||||
|
|
||||||
|
if (typeof beforeRefreshHook === 'function') {
|
||||||
|
options = await beforeRefreshHook(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// 2. Perform refresh
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
const secret = options.config.user.auth.secretKey;
|
||||||
|
const opts = {};
|
||||||
|
opts.expiresIn = options.config.user.auth.tokenExpiration;
|
||||||
|
|
||||||
|
const token = options.authorization.replace('JWT ', '');
|
||||||
|
jwt.verify(token, secret, {});
|
||||||
|
const refreshedToken = jwt.sign(token, secret);
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// 3. Execute after login hook
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
const afterRefreshHook = args.config.user && args.config.user.hooks && args.config.user.hooks.afterRefresh;
|
||||||
|
|
||||||
|
if (typeof afterRefreshHook === 'function') {
|
||||||
|
await afterRefreshHook(options, refreshedToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// 4. Return refreshed token
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
return refreshedToken;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = refresh;
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
/**
|
const meHandler = async (req, res) => {
|
||||||
* Returns User if user session is still open
|
return res.status(200).json(req.user);
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
const me = (req, res) => {
|
|
||||||
return res.status(200).send(req.user);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = me;
|
module.exports = meHandler;
|
||||||
|
|||||||
@@ -1,35 +1,22 @@
|
|||||||
const jwt = require('jsonwebtoken');
|
|
||||||
const httpStatus = require('http-status');
|
const httpStatus = require('http-status');
|
||||||
const { Forbidden, APIError } = require('../../errors');
|
|
||||||
const formatErrorResponse = require('../../express/responses/formatError');
|
const formatErrorResponse = require('../../express/responses/formatError');
|
||||||
|
const { refresh } = require('../operations');
|
||||||
|
|
||||||
/**
|
const refreshHandler = config => async (req, res) => {
|
||||||
* Refresh an expired or soon to be expired auth token
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @param next
|
|
||||||
*/
|
|
||||||
const refresh = config => (req, res, next) => {
|
|
||||||
const secret = config.user.auth.secretKey;
|
|
||||||
const opts = {};
|
|
||||||
opts.expiresIn = config.user.auth.tokenExpiration;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = req.headers.authorization.replace('JWT ', '');
|
const refreshedToken = await refresh({
|
||||||
jwt.verify(token, secret, {});
|
config,
|
||||||
const refreshedToken = jwt.sign(token, secret);
|
api: 'REST',
|
||||||
res.status(200)
|
authorization: req.headers.authorization,
|
||||||
.json({
|
});
|
||||||
message: 'Token Refresh Successful',
|
|
||||||
refreshedToken,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e.status && e.status === 401) {
|
|
||||||
return res.status(httpStatus.FORBIDDEN).send(formatErrorResponse(new Forbidden()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(httpStatus.INTERNAL_SERVER_ERROR).send(formatErrorResponse(new APIError()));
|
res.status(200).json({
|
||||||
|
message: 'Token refresh successful',
|
||||||
|
refreshedToken,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(error.status || httpStatus.INTERNAL_SERVER_ERROR).json(formatErrorResponse(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = refresh;
|
module.exports = refreshHandler;
|
||||||
|
|||||||
88
src/users/users.spec.js
Normal file
88
src/users/users.spec.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
require('isomorphic-fetch');
|
||||||
|
const faker = require('faker');
|
||||||
|
const { email, password } = require('../tests/credentials');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @jest-environment node
|
||||||
|
*/
|
||||||
|
|
||||||
|
const config = require('../../demo/payload.config');
|
||||||
|
|
||||||
|
describe('Users REST API', () => {
|
||||||
|
const url = config.serverURL;
|
||||||
|
const usernameField = config.user.auth.useAsUsername;
|
||||||
|
|
||||||
|
let token = null;
|
||||||
|
|
||||||
|
it('should prevent registering a first user', async () => {
|
||||||
|
const response = await fetch(`${url}/api/first-register`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
[usernameField]: 'thisuser@shouldbeprevented.com',
|
||||||
|
password: 'get-out',
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method: 'post',
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
expect(response.status).toBe(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should login a user successfully', async () => {
|
||||||
|
const response = await fetch(`${url}/api/login`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
[usernameField]: email,
|
||||||
|
password,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method: 'post',
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(data.token).not.toBeNull();
|
||||||
|
|
||||||
|
({ token } = data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a logged in user from /me', async () => {
|
||||||
|
const response = await fetch(`${url}/api/me`, {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
Authorization: `JWT ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(data[usernameField]).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow a user to be created', async () => {
|
||||||
|
const response = await fetch(`${url}/api/users/register`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
[usernameField]: `${faker.name.firstName()}@test.com`,
|
||||||
|
password,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: `JWT ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method: 'post',
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
expect(response.status).toBe(201);
|
||||||
|
expect(data).toHaveProperty(usernameField);
|
||||||
|
expect(data).toHaveProperty('role');
|
||||||
|
expect(data).toHaveProperty('createdAt');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
@ECHO OFF
|
|
||||||
|
|
||||||
sc query "MongoDB" | findstr "RUNNING"
|
|
||||||
if errorlevel 1 (
|
|
||||||
net start "MongoDB Server"
|
|
||||||
) else (
|
|
||||||
net stop "MongoDB Server"
|
|
||||||
)
|
|
||||||
Reference in New Issue
Block a user