mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 07:40:32 +00:00
feat: initialize the server e2e tests
This commit is contained in:
@@ -1,29 +1,4 @@
|
|||||||
import { AuthForgetPasswordService } from './AuthForgetPassword.service';
|
|
||||||
import { AuthSendResetPasswordService } from './AuthResetPassword.service';
|
|
||||||
import { AuthSigninService } from './AuthSignin.service';
|
|
||||||
import { AuthSignupService } from './AuthSignup.service';
|
|
||||||
|
|
||||||
export class AuthApplication {
|
export class AuthApplication {
|
||||||
constructor(
|
|
||||||
private readonly authSigninService: AuthSigninService,
|
|
||||||
private readonly authSignupService: AuthSignupService,
|
|
||||||
private readonly authResetPasswordService: AuthSendResetPasswordService,
|
|
||||||
private readonly authForgetPasswordService: AuthForgetPasswordService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async signin(email: string, password: string) {
|
|
||||||
return this.authSigninService.signIn(email, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
async signup(data: any) {
|
|
||||||
return this.authSignupService.signup(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetPassword(data: any) {
|
|
||||||
return this.authResetPasswordService.resetPassword(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async forgetPassword(data: any) {
|
|
||||||
return this.authForgetPasswordService.execute(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export class AuthForgetPasswordService {}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
import { Injectable, Inject } from '@nestjs/common';
|
|
||||||
import uniqid from 'uniqid';
|
|
||||||
import moment from 'moment';
|
|
||||||
import config from '@/config';
|
|
||||||
import {
|
|
||||||
IAuthResetedPasswordEventPayload,
|
|
||||||
IAuthSendedResetPassword,
|
|
||||||
IAuthSendingResetPassword,
|
|
||||||
IPasswordReset,
|
|
||||||
ISystemUser,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { PasswordReset } from '@/system/models';
|
|
||||||
import { ERRORS } from './_constants';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { hashPassword } from '@/utils';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AuthSendResetPasswordService {
|
|
||||||
constructor(
|
|
||||||
private readonly eventPublisher: EventPublisher,
|
|
||||||
@Inject('SystemUserRepository') private readonly systemUserRepository: any,
|
|
||||||
|
|
||||||
@Inject('PasswordResetModel')
|
|
||||||
private readonly passwordResetModel: typeof PasswordReset,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates and retrieve password reset token for the given user email.
|
|
||||||
* @param {string} email
|
|
||||||
* @return {<Promise<IPasswordReset>}
|
|
||||||
*/
|
|
||||||
public async sendResetPassword(email: string): Promise<PasswordReset> {
|
|
||||||
const user = await this.validateEmailExistance(email);
|
|
||||||
|
|
||||||
const token: string = uniqid();
|
|
||||||
|
|
||||||
// Triggers sending reset password event.
|
|
||||||
await this.eventPublisher.emitAsync(events.auth.sendingResetPassword, {
|
|
||||||
user,
|
|
||||||
token,
|
|
||||||
} as IAuthSendingResetPassword);
|
|
||||||
|
|
||||||
// Delete all stored tokens of reset password that associate to the give email.
|
|
||||||
this.deletePasswordResetToken(email);
|
|
||||||
|
|
||||||
// Creates a new password reset row with unique token.
|
|
||||||
const passwordReset = await this.passwordResetModel
|
|
||||||
.query()
|
|
||||||
.insert({ email, token });
|
|
||||||
|
|
||||||
// Triggers sent reset password event.
|
|
||||||
await this.eventPublisher.emitAsync(events.auth.sendResetPassword, {
|
|
||||||
user,
|
|
||||||
token,
|
|
||||||
} as IAuthSendedResetPassword);
|
|
||||||
|
|
||||||
return passwordReset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets a user password from given token.
|
|
||||||
* @param {string} token - Password reset token.
|
|
||||||
* @param {string} password - New Password.
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
public async resetPassword(token: string, password: string): Promise<void> {
|
|
||||||
// Finds the password reset token.
|
|
||||||
const tokenModel: IPasswordReset = await this.passwordResetModel
|
|
||||||
.query()
|
|
||||||
.findOne('token', token);
|
|
||||||
// In case the password reset token not found throw token invalid error..
|
|
||||||
if (!tokenModel) {
|
|
||||||
throw new ServiceError(ERRORS.TOKEN_INVALID);
|
|
||||||
}
|
|
||||||
// Different between token creation datetime and current time.
|
|
||||||
if (
|
|
||||||
moment().diff(tokenModel.createdAt, 'seconds') >
|
|
||||||
config.resetPasswordSeconds
|
|
||||||
) {
|
|
||||||
// Deletes the expired token by expired token email.
|
|
||||||
await this.deletePasswordResetToken(tokenModel.email);
|
|
||||||
throw new ServiceError(ERRORS.TOKEN_EXPIRED);
|
|
||||||
}
|
|
||||||
const user = await this.systemUserRepository.findOneByEmail(
|
|
||||||
tokenModel.email,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new ServiceError(ERRORS.USER_NOT_FOUND);
|
|
||||||
}
|
|
||||||
const hashedPassword = await hashPassword(password);
|
|
||||||
|
|
||||||
await this.systemUserRepository.update(
|
|
||||||
{ password: hashedPassword },
|
|
||||||
{ id: user.id },
|
|
||||||
);
|
|
||||||
// Deletes the used token.
|
|
||||||
await this.deletePasswordResetToken(tokenModel.email);
|
|
||||||
|
|
||||||
// Triggers `onResetPassword` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.auth.resetPassword, {
|
|
||||||
user,
|
|
||||||
token,
|
|
||||||
password,
|
|
||||||
} as IAuthResetedPasswordEventPayload);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the password reset token by the given email.
|
|
||||||
* @param {string} email
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
private async deletePasswordResetToken(email: string) {
|
|
||||||
return this.passwordResetModel.query().where('email', email).delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the given email existance on the storage.
|
|
||||||
* @throws {ServiceError}
|
|
||||||
* @param {string} email - email address.
|
|
||||||
*/
|
|
||||||
private async validateEmailExistance(email: string): Promise<ISystemUser> {
|
|
||||||
const userByEmail = await this.systemUserRepository.findOneByEmail(email);
|
|
||||||
|
|
||||||
if (!userByEmail) {
|
|
||||||
throw new ServiceError(ERRORS.EMAIL_NOT_FOUND);
|
|
||||||
}
|
|
||||||
return userByEmail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { cloneDeep } from 'lodash';
|
|
||||||
import { SystemUser } from '../System/models/SystemUser';
|
|
||||||
import { TenantModel } from '../System/models/TenantModel';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
import { events } from '@/common/events/events';
|
|
||||||
import { ServiceError } from '../Items/ServiceError';
|
|
||||||
import { ERRORS } from '../Items/Items.constants';
|
|
||||||
import {
|
|
||||||
IAuthSignedInEventPayload,
|
|
||||||
IAuthSigningInEventPayload,
|
|
||||||
IAuthSignInPOJO,
|
|
||||||
} from './Auth.interfaces';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AuthSigninService {
|
|
||||||
constructor(
|
|
||||||
private readonly eventEmitter: EventEmitter2,
|
|
||||||
|
|
||||||
@Inject(SystemUser.name)
|
|
||||||
private readonly systemUserModel: typeof SystemUser,
|
|
||||||
|
|
||||||
@Inject(TenantModel.name)
|
|
||||||
private readonly tenantModel: typeof TenantModel,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the given email and password.
|
|
||||||
* @param {ISystemUser} user
|
|
||||||
*/
|
|
||||||
public async validateSignIn(user: SystemUser) {
|
|
||||||
// Validate if the given user is inactive.
|
|
||||||
if (!user.active) {
|
|
||||||
throw new ServiceError(ERRORS.USER_INACTIVE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sign-in and generates JWT token.
|
|
||||||
* @param {string} email - Email address.
|
|
||||||
* @param {string} password - Password.
|
|
||||||
* @return {Promise<{user: IUser, token: string}>}
|
|
||||||
*/
|
|
||||||
public async signIn(
|
|
||||||
email: string,
|
|
||||||
password: string,
|
|
||||||
): Promise<IAuthSignInPOJO> {
|
|
||||||
// Finds the user of the given email address.
|
|
||||||
const user = await SystemUser.query()
|
|
||||||
.findOne('email', email)
|
|
||||||
.modify('inviteAccepted');
|
|
||||||
|
|
||||||
// Validate the given email and password.
|
|
||||||
await this.validateSignIn(user);
|
|
||||||
|
|
||||||
// Triggers on signing-in event.
|
|
||||||
await this.eventEmitter.emitAsync(events.auth.signingIn, {
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
user,
|
|
||||||
} as IAuthSigningInEventPayload);
|
|
||||||
|
|
||||||
const token = generateToken(user);
|
|
||||||
|
|
||||||
// Update the last login at of the user.
|
|
||||||
// await systemUserRepository.patchLastLoginAt(user.id);
|
|
||||||
|
|
||||||
// Triggers `onSignIn` event.
|
|
||||||
await this.eventEmitter.emitAsync(events.auth.signIn, {
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
user,
|
|
||||||
} as IAuthSignedInEventPayload);
|
|
||||||
|
|
||||||
const tenant = await this.tenantModel
|
|
||||||
.query()
|
|
||||||
.findById(user.tenantId)
|
|
||||||
.withGraphFetched('metadata');
|
|
||||||
|
|
||||||
// Keep the user object immutable.
|
|
||||||
const outputUser = cloneDeep(user);
|
|
||||||
|
|
||||||
// Remove password property from user object.
|
|
||||||
Reflect.deleteProperty(outputUser, 'password');
|
|
||||||
|
|
||||||
return { user: outputUser, token, tenant };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export class AuthSignupService {}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import * as request from 'supertest';
|
|
||||||
import { AppModule } from '../src/modules/App/App.module';
|
|
||||||
|
|
||||||
describe('AppController (e2e)', () => {
|
|
||||||
let app: INestApplication;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
||||||
imports: [AppModule],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
app = moduleFixture.createNestApplication();
|
|
||||||
await app.init();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('/ (GET)', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/')
|
|
||||||
.expect(200)
|
|
||||||
.expect('Hello World!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
20
packages/server-nest/test/init-app-test.ts
Normal file
20
packages/server-nest/test/init-app-test.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { AppModule } from '../src/modules/App/App.module';
|
||||||
|
|
||||||
|
let app: INestApplication;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [AppModule],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
app = moduleFixture.createNestApplication();
|
||||||
|
await app.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
export { app };
|
||||||
19
packages/server-nest/test/items.e2e-spec.ts
Normal file
19
packages/server-nest/test/items.e2e-spec.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import * as request from 'supertest';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { AppModule } from '../src/modules/App/App.module';
|
||||||
|
import { app } from './init-app-test';
|
||||||
|
|
||||||
|
describe('Items (e2e)', () => {
|
||||||
|
it('/items (POST)', () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.post('/items')
|
||||||
|
.set('organization-id', '4064541lv40nhca')
|
||||||
|
.send({
|
||||||
|
name: faker.commerce.productName(),
|
||||||
|
type: 'service',
|
||||||
|
})
|
||||||
|
.expect(201);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,5 +5,8 @@
|
|||||||
"testRegex": ".e2e-spec.ts$",
|
"testRegex": ".e2e-spec.ts$",
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"^@/(.*)$": "<rootDir>/../src/$1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
"@reduxjs/toolkit": "^1.2.5",
|
"@reduxjs/toolkit": "^1.2.5",
|
||||||
"@stripe/connect-js": "^3.3.12",
|
"@stripe/connect-js": "^3.3.12",
|
||||||
"@stripe/react-connect-js": "^3.3.13",
|
"@stripe/react-connect-js": "^3.3.13",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
|
||||||
"@testing-library/react": "^9.4.0",
|
"@testing-library/react": "^9.4.0",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^7.2.1",
|
||||||
"@tiptap/core": "2.1.13",
|
"@tiptap/core": "2.1.13",
|
||||||
@@ -33,7 +32,6 @@
|
|||||||
"@tiptap/pm": "2.1.13",
|
"@tiptap/pm": "2.1.13",
|
||||||
"@tiptap/react": "2.1.13",
|
"@tiptap/react": "2.1.13",
|
||||||
"@tiptap/starter-kit": "2.1.13",
|
"@tiptap/starter-kit": "2.1.13",
|
||||||
"@types/jest": "^26.0.15",
|
|
||||||
"@types/js-money": "^0.6.1",
|
"@types/js-money": "^0.6.1",
|
||||||
"@types/lodash": "^4.14.172",
|
"@types/lodash": "^4.14.172",
|
||||||
"@types/node": "^14.14.9",
|
"@types/node": "^14.14.9",
|
||||||
@@ -68,10 +66,6 @@
|
|||||||
"helmet": "^3.21.0",
|
"helmet": "^3.21.0",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"http-proxy-middleware": "^1.0.0",
|
"http-proxy-middleware": "^1.0.0",
|
||||||
"jest": "24.9.0",
|
|
||||||
"jest-environment-jsdom-fourteen": "1.0.1",
|
|
||||||
"jest-resolve": "24.9.0",
|
|
||||||
"jest-watch-typeahead": "0.4.2",
|
|
||||||
"js-cookie": "2.2.1",
|
"js-cookie": "2.2.1",
|
||||||
"js-money": "^0.6.3",
|
"js-money": "^0.6.3",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
|||||||
Reference in New Issue
Block a user