mongodb-dynamic-api
Version:
Auto generated CRUD API for MongoDB using NestJS
914 lines • 91.8 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = require("@nestjs/common");
const jwt_1 = require("@nestjs/jwt");
const mongoose_1 = require("@nestjs/mongoose");
const testing_1 = require("@nestjs/testing");
const class_validator_1 = require("class-validator");
const mongoose_2 = require("mongoose");
const src_1 = require("../src");
const socket_adapter_1 = require("../src/adapters/socket-adapter");
const e2e_setup_1 = require("./e2e.setup");
require("dotenv/config");
const utils_1 = require("./utils");
describe('DynamicApiModule forRoot (e2e)', () => {
const uri = process.env.MONGO_DB_URL;
const initModule = async (dynamicApiForRootOptions, initFixtures, initMainCb, testGateway) => {
const moduleRef = await testing_1.Test.createTestingModule({
imports: [src_1.DynamicApiModule.forRoot(uri, dynamicApiForRootOptions)],
providers: testGateway ? [e2e_setup_1.TestGateway] : [],
}).compile();
return (0, e2e_setup_1.createTestingApp)(moduleRef, initFixtures, initMainCb);
};
beforeEach(() => {
src_1.DynamicApiModule.state['resetState']();
});
afterEach(async () => {
await (0, e2e_setup_1.closeTestingApp)(mongoose_2.default.connections);
});
it('should initialize dynamic api module state with default options', async () => {
const app = await initModule({});
expect(app).toBeDefined();
expect(src_1.DynamicApiModule.state.get()).toStrictEqual({
uri,
initialized: true,
isGlobalCacheEnabled: true,
connectionName: 'dynamic-api-connection',
cacheExcludedPaths: [],
credentials: null,
isAuthEnabled: false,
jwtExpirationTime: undefined,
jwtSecret: undefined,
routesConfig: {
defaults: [
'GetMany',
'GetOne',
'CreateMany',
'CreateOne',
'UpdateMany',
'UpdateOne',
'ReplaceOne',
'DuplicateMany',
'DuplicateOne',
'DeleteMany',
'DeleteOne',
],
excluded: [],
},
gatewayOptions: undefined,
});
});
it('should initialize dynamic api module state with custom options', async () => {
const app = await initModule({
useGlobalCache: false,
cacheOptions: {
excludePaths: ['/fake-path'],
},
routesConfig: {
defaults: ['GetMany', 'GetOne', 'CreateOne', 'UpdateOne', 'DeleteOne'],
excluded: ['CreateMany', 'UpdateMany', 'DeleteMany'],
},
});
expect(app).toBeDefined();
expect(src_1.DynamicApiModule.state.get()).toStrictEqual({
uri,
initialized: true,
isGlobalCacheEnabled: false,
connectionName: 'dynamic-api-connection',
cacheExcludedPaths: ['/fake-path'],
credentials: null,
isAuthEnabled: false,
jwtExpirationTime: undefined,
jwtSecret: undefined,
routesConfig: {
defaults: ['GetMany', 'GetOne', 'CreateOne', 'UpdateOne', 'DeleteOne'],
excluded: ['CreateMany', 'UpdateMany', 'DeleteMany'],
},
gatewayOptions: undefined,
});
});
describe('Authentication API', () => {
describe('useAuth when only userEntity is provided', () => {
let UserEntity = class UserEntity extends src_1.BaseEntity {
};
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], UserEntity.prototype, "email", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], UserEntity.prototype, "password", void 0);
UserEntity = __decorate([
(0, mongoose_1.Schema)({ collection: 'users' })
], UserEntity);
let app;
beforeEach(async () => {
app = await initModule({ useAuth: { userEntity: UserEntity } });
});
it('should initialize dynamic api module state and authentication API with default options', async () => {
expect(app).toBeDefined();
expect(src_1.DynamicApiModule.state.get()).toStrictEqual({
uri,
initialized: true,
isGlobalCacheEnabled: true,
connectionName: 'dynamic-api-connection',
cacheExcludedPaths: [],
credentials: {
loginField: 'email',
passwordField: 'password',
},
isAuthEnabled: true,
jwtExpirationTime: '1d',
jwtSecret: 'dynamic-api-jwt-secret',
routesConfig: {
defaults: [
'GetMany',
'GetOne',
'CreateMany',
'CreateOne',
'UpdateMany',
'UpdateOne',
'ReplaceOne',
'DuplicateMany',
'DuplicateOne',
'DeleteMany',
'DeleteOne',
],
excluded: [],
},
gatewayOptions: undefined,
});
});
describe('POST /auth/register', () => {
it('should throw a bad request exception if email is missing', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/register', { username: 'unit-test', password: 'test-2' });
expect(status).toBe(400);
expect(body).toEqual({
error: 'Bad Request',
message: ['email property is required'],
statusCode: 400,
});
});
it('should throw a bad request exception if password is missing', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', pass: 'test-2' });
expect(status).toBe(400);
expect(body).toEqual({
error: 'Bad Request',
message: ['password property is required'],
statusCode: 400,
});
});
it('should create a new user and return access token', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'test' });
expect(status).toBe(201);
expect(body).toEqual({ accessToken: expect.any(String) });
});
});
describe('POST /auth/login', () => {
it('should throw an unauthorized exception if email is missing', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/login', { pass: 'test-2' });
expect(status).toBe(401);
expect(body).toEqual({
message: 'Unauthorized',
statusCode: 401,
});
});
it('should throw an unauthorized exception if password is missing', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/login', { email: 'unit@test.co' });
expect(status).toBe(401);
expect(body).toEqual({
message: 'Unauthorized',
statusCode: 401,
});
});
it('should return access token', async () => {
await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'test' });
const { body, status } = await e2e_setup_1.server.post('/auth/login', { email: 'unit@test.co', password: 'test' });
expect(status).toBe(200);
expect(body).toEqual({ accessToken: expect.any(String) });
});
});
describe('GET /auth/account', () => {
it('should throw an unauthorized exception if access token is missing', async () => {
const { body, status } = await e2e_setup_1.server.get('/auth/account');
expect(status).toBe(401);
expect(body).toEqual({
message: 'Unauthorized',
statusCode: 401,
});
});
it('should return user account', async () => {
await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'test' });
const { body: { accessToken } } = await e2e_setup_1.server.post('/auth/login', { email: 'unit@test.co', password: 'test' });
const headers = { Authorization: `Bearer ${accessToken}` };
const { body: account, status: accountStatus } = await e2e_setup_1.server.get('/auth/account', { headers });
expect(accountStatus).toBe(200);
expect(account).toEqual({ id: expect.any(String), email: 'unit@test.co' });
});
});
it('should throw a Service Unavailable exception when requesting reset password endpoint if reset password options are not configured', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/reset-password', { email: 'toto@test.co' });
expect(status).toBe(503);
expect(body).toEqual({
error: 'Service Unavailable',
message: 'This feature is not available',
statusCode: 503,
});
});
it('should throw a Service Unavailable exception when requesting change password endpoint if reset password options are not configured', async () => {
const { body, status } = await e2e_setup_1.server.patch('/auth/change-password', { newPassword: 'test' });
expect(status).toBe(503);
expect(body).toEqual({
error: 'Service Unavailable',
message: 'This feature is not available',
statusCode: 503,
});
});
});
describe('useAuth with jwt options', () => {
let jwtService;
let token;
let app;
let UserEntity = class UserEntity extends src_1.BaseEntity {
};
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], UserEntity.prototype, "email", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], UserEntity.prototype, "password", void 0);
UserEntity = __decorate([
(0, mongoose_1.Schema)({ collection: 'users' })
], UserEntity);
beforeEach(async () => {
app = await initModule({
useAuth: {
userEntity: UserEntity,
jwt: {
secret: 'test-secret',
expiresIn: '4s',
},
},
});
jwtService = app.get(jwt_1.JwtService);
const { body: { accessToken } } = await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'test' });
token = accessToken;
});
it('should initialize dynamic api module state and authentication API with jwt options', async () => {
expect(app).toBeDefined();
expect(src_1.DynamicApiModule.state.get()).toStrictEqual({
uri,
initialized: true,
isGlobalCacheEnabled: true,
connectionName: 'dynamic-api-connection',
cacheExcludedPaths: [],
credentials: {
loginField: 'email',
passwordField: 'password',
},
isAuthEnabled: true,
jwtExpirationTime: '4s',
jwtSecret: 'test-secret',
routesConfig: {
defaults: [
'GetMany',
'GetOne',
'CreateMany',
'CreateOne',
'UpdateMany',
'UpdateOne',
'ReplaceOne',
'DuplicateMany',
'DuplicateOne',
'DeleteMany',
'DeleteOne',
],
excluded: [],
},
gatewayOptions: undefined,
});
});
it('should throw an unauthorized exception if access token is expired', async () => {
await (0, utils_1.wait)(5000);
const headers = { Authorization: `Bearer ${token}` };
const { body, status } = await e2e_setup_1.server.get('/auth/account', { headers });
expect(status).toBe(401);
expect(body).toEqual({
message: 'Unauthorized',
statusCode: 401,
});
}, 6000);
it('should throw an unauthorized exception if secret is invalid', async () => {
const invalidToken = jwtService.sign({ email: 'u', password: 'p' }, { secret: 'invalid-secret' });
const headers = { Authorization: `Bearer ${invalidToken}` };
const { body, status } = await e2e_setup_1.server.get('/auth/account', { headers });
expect(status).toBe(401);
expect(body).toEqual({
message: 'Unauthorized',
statusCode: 401,
});
});
});
describe('useAuth with validation options', () => {
let app;
let UserEntity = class UserEntity extends src_1.BaseEntity {
};
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
(0, class_validator_1.IsEmail)(),
__metadata("design:type", String)
], UserEntity.prototype, "email", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
(0, class_validator_1.IsStrongPassword)({
minLength: 6,
minLowercase: 1,
minUppercase: 1,
minNumbers: 1,
minSymbols: 1,
}),
__metadata("design:type", String)
], UserEntity.prototype, "password", void 0);
UserEntity = __decorate([
(0, mongoose_1.Schema)({ collection: 'users' })
], UserEntity);
beforeEach(async () => {
app = await initModule({
useAuth: {
userEntity: UserEntity,
validationPipeOptions: {
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
},
},
});
});
describe('POST /auth/register', () => {
it('should throw a bad request exception if payload contains non whitelisted property', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'Test-2', role: 'ADMIN' });
expect(status).toBe(400);
expect(body).toEqual({
error: 'Bad Request',
message: ['property role should not exist'],
statusCode: 400,
});
});
it('should throw a bad request exception if validation fails', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/register', { email: 'unit.test.co', password: 'test-2' });
expect(status).toBe(400);
expect(body).toEqual({
error: 'Bad Request',
message: [
'email must be an email',
'password is not strong enough',
],
statusCode: 400,
});
});
it('should create a new user and return access token if the validation was successful', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'Test-2' });
expect(status).toBe(201);
expect(body).toEqual({ accessToken: expect.any(String) });
});
});
describe('POST /auth/login', () => {
beforeEach(async () => {
await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'Test-2' });
});
it('should throw an unauthorized exception if payload contains non whitelisted property', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/login', { email: 'unit@test.co', password: 'Test-2', role: 'ADMIN' });
expect(status).toBe(400);
expect(body).toEqual({
error: 'Bad Request',
message: ['property role should not exist'],
statusCode: 400,
});
});
it('should throw an unauthorized exception if email is missing', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/login', { password: 'Test-2' });
expect(status).toBe(401);
expect(body).toEqual({
message: 'Unauthorized',
statusCode: 401,
});
});
it('should throw an unauthorized exception if password is missing', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/login', { email: 'unit@test.co' });
expect(status).toBe(401);
expect(body).toEqual({
message: 'Unauthorized',
statusCode: 401,
});
});
it('should return access token if the validation was successful', async () => {
await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'test' });
const { body, status } = await e2e_setup_1.server.post('/auth/login', { email: 'unit@test.co', password: 'Test-2' });
expect(status).toBe(200);
expect(body).toEqual({ accessToken: expect.any(String) });
});
});
});
describe('POST /auth/register with register options', () => {
let User = class User extends src_1.BaseEntity {
constructor() {
super(...arguments);
this.role = 'user';
}
};
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], User.prototype, "email", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], User.prototype, "password", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: String, default: 'user' }),
__metadata("design:type", String)
], User.prototype, "role", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: Boolean, default: false }),
__metadata("design:type", Boolean)
], User.prototype, "isVerified", void 0);
User = __decorate([
(0, mongoose_1.Schema)({ collection: 'users' })
], User);
const admin = { email: 'admin@test.co', password: 'admin', role: 'admin', isVerified: true };
const user = { email: 'user@test.co', password: 'user' };
beforeEach(async () => {
const bcryptService = new src_1.BcryptService();
const fixtures = async (_) => {
const model = await (0, utils_1.getModelFromEntity)(User);
await model.insertMany([
{ ...admin, password: await bcryptService.hashPassword(admin.password) },
{ ...user, password: await bcryptService.hashPassword(user.password) },
]);
};
await initModule({
useAuth: {
userEntity: User,
register: {
protected: true,
abilityPredicate: (user) => user.isVerified,
additionalFields: ['role'],
callback: async (user, { updateOneDocument }) => {
if (user.role !== 'admin') {
return;
}
await updateOneDocument(User, { _id: user.id }, { $set: { isVerified: true } });
},
},
login: {
additionalFields: ['role', 'isVerified'],
},
},
}, fixtures);
});
describe('protected', () => {
it('should throw an unauthorized exception if user is not logged in and protected is true', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'test' });
expect(status).toBe(401);
expect(body).toEqual({
message: 'Unauthorized',
statusCode: 401,
});
});
});
describe('abilityPredicate', () => {
it('should not create a new user if user is not verified', async () => {
const { email, password } = user;
const { body: { accessToken } } = await e2e_setup_1.server.post('/auth/login', { email, password });
const { body, status } = await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'test' }, {
headers: { Authorization: `Bearer ${accessToken}` },
});
expect(status).toBe(403);
expect(body).toEqual({
error: 'Forbidden',
message: 'Access denied',
statusCode: 403,
});
});
it('should create a new user and return access token if user is verified', async () => {
const { email, password } = admin;
const { body: { accessToken } } = await e2e_setup_1.server.post('/auth/login', { email, password });
const { body, status } = await e2e_setup_1.server.post('/auth/register', { email: 'unit@test.co', password: 'test' }, {
headers: { Authorization: `Bearer ${accessToken}` },
});
expect(status).toBe(201);
expect(body).toEqual({ accessToken: expect.any(String) });
});
});
describe('additionalFields', () => {
it('should allow to register a new user with additional fields', async () => {
const { email, password } = admin;
const { body: { accessToken } } = await e2e_setup_1.server.post('/auth/login', { email, password });
const { body, status } = await e2e_setup_1.server.post('/auth/register', { email: 'client@test.co', password: 'client', role: 'client' }, {
headers: { Authorization: `Bearer ${accessToken}` },
});
expect(status).toBe(201);
expect(body).toEqual({ accessToken: expect.any(String) });
});
});
describe('callback', () => {
it('should not set isVerified to true if role is not admin', async () => {
const { email, password } = admin;
const { body: loginBody } = await e2e_setup_1.server.post('/auth/login', { email, password });
const { body: { accessToken } } = await e2e_setup_1.server.post('/auth/register', { email: 'client@test.co', password: 'client', role: 'client' }, {
headers: { Authorization: `Bearer ${loginBody.accessToken}` },
});
const { body, status } = await e2e_setup_1.server.get('/auth/account', { headers: { Authorization: `Bearer ${accessToken}` } });
expect(status).toBe(200);
expect(body).toHaveProperty('isVerified', false);
});
it('should set isVerified to true if role is admin', async () => {
const { email, password } = admin;
const { body: loginBody } = await e2e_setup_1.server.post('/auth/login', { email, password });
const { body: { accessToken } } = await e2e_setup_1.server.post('/auth/register', { email: 'admin2@test.co', password: 'admin2', role: 'admin' }, {
headers: { Authorization: `Bearer ${loginBody.accessToken}` },
});
const { body, status } = await e2e_setup_1.server.get('/auth/account', { headers: { Authorization: `Bearer ${accessToken}` } });
expect(status).toBe(200);
expect(body).toHaveProperty('isVerified', true);
});
});
});
describe('POST /auth/login with login options', () => {
let User = class User extends src_1.BaseEntity {
constructor() {
super(...arguments);
this.role = 'user';
}
};
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], User.prototype, "username", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], User.prototype, "pass", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: String, default: 'user' }),
__metadata("design:type", String)
], User.prototype, "role", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: Boolean, default: false }),
__metadata("design:type", Boolean)
], User.prototype, "isVerified", void 0);
User = __decorate([
(0, mongoose_1.Schema)({ collection: 'users' })
], User);
const admin = { username: 'admin', pass: 'admin', role: 'admin', isVerified: true };
const user = { username: 'user', pass: 'user' };
const client = { username: 'client', pass: 'client', role: 'client', isVerified: true };
beforeEach(async () => {
const bcryptService = new src_1.BcryptService();
const fixtures = async (_) => {
const model = await (0, utils_1.getModelFromEntity)(User);
await model.insertMany([
{ ...admin, pass: await bcryptService.hashPassword(admin.pass) },
{ ...user, pass: await bcryptService.hashPassword(user.pass) },
{ ...client, pass: await bcryptService.hashPassword(client.pass) },
]);
};
await initModule({
useAuth: {
userEntity: User,
login: {
loginField: 'username',
passwordField: 'pass',
additionalFields: ['role', 'isVerified'],
abilityPredicate: (user) => user.role === 'admin' || user.role === 'user',
callback: async (user) => {
if (user.isVerified) {
return;
}
throw new common_1.UnauthorizedException(`Hello ${user.username}, you must verify your account first!`);
},
},
},
}, fixtures);
});
describe('loginField', () => {
it('should throw an unauthorized exception if loginField is missing', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/login', { pass: 'test' });
expect(status).toBe(401);
expect(body).toEqual({
message: 'Unauthorized',
statusCode: 401,
});
});
});
describe('passwordField', () => {
it('should throw an unauthorized exception if passwordField is missing', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/login', { username: 'unit' });
expect(status).toBe(401);
expect(body).toEqual({
message: 'Unauthorized',
statusCode: 401,
});
});
});
describe('abilityPredicate', () => {
it('should throw an forbidden exception if user role is not admin or user', async () => {
const { username, pass } = client;
const { body, status } = await e2e_setup_1.server.post('/auth/login', { username, pass });
expect(status).toBe(403);
expect(body).toEqual({
error: 'Forbidden',
message: 'Access denied',
statusCode: 403,
});
});
});
describe('callback', () => {
it('should throw an unauthorized exception if user is not verified', async () => {
const { username, pass } = user;
const { body, status } = await e2e_setup_1.server.post('/auth/login', { username, pass });
expect(status).toBe(401);
expect(body).toEqual({
error: 'Unauthorized',
message: `Hello ${username}, you must verify your account first!`,
statusCode: 401,
});
});
});
describe('additionalFields', () => {
it('should return additional fields', async () => {
const { username, pass } = admin;
const { body: { accessToken } } = await e2e_setup_1.server.post('/auth/login', { username, pass });
const { body, status } = await e2e_setup_1.server.get('/auth/account', { headers: { Authorization: `Bearer ${accessToken}` } });
expect(status).toBe(200);
expect(body).toEqual({ id: expect.any(String), username: 'admin', role: 'admin', isVerified: true });
});
});
});
describe('useAuth with resetPassword options', () => {
let User = class User extends src_1.BaseEntity {
};
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], User.prototype, "email", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], User.prototype, "password", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: Boolean, default: false }),
__metadata("design:type", Boolean)
], User.prototype, "isVerified", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: String }),
__metadata("design:type", String)
], User.prototype, "resetPasswordToken", void 0);
User = __decorate([
(0, mongoose_1.Schema)({ collection: 'users' })
], User);
let model;
let user;
let client;
let app;
beforeEach(async () => {
user = { email: 'user@test.co', password: 'user', isVerified: true };
client = { email: 'client@test.co', password: 'client' };
const bcryptService = new src_1.BcryptService();
const fixtures = async (_) => {
model = await (0, utils_1.getModelFromEntity)(User);
await model.insertMany([
{ ...user, password: await bcryptService.hashPassword(user.password) },
{ ...client, password: await bcryptService.hashPassword(client.password) },
]);
};
app = await initModule({
useAuth: {
userEntity: User,
resetPassword: {
emailField: 'email',
expirationInMinutes: 1,
resetPasswordCallback: async ({ resetPasswordToken }, { updateUserByEmail }) => {
await updateUserByEmail({ $set: { resetPasswordToken } });
},
changePasswordAbilityPredicate: (user) => user.isVerified && !!user.resetPasswordToken,
changePasswordCallback: async (user, { updateOneDocument }) => {
await updateOneDocument(User, { _id: user.id }, { $unset: { resetPasswordToken: 1 } });
},
},
},
}, fixtures);
});
describe('POST /auth/reset-password', () => {
it('should throw a bad request exception if email is missing if no validation options are provided', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/reset-password', {});
expect(status).toBe(400);
expect(body).toEqual({
error: 'Bad Request',
message: 'Invalid or missing argument',
statusCode: 400,
});
});
it('should not throw a bad request exception if email is invalid if no validation options are provided', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/reset-password', { email: 'unit.test.co' });
expect(status).toBe(204);
expect(body).toEqual({});
});
it('should not throw an exception if email is not found', async () => {
const { body, status } = await e2e_setup_1.server.post('/auth/reset-password', { email: 'invalid@test.co' });
expect(status).toBe(204);
expect(body).toEqual({});
});
describe('resetPasswordCallback', () => {
it('should set resetPasswordToken if email is valid', async () => {
const { email } = user;
const { resetPasswordToken: resetPasswordTokenBeforeUpdate } = (await model.findOne({ email }).lean().exec());
const { status } = await e2e_setup_1.server.post('/auth/reset-password', { email });
const { resetPasswordToken: resetPasswordTokenAfterUpdate } = (await model.findOne({ email }).lean().exec());
expect(status).toBe(204);
expect(resetPasswordTokenBeforeUpdate).toStrictEqual(undefined);
expect(resetPasswordTokenAfterUpdate).toStrictEqual(expect.any(String));
});
});
});
describe('PATCH /auth/change-password', () => {
it('should throw a bad request exception if resetPasswordToken is missing', async () => {
const { body, status } = await e2e_setup_1.server.patch('/auth/change-password', { newPassword: 'test' });
expect(status).toBe(400);
expect(body).toEqual({
error: 'Bad Request',
message: 'Invalid or missing argument',
statusCode: 400,
});
});
it('should throw a bad request exception if newPassword is missing', async () => {
const { email } = user;
await e2e_setup_1.server.post('/auth/reset-password', { email });
const { resetPasswordToken: resetPasswordTokenAfterUpdate } = (await model.findOne({ email }).lean().exec());
const resetPasswordToken = resetPasswordTokenAfterUpdate;
const { body, status } = await e2e_setup_1.server.patch('/auth/change-password', { resetPasswordToken });
expect(status).toBe(400);
expect(body).toEqual({
error: 'Bad Request',
message: 'Invalid or missing argument',
statusCode: 400,
});
});
it('should throw an unauthorized exception if resetPasswordToken is invalid', async () => {
const { body, status } = await e2e_setup_1.server.patch('/auth/change-password', { resetPasswordToken: 'test', newPassword: 'newPassword' });
expect(status).toBe(400);
expect(body).toEqual({
error: 'Bad Request',
message: 'Invalid reset password token. Please redo the reset password process.',
statusCode: 400,
});
});
it('should throw an unauthorized exception if resetPasswordToken is expired', async () => {
const jwtService = app.get(jwt_1.JwtService);
const expiredResetPasswordToken = jwtService.sign({ email: user.email }, { expiresIn: 1 });
await (0, utils_1.wait)(500);
const { body, status } = await e2e_setup_1.server.patch('/auth/change-password', { resetPasswordToken: expiredResetPasswordToken, newPassword: 'newPassword' });
expect(status).toBe(401);
expect(body).toEqual({
error: 'Unauthorized',
message: 'Time to reset password has expired. Please redo the reset password process.',
statusCode: 401,
});
});
describe('changePasswordAbilityPredicate', () => {
let resetPasswordToken;
beforeEach(async () => {
await e2e_setup_1.server.post('/auth/reset-password', { email: client.email });
const { resetPasswordToken: token } = (await model.findOne({ email: client.email }).lean().exec());
resetPasswordToken = token;
});
it('should throw a forbidden exception if user is not allowed to change password', async () => {
expect(resetPasswordToken).toStrictEqual(expect.any(String));
const { body, status } = await e2e_setup_1.server.patch('/auth/change-password', { resetPasswordToken, newPassword: 'newPassword' });
expect(status).toBe(403);
expect(body).toEqual({
error: 'Forbidden',
message: 'You are not allowed to change your password.',
statusCode: 403,
});
});
});
describe('changePasswordCallback', () => {
let resetPasswordToken;
beforeEach(async () => {
await e2e_setup_1.server.post('/auth/reset-password', { email: user.email });
const { resetPasswordToken: token } = (await model.findOne({ email: user.email }).lean().exec());
resetPasswordToken = token;
});
it('should change password and unset resetPasswordToken if resetPasswordToken is valid', async () => {
expect(resetPasswordToken).toStrictEqual(expect.any(String));
const newPassword = 'newPassword';
const bcryptService = app.get(src_1.BcryptService);
const { password: passwordBeforeUpdate } = (await model.findOne({ email: user.email }).lean().exec());
const { status } = await e2e_setup_1.server.patch('/auth/change-password', { resetPasswordToken, newPassword });
const { password: passwordAfterUpdate, resetPasswordToken: tokenAfterUpdate } = (await model.findOne({ email: user.email }).lean().exec());
const isPreviousPassword = await bcryptService.comparePassword(user.password, passwordBeforeUpdate);
expect(isPreviousPassword).toBe(true);
const isNewPassword = await bcryptService.comparePassword(newPassword, passwordAfterUpdate);
expect(isNewPassword).toBe(true);
expect(status).toBe(204);
expect(tokenAfterUpdate).toStrictEqual(undefined);
});
});
});
});
});
describe('Websockets', () => {
it('should enable websockets globally', async () => {
await initModule({
webSocket: true,
}, undefined, async (app) => {
app.useWebSocketAdapter(new socket_adapter_1.SocketAdapter(app));
}, true);
expect(src_1.DynamicApiModule.state.get('gatewayOptions')).toStrictEqual({});
await e2e_setup_1.server.emit('test', { message: 'Hello' });
expect(e2e_setup_1.handleSocketResponse).toHaveBeenCalledTimes(1);
expect(e2e_setup_1.handleSocketResponse).toHaveBeenCalledWith({ message: 'Hello' });
expect(e2e_setup_1.handleSocketException).not.toHaveBeenCalled();
});
describe('Authentication EVENTS', () => {
describe('useAuth when only userEntity is provided', () => {
let UserEntity = class UserEntity extends src_1.BaseEntity {
};
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], UserEntity.prototype, "email", void 0);
__decorate([
(0, mongoose_1.Prop)({ type: String, required: true }),
__metadata("design:type", String)
], UserEntity.prototype, "password", void 0);
UserEntity = __decorate([
(0, mongoose_1.Schema)({ collection: 'users' })
], UserEntity);
beforeEach(async () => {
await initModule({
useAuth: {
userEntity: UserEntity,
webSocket: true,
},
}, undefined, async (app) => {
app.useWebSocketAdapter(new socket_adapter_1.SocketAdapter(app));
});
});
describe('EVENT auth-register', () => {
it('should throw a ws exception if email is missing', async () => {
await e2e_setup_1.server.emit('auth-register', { username: 'unit-test', password: 'test-2' });
expect(e2e_setup_1.handleSocketException).toHaveBeenCalledTimes(1);
expect(e2e_setup_1.handleSocketException).toHaveBeenCalledWith({
message: ['email property is required'],
});
expect(e2e_setup_1.handleSocketResponse).not.toHaveBeenCalled();
});
it('should throw a ws exception if password is missing', async () => {
await e2e_setup_1.server.emit('auth-register', { email: 'unit@test.co', pass: 'test-2' });
expect(e2e_setup_1.handleSocketException).toHaveBeenCalledTimes(1);
expect(e2e_setup_1.handleSocketException).toHaveBeenCalledWith({
message: ['password property is required'],
});
expect(e2e_setup_1.handleSocketResponse).not.toHaveBeenCalled();
});
it('should create a new user and return access token', async () => {
await e2e_setup_1.server.emit('auth-register', { email: 'unit@test.co', password: 'test' });
expect(e2e_setup_1.handleSocketException).not.toHaveBeenCalled();
expect(e2e_setup_1.handleSocketResponse).toHaveBeenCalledTimes(1);
expect(e2e_setup_1.handleSocketResponse).toHaveBeenCalledWith({ accessToken: expect.any(String) });
});
});
describe('EVENT auth-login', () => {
it('should throw a ws exception if email is missing', async () => {
await e2e_setup_1.server.emit('auth-login', { password: 'test-2' });
expect(e2e_setup_1.handleSocketResponse).not.toHaveBeenCalled();
expect(e2e_setup_1.handleSocketException).toHaveBeenCalledTimes(1);
expect(e2e_setup_1.handleSocketException).toHaveBeenCalledWith({
message: 'Unauthorized',
});
});
it('should throw a ws exception if password is missing', async () => {
await e2e_setup_1.server.emit('auth-login', { email: 'unit@test.co' });
expect(e2e_setup_1.handleSocketResponse).not.toHaveBeenCalled();
expect(e2e_setup_1.handleSocketException).toHaveBeenCalledTimes(1);
expect(e2e_setup_1.handleSocketException).toHaveBeenCalledWith({
message: 'Unauthorized',
});
});
it('should return access token', async () => {
await e2e_setup_1.server.emit('auth-register', { email: 'unit@test.co', password: 'test' });
await e2e_setup_1.server.emit('auth-login', { email