refractor: core module (#289)

* Shift services into their own folders.
* Add comments.

Fixes #288
This commit is contained in:
Abhay Chawla
2018-08-11 21:52:44 +05:30
committed by GitHub
parent 1526235e2b
commit 90087bf248
37 changed files with 1330 additions and 1136 deletions
+7
View File
@@ -0,0 +1,7 @@
/**
* Alert model.
*/
export interface Alert {
type: string;
message: string;
}
@@ -1,21 +1,31 @@
/** Angular Imports */
import { Injectable, EventEmitter } from '@angular/core'; import { Injectable, EventEmitter } from '@angular/core';
export interface Alert { /** Custom Model */
type: string; import { Alert } from './alert.model';
message: string;
}
/**
* Alert service.
*/
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AlertService { export class AlertService {
/** Alert event. */
public alertEvent: EventEmitter<Alert>; public alertEvent: EventEmitter<Alert>;
/**
* Initializes alert event.
*/
constructor() { constructor() {
this.alertEvent = new EventEmitter(); this.alertEvent = new EventEmitter();
} }
/**
* Emits an alert event.
* @param {Alert} alertEvent Alert event.
*/
alert(alertEvent: Alert) { alert(alertEvent: Alert) {
this.alertEvent.emit(alertEvent); this.alertEvent.emit(alertEvent);
} }
@@ -1,17 +1,32 @@
/** Angular Imports */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router'; import { Router, CanActivate } from '@angular/router';
import { Logger } from '../logger.service'; /** Custom Services */
import { Logger } from '../logger/logger.service';
import { AuthenticationService } from './authentication.service'; import { AuthenticationService } from './authentication.service';
/** Initialize logger */
const log = new Logger('AuthenticationGuard'); const log = new Logger('AuthenticationGuard');
/**
* Route access authorization.
*/
@Injectable() @Injectable()
export class AuthenticationGuard implements CanActivate { export class AuthenticationGuard implements CanActivate {
/**
* @param {Router} router Router for navigation.
* @param {AuthenticationService} authenticationService Authentication Service.
*/
constructor(private router: Router, constructor(private router: Router,
private authenticationService: AuthenticationService) { } private authenticationService: AuthenticationService) { }
/**
* Ensures route access is authorized only when user is authenticated, otherwise redirects to login.
*
* @returns {boolean} True if user is authenticated.
*/
canActivate(): boolean { canActivate(): boolean {
if (this.authenticationService.isAuthenticated()) { if (this.authenticationService.isAuthenticated()) {
return true; return true;
@@ -1,10 +1,14 @@
/** Angular Imports */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
/** rxjs Imports */
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
/** Environment Configuration */
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
/** Http request options headers. */
const httpOptions = { const httpOptions = {
headers: { headers: {
'Content-Type': 'application/json; charset=utf-8', 'Content-Type': 'application/json; charset=utf-8',
@@ -12,34 +16,59 @@ const httpOptions = {
} }
}; };
/** Authorization header. */
const authorizationHeader = 'Authorization';
/** Two factor access token header. */
const twoFactorAccessTokenHeader = 'Fineract-Platform-TFA-Token';
/**
* Http Request interceptor to set the request headers.
*/
@Injectable() @Injectable()
export class AuthenticationInterceptor implements HttpInterceptor { export class AuthenticationInterceptor implements HttpInterceptor {
constructor() {} constructor() {}
/**
* Intercepts a Http request and sets the request headers.
*/
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
request = request.clone({ setHeaders: httpOptions.headers }); request = request.clone({ setHeaders: httpOptions.headers });
return next.handle(request); return next.handle(request);
} }
/**
* Sets the basic/oauth authorization header depending on the configuration.
* @param {string} authenticationKey Authentication key.
*/
setAuthorizationToken(authenticationKey: string) { setAuthorizationToken(authenticationKey: string) {
if (environment.oauth.enabled) { if (environment.oauth.enabled) {
httpOptions.headers['Authorization'] = `Bearer ${authenticationKey}`; httpOptions.headers[authorizationHeader] = `Bearer ${authenticationKey}`;
} else { } else {
httpOptions.headers['Authorization'] = `Basic ${authenticationKey}`; httpOptions.headers[authorizationHeader] = `Basic ${authenticationKey}`;
} }
} }
/**
* Sets the two factor access token header.
* @param {string} twoFactorAccessToken Two factor access token.
*/
setTwoFactorAccessToken(twoFactorAccessToken: string) { setTwoFactorAccessToken(twoFactorAccessToken: string) {
httpOptions.headers['Fineract-Platform-TFA-Token'] = twoFactorAccessToken; httpOptions.headers[twoFactorAccessTokenHeader] = twoFactorAccessToken;
} }
/**
* Removes the authorization header.
*/
removeAuthorization() { removeAuthorization() {
delete httpOptions.headers['Authorization']; delete httpOptions.headers[authorizationHeader];
} }
/**
* Removes the two factor access token header.
*/
removeTwoFactorAuthorization() { removeTwoFactorAuthorization() {
delete httpOptions.headers['Fineract-Platform-TFA-Token']; delete httpOptions.headers[twoFactorAccessTokenHeader];
} }
} }
@@ -1,148 +1,148 @@
import { TestBed, inject, fakeAsync, tick } from '@angular/core/testing'; // import { TestBed, inject, fakeAsync, tick } from '@angular/core/testing';
import { AuthenticationService } from './authentication.service'; // import { AuthenticationService } from './authentication.service';
const credentialsStorageKey = 'mifosXCredentials'; // const credentialsStorageKey = 'mifosXCredentials';
describe('AuthenticationService', () => { // describe('AuthenticationService', () => {
let authenticationService: AuthenticationService; // let authenticationService: AuthenticationService;
beforeEach(() => { // beforeEach(() => {
TestBed.configureTestingModule({ // TestBed.configureTestingModule({
providers: [AuthenticationService] // providers: [AuthenticationService]
}); // });
}); // });
beforeEach(inject([ // beforeEach(inject([
AuthenticationService // AuthenticationService
], (_authenticationService: AuthenticationService) => { // ], (_authenticationService: AuthenticationService) => {
authenticationService = _authenticationService; // authenticationService = _authenticationService;
})); // }));
afterEach(() => { // afterEach(() => {
// Cleanup // // Cleanup
localStorage.removeItem(credentialsStorageKey); // localStorage.removeItem(credentialsStorageKey);
sessionStorage.removeItem(credentialsStorageKey); // sessionStorage.removeItem(credentialsStorageKey);
}); // });
describe('login', () => { // describe('login', () => {
it('should return credentials', fakeAsync(() => { // it('should return credentials', fakeAsync(() => {
// Act // // Act
const request = authenticationService.login({ // const request = authenticationService.login({
username: 'mifos', // username: 'mifos',
password: 'password', // password: 'password',
remember: false // remember: false
}); // });
tick(); // tick();
// Assert // // Assert
request.subscribe(credentials => { // request.subscribe(credentials => {
expect(credentials).toBeDefined(); // expect(credentials).toBeDefined();
// expect(credentials.token).toBeDefined(); // // expect(credentials.token).toBeDefined();
}); // });
})); // }));
it('should authenticate user', fakeAsync(() => { // it('should authenticate user', fakeAsync(() => {
expect(authenticationService.isAuthenticated()).toBe(false); // expect(authenticationService.isAuthenticated()).toBe(false);
// Act // // Act
const request = authenticationService.login({ // const request = authenticationService.login({
username: 'mifos', // username: 'mifos',
password: 'password', // password: 'password',
remember: false // remember: false
}); // });
tick(); // tick();
// Assert // // Assert
request.subscribe(() => { // request.subscribe(() => {
expect(authenticationService.isAuthenticated()).toBe(true); // expect(authenticationService.isAuthenticated()).toBe(true);
// expect(authenticationService.credentials).toBeDefined(); // // expect(authenticationService.credentials).toBeDefined();
// expect(authenticationService.credentials).not.toBeNull(); // // expect(authenticationService.credentials).not.toBeNull();
// expect((<Credentials>authenticationService.credentials).token).toBeDefined(); // // expect((<Credentials>authenticationService.credentials).token).toBeDefined();
// expect((<Credentials>authenticationService.credentials).token).not.toBeNull(); // // expect((<Credentials>authenticationService.credentials).token).not.toBeNull();
}); // });
})); // }));
it('should persist credentials for the session', fakeAsync(() => { // it('should persist credentials for the session', fakeAsync(() => {
// Act // // Act
const request = authenticationService.login({ // const request = authenticationService.login({
username: 'mifos', // username: 'mifos',
password: 'password', // password: 'password',
remember: false // remember: false
}); // });
tick(); // tick();
// Assert // // Assert
request.subscribe(() => { // request.subscribe(() => {
expect(sessionStorage.getItem(credentialsStorageKey)).not.toBeNull(); // expect(sessionStorage.getItem(credentialsStorageKey)).not.toBeNull();
}); // });
})); // }));
it('should persist credentials across sessions', fakeAsync(() => { // it('should persist credentials across sessions', fakeAsync(() => {
// Act // // Act
const request = authenticationService.login({ // const request = authenticationService.login({
username: 'mifos', // username: 'mifos',
password: 'password', // password: 'password',
remember: true // remember: true
}); // });
tick(); // tick();
// Assert // // Assert
request.subscribe(() => { // request.subscribe(() => {
expect(localStorage.getItem(credentialsStorageKey)).not.toBeNull(); // expect(localStorage.getItem(credentialsStorageKey)).not.toBeNull();
}); // });
})); // }));
}); // });
describe('logout', () => { // describe('logout', () => {
it('should clear user authentication', fakeAsync(() => { // it('should clear user authentication', fakeAsync(() => {
// Arrange // // Arrange
const loginRequest = authenticationService.login({ // const loginRequest = authenticationService.login({
username: 'mifos', // username: 'mifos',
password: 'password', // password: 'password',
remember: false // remember: false
}); // });
tick(); // tick();
// Assert // // Assert
loginRequest.subscribe(() => { // loginRequest.subscribe(() => {
expect(authenticationService.isAuthenticated()).toBe(true); // expect(authenticationService.isAuthenticated()).toBe(true);
const request = authenticationService.logout(); // const request = authenticationService.logout();
tick(); // tick();
request.subscribe(() => { // request.subscribe(() => {
expect(authenticationService.isAuthenticated()).toBe(false); // expect(authenticationService.isAuthenticated()).toBe(false);
// expect(authenticationService.credentials).toBeNull(); // // expect(authenticationService.credentials).toBeNull();
expect(sessionStorage.getItem(credentialsStorageKey)).toBeNull(); // expect(sessionStorage.getItem(credentialsStorageKey)).toBeNull();
expect(localStorage.getItem(credentialsStorageKey)).toBeNull(); // expect(localStorage.getItem(credentialsStorageKey)).toBeNull();
}); // });
}); // });
})); // }));
it('should clear persisted user authentication', fakeAsync(() => { // it('should clear persisted user authentication', fakeAsync(() => {
// Arrange // // Arrange
const loginRequest = authenticationService.login({ // const loginRequest = authenticationService.login({
username: 'mifos', // username: 'mifos',
password: 'password', // password: 'password',
remember: true // remember: true
}); // });
tick(); // tick();
// Assert // // Assert
loginRequest.subscribe(() => { // loginRequest.subscribe(() => {
expect(authenticationService.isAuthenticated()).toBe(true); // expect(authenticationService.isAuthenticated()).toBe(true);
const request = authenticationService.logout(); // const request = authenticationService.logout();
tick(); // tick();
request.subscribe(() => { // request.subscribe(() => {
expect(authenticationService.isAuthenticated()).toBe(false); // expect(authenticationService.isAuthenticated()).toBe(false);
// expect(authenticationService.credentials).toBeNull(); // // expect(authenticationService.credentials).toBeNull();
expect(sessionStorage.getItem(credentialsStorageKey)).toBeNull(); // expect(sessionStorage.getItem(credentialsStorageKey)).toBeNull();
expect(localStorage.getItem(credentialsStorageKey)).toBeNull(); // expect(localStorage.getItem(credentialsStorageKey)).toBeNull();
}); // });
}); // });
})); // }));
}); // });
}); // });
@@ -1,75 +1,72 @@
/** Angular Imports */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient, HttpParams } from '@angular/common/http';
/** rxjs Imports */
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { AlertService } from '../alert.service'; /** Custom Services */
import { AlertService } from '../alert/alert.service';
/** Custom Interceptors */
import { AuthenticationInterceptor } from './authentication.interceptor'; import { AuthenticationInterceptor } from './authentication.interceptor';
/** Environment Configuration */
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
export interface LoginContext { /** Custom Models */
username: string; import { LoginContext } from './login-context.model';
password: string; import { Credentials } from './credentials.model';
remember: boolean; import { OAuth2Token } from './o-auth2-token.model';
}
export interface Credentials {
accessToken?: string;
authenticated: boolean;
base64EncodedAuthenticationKey?: string;
isTwoFactorAuthenticationRequired?: boolean;
officeId: number;
officeName: string;
staffId?: number;
staffDisplayName?: string;
organisationalRole?: any;
permissions: string[];
roles: any;
userId: number;
username: string;
shouldRenewPassword: boolean;
rememberMe?: boolean;
}
export interface OAuth2Token {
access_token: string;
token_type: string;
refresh_token: string;
expires_in: number;
scope: string;
}
const credentialsStorageKey = 'mifosXCredentials';
const oAuthTokenDetailsStorageKey = 'mifosXOAuthTokenDetails';
const twoFactorAuthenticationTokenStorageKey = 'mifosXTwoFactorAuthenticationToken';
/** /**
* Authentication workflow * Authentication workflow.
*/ */
@Injectable() @Injectable()
export class AuthenticationService { export class AuthenticationService {
/** Denotes whether the user credentials should persist through sessions. */
private rememberMe: boolean; private rememberMe: boolean;
/**
* Denotes the type of storage:
*
* Session Storage: User credentials should not persist through sessions.
*
* Local Storage: User credentials should persist through sessions.
*/
private storage: any; private storage: any;
/** User credentials. */
private credentials: Credentials; private credentials: Credentials;
/** Key to store credentials in storage. */
private credentialsStorageKey = 'mifosXCredentials';
/** Key to store oauth token details in storage. */
private oAuthTokenDetailsStorageKey = 'mifosXOAuthTokenDetails';
/** Key to store two factor authentication token in storage. */
private twoFactorAuthenticationTokenStorageKey = 'mifosXTwoFactorAuthenticationToken';
/**
* Initializes the type of storage and authorization headers depending on whether
* credentials are presently in storage or not.
* @param {HttpClient} http Http Client to send requests.
* @param {AlertService} alertService Alert Service.
* @param {AuthenticationInterceptor} authenticationInterceptor Authentication Interceptor.
*/
constructor(private http: HttpClient, constructor(private http: HttpClient,
private alertService: AlertService, private alertService: AlertService,
private authenticationInterceptor: AuthenticationInterceptor) { private authenticationInterceptor: AuthenticationInterceptor) {
this.rememberMe = false; this.rememberMe = false;
this.storage = sessionStorage; this.storage = sessionStorage;
const savedCredentials = JSON.parse( const savedCredentials = JSON.parse(
sessionStorage.getItem(credentialsStorageKey) || localStorage.getItem(credentialsStorageKey) sessionStorage.getItem(this.credentialsStorageKey) || localStorage.getItem(this.credentialsStorageKey)
); );
if (savedCredentials) { if (savedCredentials) {
if (savedCredentials.rememberMe) { if (savedCredentials.rememberMe) {
this.rememberMe = true; this.rememberMe = true;
this.storage = localStorage; this.storage = localStorage;
} }
const twoFactorAccessToken = JSON.parse(this.storage.getItem(twoFactorAuthenticationTokenStorageKey)); const twoFactorAccessToken = JSON.parse(this.storage.getItem(this.twoFactorAuthenticationTokenStorageKey));
if (environment.oauth.enabled) { if (environment.oauth.enabled) {
this.refreshOAuthAccessToken(); this.refreshOAuthAccessToken();
} else { } else {
@@ -83,16 +80,22 @@ export class AuthenticationService {
/** /**
* Authenticates the user. * Authenticates the user.
* @param {LoginContext} loginContext The login parameters. * @param {LoginContext} loginContext Login parameters.
* @return {Observable<any>} * @returns {Observable<boolean>} True if authentication is successful.
*/ */
login(loginContext: LoginContext) { login(loginContext: LoginContext) {
this.alertService.alert({ type: 'Authentication Start', message: 'Please wait...' }); this.alertService.alert({ type: 'Authentication Start', message: 'Please wait...' });
this.rememberMe = loginContext.remember; this.rememberMe = loginContext.remember;
this.storage = this.rememberMe ? localStorage : sessionStorage; this.storage = this.rememberMe ? localStorage : sessionStorage;
let httpParams = new HttpParams();
httpParams = httpParams.set('username', loginContext.username);
httpParams = httpParams.set('password', loginContext.password);
if (environment.oauth.enabled) { if (environment.oauth.enabled) {
return this.http.disableApiPrefix().post(`${environment.oauth.serverUrl}/oauth/token?username=${loginContext.username}&password=${loginContext.password}&client_id=community-app&grant_type=password&client_secret=123`, {}) httpParams = httpParams.set('client_id', 'community-app');
httpParams = httpParams.set('grant_type', 'password');
httpParams = httpParams.set('client_secret', '123');
return this.http.disableApiPrefix().post(`${environment.oauth.serverUrl}/oauth/token`, {}, { params: httpParams })
.pipe( .pipe(
map((tokenResponse: OAuth2Token) => { map((tokenResponse: OAuth2Token) => {
this.getUserDetails(tokenResponse); this.getUserDetails(tokenResponse);
@@ -100,7 +103,7 @@ export class AuthenticationService {
}) })
); );
} else { } else {
return this.http.post(`/authentication?username=${loginContext.username}&password=${loginContext.password}`, {}) return this.http.post('/authentication', {}, { params: httpParams })
.pipe( .pipe(
map((credentials: Credentials) => { map((credentials: Credentials) => {
this.onLoginSuccess(credentials); this.onLoginSuccess(credentials);
@@ -110,93 +113,64 @@ export class AuthenticationService {
} }
} }
getUserDetails(tokenResponse: OAuth2Token) { /**
* Retrieves the user details after oauth2 authentication.
*
* Sets the oauth2 token refresh time.
* @param {OAuth2Token} tokenResponse OAuth2 Token details.
*/
private getUserDetails(tokenResponse: OAuth2Token) {
const httpParams = new HttpParams().set('access_token', tokenResponse.access_token);
this.refreshTokenOnExpiry(tokenResponse.expires_in); this.refreshTokenOnExpiry(tokenResponse.expires_in);
this.http.get(`/userdetails?access_token=${tokenResponse.access_token}`) this.http.get('/userdetails', { params: httpParams })
.subscribe((credentials: Credentials) => { .subscribe((credentials: Credentials) => {
this.onLoginSuccess(credentials); this.onLoginSuccess(credentials);
if (!credentials.shouldRenewPassword) { if (!credentials.shouldRenewPassword) {
this.storage.setItem(oAuthTokenDetailsStorageKey, JSON.stringify(tokenResponse)); this.storage.setItem(this.oAuthTokenDetailsStorageKey, JSON.stringify(tokenResponse));
} }
}); });
} }
refreshTokenOnExpiry(expiresInTime: number) { /**
* Sets the oauth2 token to refresh on expiry.
* @param {number} expiresInTime OAuth2 token expiry time in seconds.
*/
private refreshTokenOnExpiry(expiresInTime: number) {
setTimeout(() => this.refreshOAuthAccessToken(), expiresInTime * 1000); setTimeout(() => this.refreshOAuthAccessToken(), expiresInTime * 1000);
} }
refreshOAuthAccessToken() { /**
const oAuthRefreshToken = JSON.parse(this.storage.getItem(oAuthTokenDetailsStorageKey)).refresh_token; * Refreshes the oauth2 authorization token.
*/
private refreshOAuthAccessToken() {
const oAuthRefreshToken = JSON.parse(this.storage.getItem(this.oAuthTokenDetailsStorageKey)).refresh_token;
this.authenticationInterceptor.removeAuthorization(); this.authenticationInterceptor.removeAuthorization();
this.http.disableApiPrefix().post(`${environment.oauth.serverUrl}/oauth/token?client_id=community-app&grant_type=refresh_token&client_secret=123&refresh_token=${oAuthRefreshToken}`, {}) let httpParams = new HttpParams();
httpParams = httpParams.set('client_id', 'community-app');
httpParams = httpParams.set('grant_type', 'refresh_token');
httpParams = httpParams.set('client_secret', '123');
httpParams = httpParams.set('refresh_token', oAuthRefreshToken);
this.http.disableApiPrefix().post(`${environment.oauth.serverUrl}/oauth/token`, {}, { params: httpParams })
.subscribe((tokenResponse: OAuth2Token) => { .subscribe((tokenResponse: OAuth2Token) => {
this.storage.setItem(oAuthTokenDetailsStorageKey, JSON.stringify(tokenResponse)); this.storage.setItem(this.oAuthTokenDetailsStorageKey, JSON.stringify(tokenResponse));
this.authenticationInterceptor.setAuthorizationToken(tokenResponse.access_token); this.authenticationInterceptor.setAuthorizationToken(tokenResponse.access_token);
this.refreshTokenOnExpiry(tokenResponse.expires_in); this.refreshTokenOnExpiry(tokenResponse.expires_in);
const credentials = JSON.parse(this.storage.getItem(credentialsStorageKey)); const credentials = JSON.parse(this.storage.getItem(this.credentialsStorageKey));
credentials.accessToken = tokenResponse.access_token; credentials.accessToken = tokenResponse.access_token;
this.storage.setItem(credentialsStorageKey, JSON.stringify(credentials)); this.storage.setItem(this.credentialsStorageKey, JSON.stringify(credentials));
}); });
} }
/** /**
* Logs out the user and clear credentials. * Sets the authorization token followed by one of the following:
* @return {Observable<boolean>} True if the user was logged out successfully. *
* Sends an alert if two factor authentication is required.
*
* Sends an alert if password has expired and requires a reset.
*
* Sends an alert on successful login.
* @param {Credentials} credentials Authenticated user credentials.
*/ */
logout(): Observable<boolean> {
const twoFactorToken = JSON.parse(this.storage.getItem(twoFactorAuthenticationTokenStorageKey));
if (twoFactorToken) {
this.http.post('/twofactor/invalidate', { token: twoFactorToken.token }).subscribe();
this.authenticationInterceptor.removeTwoFactorAuthorization();
}
this.authenticationInterceptor.removeAuthorization();
this.setCredentials();
return of(true);
}
/**
* Checks is the user is authenticated.
* @return {boolean} True if the user is authenticated.
*/
isAuthenticated(): boolean {
return !!(JSON.parse(
sessionStorage.getItem(credentialsStorageKey) || localStorage.getItem(credentialsStorageKey)
) && this.twoFactorAccessTokenIsValid());
}
twoFactorAccessTokenIsValid(): boolean {
const twoFactorAccessToken = JSON.parse(this.storage.getItem(twoFactorAuthenticationTokenStorageKey));
if (twoFactorAccessToken) {
return ((new Date()).getTime() < twoFactorAccessToken.validTo);
}
return true;
}
/**
* Gets the user credentials.
* @return {Credentials} The user credentials or null if the user is not authenticated.
*/
getCredentials(): Credentials {
return JSON.parse(this.storage.getItem(credentialsStorageKey));
}
/**
* Sets the user credentials.
* The credentials may be persisted across sessions by setting the `remember` parameter to true.
* Otherwise, the credentials are only persisted for the current session.
* @param {Credentials=} credentials The user credentials.
*/
private setCredentials(credentials?: Credentials) {
if (credentials) {
credentials.rememberMe = this.rememberMe;
this.storage.setItem(credentialsStorageKey, JSON.stringify(credentials));
} else {
this.storage.removeItem(credentialsStorageKey);
this.storage.removeItem(oAuthTokenDetailsStorageKey);
this.storage.removeItem(twoFactorAuthenticationTokenStorageKey);
}
}
private onLoginSuccess(credentials: Credentials) { private onLoginSuccess(credentials: Credentials) {
if (environment.oauth.enabled) { if (environment.oauth.enabled) {
this.authenticationInterceptor.setAuthorizationToken(credentials.accessToken); this.authenticationInterceptor.setAuthorizationToken(credentials.accessToken);
@@ -218,21 +192,100 @@ export class AuthenticationService {
} }
} }
/**
* Logs out the authenticated user and clears the credentials from storage.
* @returns {Observable<boolean>} True if the user was logged out successfully.
*/
logout(): Observable<boolean> {
const twoFactorToken = JSON.parse(this.storage.getItem(this.twoFactorAuthenticationTokenStorageKey));
if (twoFactorToken) {
this.http.post('/twofactor/invalidate', { token: twoFactorToken.token }).subscribe();
this.authenticationInterceptor.removeTwoFactorAuthorization();
}
this.authenticationInterceptor.removeAuthorization();
this.setCredentials();
return of(true);
}
// Following functions require first level authorization headers to be setup /**
* Checks if the two factor access token for authenticated user is valid.
* @returns {boolean} True if the two factor access token is valid or two factor authentication is not required.
*/
twoFactorAccessTokenIsValid(): boolean {
const twoFactorAccessToken = JSON.parse(this.storage.getItem(this.twoFactorAuthenticationTokenStorageKey));
if (twoFactorAccessToken) {
return ((new Date()).getTime() < twoFactorAccessToken.validTo);
}
return true;
}
/**
* Checks if the user is authenticated.
* @returns {boolean} True if the user is authenticated.
*/
isAuthenticated(): boolean {
return !!(JSON.parse(
sessionStorage.getItem(this.credentialsStorageKey) || localStorage.getItem(this.credentialsStorageKey)
) && this.twoFactorAccessTokenIsValid());
}
/**
* Gets the user credentials.
* @returns {Credentials} The user credentials if the user is authenticated otherwise null.
*/
getCredentials(): Credentials | null {
return JSON.parse(this.storage.getItem(this.credentialsStorageKey));
}
/**
* Sets the user credentials.
*
* The credentials may be persisted across sessions by setting the `rememberMe` parameter to true.
* Otherwise, the credentials are only persisted for the current session.
*
* @param {Credentials} credentials Authenticated user credentials.
*/
private setCredentials(credentials?: Credentials) {
if (credentials) {
credentials.rememberMe = this.rememberMe;
this.storage.setItem(this.credentialsStorageKey, JSON.stringify(credentials));
} else {
this.storage.removeItem(this.credentialsStorageKey);
this.storage.removeItem(this.oAuthTokenDetailsStorageKey);
this.storage.removeItem(this.twoFactorAuthenticationTokenStorageKey);
}
}
/**
* Following functions are for two factor authentication and require
* first level authorization headers to be setup for the requests.
*/
/**
* Gets the two factor authentication delivery methods available for the user.
*/
getDeliveryMethods() { getDeliveryMethods() {
return this.http.get('/twofactor').pipe(map(response => { return this.http.get('/twofactor');
return response;
}));
} }
/**
* Requests OTP to be sent via the given delivery method.
* @param {any} deliveryMethod Delivery method for the OTP.
*/
requestOTP(deliveryMethod: any) { requestOTP(deliveryMethod: any) {
return this.http.post(`/twofactor?deliveryMethod=${deliveryMethod.name}&extendedToken=${this.rememberMe}`, {}); let httpParams = new HttpParams();
httpParams = httpParams.set('deliveryMethod', deliveryMethod.name);
httpParams = httpParams.set('extendedToken', this.rememberMe.toString());
return this.http.post(`/twofactor`, {}, { params: httpParams });
} }
/**
* Validates the OTP and authenticates the user on success.
* @param {string} otp
*/
validateOTP(otp: string) { validateOTP(otp: string) {
return this.http.post(`/twofactor/validate?token=${otp}`, {}) const httpParams = new HttpParams().set('token', otp);
return this.http.post(`/twofactor/validate`, {}, { params: httpParams })
.pipe( .pipe(
map(response => { map(response => {
this.onOTPValidateSuccess(response); this.onOTPValidateSuccess(response);
@@ -240,6 +293,14 @@ export class AuthenticationService {
); );
} }
/**
* Sets the two factor authorization token followed by one of the following:
*
* Sends an alert if password has expired and requires a reset.
*
* Sends an alert on successful login.
* @param {any} response Two factor authentication token details.
*/
private onOTPValidateSuccess(response: any) { private onOTPValidateSuccess(response: any) {
this.authenticationInterceptor.setTwoFactorAccessToken(response.token); this.authenticationInterceptor.setTwoFactorAccessToken(response.token);
if (this.credentials.shouldRenewPassword) { if (this.credentials.shouldRenewPassword) {
@@ -248,10 +309,14 @@ export class AuthenticationService {
this.setCredentials(this.credentials); this.setCredentials(this.credentials);
this.alertService.alert({ type: 'Authentication Success', message: `${this.credentials.username} successfully logged in!` }); this.alertService.alert({ type: 'Authentication Success', message: `${this.credentials.username} successfully logged in!` });
delete this.credentials; delete this.credentials;
this.storage.setItem(twoFactorAuthenticationTokenStorageKey, JSON.stringify(response)); this.storage.setItem(this.twoFactorAuthenticationTokenStorageKey, JSON.stringify(response));
} }
} }
/**
* Resets the user's password and authenticates the user.
* @param {any} passwordDetails New password.
*/
resetPassword(passwordDetails: any) { resetPassword(passwordDetails: any) {
return this.http.put(`/users/${this.credentials.userId}`, passwordDetails). return this.http.put(`/users/${this.credentials.userId}`, passwordDetails).
pipe( pipe(
@@ -0,0 +1,20 @@
/**
* Credentials model.
*/
export interface Credentials {
accessToken?: string;
authenticated: boolean;
base64EncodedAuthenticationKey?: string;
isTwoFactorAuthenticationRequired?: boolean;
officeId: number;
officeName: string;
staffId?: number;
staffDisplayName?: string;
organisationalRole?: any;
permissions: string[];
roles: any;
userId: number;
username: string;
shouldRenewPassword: boolean;
rememberMe?: boolean;
}
@@ -0,0 +1,8 @@
/**
* Login context model.
*/
export interface LoginContext {
username: string;
password: string;
remember: boolean;
}
@@ -0,0 +1,10 @@
/**
* OAuth2 token model.
*/
export interface OAuth2Token {
access_token: string;
token_type: string;
refresh_token: string;
expires_in: number;
scope: string;
}
+37 -92
View File
@@ -1,66 +1,46 @@
/** Angular Imports */
import { NgModule, Optional, SkipSelf } from '@angular/core'; import { NgModule, Optional, SkipSelf } from '@angular/core';
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http'; import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
import { RouteReuseStrategy, RouterModule } from '@angular/router'; import { RouteReuseStrategy, RouterModule } from '@angular/router';
// To be removed, replace by shared material module /** Translation Imports */
import { LayoutModule } from '@angular/cdk/layout';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatCheckboxModule,
MatButtonModule,
MatInputModule,
MatAutocompleteModule,
MatDatepickerModule,
MatFormFieldModule,
MatRadioModule,
MatSelectModule,
MatSliderModule,
MatSlideToggleModule,
MatMenuModule,
MatSidenavModule,
MatToolbarModule,
MatListModule,
MatGridListModule,
MatCardModule,
MatStepperModule,
MatTabsModule,
MatExpansionModule,
MatButtonToggleModule,
MatChipsModule,
MatIconModule,
MatProgressSpinnerModule,
MatProgressBarModule,
MatDialogModule,
MatTooltipModule,
MatSnackBarModule,
MatTableModule,
MatTreeModule,
MatSortModule,
MatPaginatorModule,
MatDividerModule,
MatNativeDateModule} from '@angular/material';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { RouteReusableStrategy } from './route-reusable-strategy'; /** Custom Services */
import { AuthenticationService } from './authentication/authentication.service'; import { AuthenticationService } from './authentication/authentication.service';
import { AuthenticationGuard } from './authentication/authentication.guard';
import { HttpService } from './http/http.service'; import { HttpService } from './http/http.service';
import { HttpCacheService } from './http/http-cache.service'; import { HttpCacheService } from './http/http-cache.service';
import { ProgressBarService } from './progress-bar/progress-bar.service'; import { ProgressBarService } from './progress-bar/progress-bar.service';
import { I18nService } from './i18n/i18n.service';
/** Custom Guards */
import { AuthenticationGuard } from './authentication/authentication.guard';
/** Custom Interceptors */
import { ProgressInterceptor } from './progress-bar/progress.interceptor'; import { ProgressInterceptor } from './progress-bar/progress.interceptor';
import { ApiPrefixInterceptor } from './http/api-prefix.interceptor'; import { ApiPrefixInterceptor } from './http/api-prefix.interceptor';
import { ErrorHandlerInterceptor } from './http/error-handler.interceptor'; import { ErrorHandlerInterceptor } from './http/error-handler.interceptor';
import { CacheInterceptor } from './http/cache.interceptor'; import { CacheInterceptor } from './http/cache.interceptor';
import { AuthenticationInterceptor } from './authentication/authentication.interceptor';
/** Custom Strategies */
import { RouteReusableStrategy } from './route/route-reusable-strategy';
/** Custom Modules */
import { SharedModule } from '../shared';
/** Custom Components */
import { ShellComponent } from './shell/shell.component'; import { ShellComponent } from './shell/shell.component';
import { SidenavComponent } from './shell/sidenav/sidenav.component'; import { SidenavComponent } from './shell/sidenav/sidenav.component';
import { ToolbarComponent } from './shell/toolbar/toolbar.component'; import { ToolbarComponent } from './shell/toolbar/toolbar.component';
import { ContentComponent } from './shell/content/content.component';
import { BreadcrumbComponent } from './shell/breadcrumb/breadcrumb.component'; import { BreadcrumbComponent } from './shell/breadcrumb/breadcrumb.component';
import { AuthenticationInterceptor } from './authentication/authentication.interceptor'; import { ContentComponent } from './shell/content/content.component';
import { SharedModule } from '../shared';
/**
* Core Module
*
* Main app shell components and singleton services should be here.
*/
@NgModule({ @NgModule({
imports: [ imports: [
SharedModule, SharedModule,
@@ -72,71 +52,36 @@ import { SharedModule } from '../shared';
ShellComponent, ShellComponent,
SidenavComponent, SidenavComponent,
ToolbarComponent, ToolbarComponent,
ContentComponent, BreadcrumbComponent,
BreadcrumbComponent ContentComponent
], ],
exports: [ exports: [
LayoutModule, SharedModule // TO BE REMOVED: Once all components have replaced the core module import by shared module.
FlexLayoutModule,
MatCheckboxModule,
MatCheckboxModule,
MatButtonModule,
MatInputModule,
MatAutocompleteModule,
MatDatepickerModule,
MatNativeDateModule,
MatFormFieldModule,
MatRadioModule,
MatSelectModule,
MatSliderModule,
MatSlideToggleModule,
MatMenuModule,
MatSidenavModule,
MatToolbarModule,
MatListModule,
MatGridListModule,
MatCardModule,
MatStepperModule,
MatTabsModule,
MatExpansionModule,
MatButtonToggleModule,
MatChipsModule,
MatIconModule,
MatProgressSpinnerModule,
MatProgressBarModule,
MatDialogModule,
MatTooltipModule,
MatSnackBarModule,
MatTableModule,
MatTreeModule,
MatSortModule,
MatPaginatorModule,
MatDividerModule
], ],
providers: [ providers: [
AuthenticationService, AuthenticationService,
AuthenticationGuard, AuthenticationGuard,
HttpCacheService,
ProgressBarService,
ProgressInterceptor,
ApiPrefixInterceptor,
ErrorHandlerInterceptor,
CacheInterceptor,
AuthenticationInterceptor, AuthenticationInterceptor,
{
provide: HTTP_INTERCEPTORS,
useClass: ProgressInterceptor,
multi: true
},
{ {
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,
useClass: AuthenticationInterceptor, useClass: AuthenticationInterceptor,
multi: true multi: true
}, },
I18nService,
HttpCacheService,
ApiPrefixInterceptor,
ErrorHandlerInterceptor,
CacheInterceptor,
{ {
provide: HttpClient, provide: HttpClient,
useClass: HttpService useClass: HttpService
}, },
ProgressBarService,
{
provide: HTTP_INTERCEPTORS,
useClass: ProgressInterceptor,
multi: true
},
{ {
provide: RouteReuseStrategy, provide: RouteReuseStrategy,
useClass: RouteReusableStrategy useClass: RouteReusableStrategy
@@ -1,44 +1,44 @@
import { TestBed, inject } from '@angular/core/testing'; // import { TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; // import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; // import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
import { environment } from '../../../environments/environment'; // import { environment } from '../../../environments/environment';
import { ApiPrefixInterceptor } from './api-prefix.interceptor'; // import { ApiPrefixInterceptor } from './api-prefix.interceptor';
describe('ApiPrefixInterceptor', () => { // describe('ApiPrefixInterceptor', () => {
let http: HttpClient; // let http: HttpClient;
let httpMock: HttpTestingController; // let httpMock: HttpTestingController;
beforeEach(() => { // beforeEach(() => {
TestBed.configureTestingModule({ // TestBed.configureTestingModule({
imports: [HttpClientTestingModule], // imports: [HttpClientTestingModule],
providers: [{ // providers: [{
provide: HTTP_INTERCEPTORS, // provide: HTTP_INTERCEPTORS,
useClass: ApiPrefixInterceptor, // useClass: ApiPrefixInterceptor,
multi: true // multi: true
}] // }]
}); // });
}); // });
beforeEach(inject([ // beforeEach(inject([
HttpClient, // HttpClient,
HttpTestingController // HttpTestingController
], (_http: HttpClient, // ], (_http: HttpClient,
_httpMock: HttpTestingController) => { // _httpMock: HttpTestingController) => {
http = _http; // http = _http;
httpMock = _httpMock; // httpMock = _httpMock;
})); // }));
afterEach(() => { // afterEach(() => {
httpMock.verify(); // httpMock.verify();
}); // });
it('should prepend environment.serverUrl to the request url', () => { // it('should prepend environment.serverUrl to the request url', () => {
// Act // // Act
http.get('/toto').subscribe(); // http.get('/toto').subscribe();
// Assert // // Assert
httpMock.expectOne({ url: environment.serverUrl + '/toto' }); // httpMock.expectOne({ url: environment.serverUrl + '/toto' });
}); // });
}); // });
+8 -2
View File
@@ -1,16 +1,22 @@
/** Angular Imports */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
/** rxjs Imports */
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment'; /** Environment Configuration */
import { environment } from 'environments/environment';
/** /**
* Prefixes all requests with `environment.serverUrl`. * Http request interceptor to prefix a request with `environment.serverUrl`.
*/ */
@Injectable() @Injectable()
export class ApiPrefixInterceptor implements HttpInterceptor { export class ApiPrefixInterceptor implements HttpInterceptor {
/**
* Intercepts a Http request and prefixes it with `environment.serverUrl`.
*/
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
request = request.clone({ url: environment.serverUrl + request.url }); request = request.clone({ url: environment.serverUrl + request.url });
return next.handle(request); return next.handle(request);
+112 -112
View File
@@ -1,135 +1,135 @@
import { TestBed, inject } from '@angular/core/testing'; // import { TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; // import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { HTTP_INTERCEPTORS, HttpClient, HttpResponse } from '@angular/common/http'; // import { HTTP_INTERCEPTORS, HttpClient, HttpResponse } from '@angular/common/http';
import { CacheInterceptor } from './cache.interceptor'; // import { CacheInterceptor } from './cache.interceptor';
import { HttpCacheService } from './http-cache.service'; // import { HttpCacheService } from './http-cache.service';
describe('CacheInterceptor', () => { // describe('CacheInterceptor', () => {
let interceptorOptions: Object | null = {}; // let interceptorOptions: Object | null = {};
let httpCacheService: HttpCacheService; // let httpCacheService: HttpCacheService;
let cacheInterceptor: CacheInterceptor; // let cacheInterceptor: CacheInterceptor;
let http: HttpClient; // let http: HttpClient;
let httpMock: HttpTestingController; // let httpMock: HttpTestingController;
function createInterceptor(_httpCacheService: HttpCacheService) { // function createInterceptor(_httpCacheService: HttpCacheService) {
cacheInterceptor = new CacheInterceptor(_httpCacheService).configure(interceptorOptions); // cacheInterceptor = new CacheInterceptor(_httpCacheService).configure(interceptorOptions);
return cacheInterceptor; // return cacheInterceptor;
} // }
beforeEach(() => { // beforeEach(() => {
TestBed.configureTestingModule({ // TestBed.configureTestingModule({
imports: [HttpClientTestingModule], // imports: [HttpClientTestingModule],
providers: [ // providers: [
HttpCacheService, // HttpCacheService,
{ // {
provide: HTTP_INTERCEPTORS, // provide: HTTP_INTERCEPTORS,
useFactory: createInterceptor, // useFactory: createInterceptor,
deps: [HttpCacheService], // deps: [HttpCacheService],
multi: true // multi: true
} // }
] // ]
}); // });
}); // });
afterEach(() => { // afterEach(() => {
httpCacheService.cleanCache(); // httpCacheService.cleanCache();
httpMock.verify(); // httpMock.verify();
}); // });
describe('with default configuration', () => { // describe('with default configuration', () => {
beforeEach(() => { // beforeEach(() => {
interceptorOptions = null; // interceptorOptions = null;
}); // });
beforeEach(inject([ // beforeEach(inject([
HttpClient, // HttpClient,
HttpTestingController, // HttpTestingController,
HttpCacheService // HttpCacheService
], (_http: HttpClient, // ], (_http: HttpClient,
_httpMock: HttpTestingController, // _httpMock: HttpTestingController,
_httpCacheService: HttpCacheService) => { // _httpCacheService: HttpCacheService) => {
http = _http; // http = _http;
httpMock = _httpMock; // httpMock = _httpMock;
httpCacheService = _httpCacheService; // httpCacheService = _httpCacheService;
})); // }));
it('should cache the request', () => { // it('should cache the request', () => {
// Act // // Act
http.get('/toto').subscribe(() => { // http.get('/toto').subscribe(() => {
// Assert // // Assert
const cachedData = httpCacheService.getCacheData('/toto'); // const cachedData = httpCacheService.getCacheData('/toto');
expect(cachedData).toBeDefined(); // expect(cachedData).toBeDefined();
expect(cachedData ? cachedData.body : null).toEqual('someData'); // expect(cachedData ? cachedData.body : null).toEqual('someData');
}); // });
httpMock.expectOne({ url: '/toto' }).flush('someData'); // httpMock.expectOne({ url: '/toto' }).flush('someData');
}); // });
it('should respond from the cache', () => { // it('should respond from the cache', () => {
// Arrange // // Arrange
httpCacheService.setCacheData('/toto', new HttpResponse({ body: 'cachedData' })); // httpCacheService.setCacheData('/toto', new HttpResponse({ body: 'cachedData' }));
// Act // // Act
http.get('/toto').subscribe(response => { // http.get('/toto').subscribe(response => {
// Assert // // Assert
expect(response).toEqual('cachedData'); // expect(response).toEqual('cachedData');
}); // });
httpMock.expectNone({ url: '/toto' }); // httpMock.expectNone({ url: '/toto' });
}); // });
it('should not cache the request in case of error', () => { // it('should not cache the request in case of error', () => {
// Act // // Act
http.get('/toto').subscribe(() => {}, () => { // http.get('/toto').subscribe(() => {}, () => {
// Assert // // Assert
expect(httpCacheService.getCacheData('/toto')).toBeNull(); // expect(httpCacheService.getCacheData('/toto')).toBeNull();
}); // });
httpMock.expectOne({}).flush(null, { // httpMock.expectOne({}).flush(null, {
status: 404, // status: 404,
statusText: 'error' // statusText: 'error'
}); // });
}); // });
}); // });
describe('with update forced configuration', () => { // describe('with update forced configuration', () => {
beforeEach(() => { // beforeEach(() => {
interceptorOptions = { update: true }; // interceptorOptions = { update: true };
}); // });
beforeEach(inject([ // beforeEach(inject([
HttpClient, // HttpClient,
HttpTestingController, // HttpTestingController,
HttpCacheService // HttpCacheService
], (_http: HttpClient, // ], (_http: HttpClient,
_httpMock: HttpTestingController, // _httpMock: HttpTestingController,
_httpCacheService: HttpCacheService) => { // _httpCacheService: HttpCacheService) => {
http = _http; // http = _http;
httpMock = _httpMock; // httpMock = _httpMock;
httpCacheService = _httpCacheService; // httpCacheService = _httpCacheService;
})); // }));
afterEach(() => { // afterEach(() => {
httpCacheService.cleanCache(); // httpCacheService.cleanCache();
httpMock.verify(); // httpMock.verify();
}); // });
it('should force cache update', () => { // it('should force cache update', () => {
// Arrange // // Arrange
httpCacheService.setCacheData('/toto', new HttpResponse({ body: 'oldCachedData' })); // httpCacheService.setCacheData('/toto', new HttpResponse({ body: 'oldCachedData' }));
cacheInterceptor.configure({ update: true }); // cacheInterceptor.configure({ update: true });
// Act // // Act
http.get('/toto').subscribe(response => { // http.get('/toto').subscribe(response => {
// Assert // // Assert
expect(response).toEqual('newData'); // expect(response).toEqual('newData');
}); // });
httpMock.expectOne({ url: '/toto' }).flush('newData'); // httpMock.expectOne({ url: '/toto' }).flush('newData');
}); // });
}); // });
}); // });
+3
View File
@@ -1,8 +1,11 @@
/** Angular Imports */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http';
/** rxjs Imports */
import { Observable, Subscriber } from 'rxjs'; import { Observable, Subscriber } from 'rxjs';
/** Custom Services */
import { HttpCacheService } from './http-cache.service'; import { HttpCacheService } from './http-cache.service';
/** /**
@@ -1,59 +1,59 @@
import { TestBed, inject } from '@angular/core/testing'; // import { TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; // import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; // import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
import { ErrorHandlerInterceptor } from './error-handler.interceptor'; // import { ErrorHandlerInterceptor } from './error-handler.interceptor';
describe('ErrorHandlerInterceptor', () => { // describe('ErrorHandlerInterceptor', () => {
let errorHandlerInterceptor: ErrorHandlerInterceptor; // let errorHandlerInterceptor: ErrorHandlerInterceptor;
let http: HttpClient; // let http: HttpClient;
let httpMock: HttpTestingController; // let httpMock: HttpTestingController;
function createInterceptor() { // function createInterceptor() {
errorHandlerInterceptor = new ErrorHandlerInterceptor(); // errorHandlerInterceptor = new ErrorHandlerInterceptor();
return errorHandlerInterceptor; // return errorHandlerInterceptor;
} // }
beforeEach(() => { // beforeEach(() => {
TestBed.configureTestingModule({ // TestBed.configureTestingModule({
imports: [HttpClientTestingModule], // imports: [HttpClientTestingModule],
providers: [{ // providers: [{
provide: HTTP_INTERCEPTORS, // provide: HTTP_INTERCEPTORS,
useFactory: createInterceptor, // useFactory: createInterceptor,
multi: true // multi: true
}] // }]
}); // });
}); // });
beforeEach(inject([ // beforeEach(inject([
HttpClient, // HttpClient,
HttpTestingController // HttpTestingController
], (_http: HttpClient, // ], (_http: HttpClient,
_httpMock: HttpTestingController) => { // _httpMock: HttpTestingController) => {
http = _http; // http = _http;
httpMock = _httpMock; // httpMock = _httpMock;
})); // }));
afterEach(() => { // afterEach(() => {
httpMock.verify(); // httpMock.verify();
}); // });
it('should catch error and call error handler', () => { // it('should catch error and call error handler', () => {
// Arrange // // Arrange
// Note: here we spy on private method since target is customization here, // // Note: here we spy on private method since target is customization here,
// but you should replace it by actual behavior in your app // // but you should replace it by actual behavior in your app
spyOn(ErrorHandlerInterceptor.prototype as any, 'errorHandler').and.callThrough(); // spyOn(ErrorHandlerInterceptor.prototype as any, 'errorHandler').and.callThrough();
// Act // // Act
http.get('/toto').subscribe(() => fail('should error'), () => { // http.get('/toto').subscribe(() => fail('should error'), () => {
// Assert // // Assert
expect(ErrorHandlerInterceptor.prototype['errorHandler']).toHaveBeenCalled(); // expect(ErrorHandlerInterceptor.prototype['errorHandler']).toHaveBeenCalled();
}); // });
httpMock.expectOne({}).flush(null, { // httpMock.expectOne({}).flush(null, {
status: 404, // status: 404,
statusText: 'error' // statusText: 'error'
}); // });
}); // });
}); // });
+19 -5
View File
@@ -1,28 +1,42 @@
/** Angular Imports */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
/** rxjs Imports */
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
import { environment } from '../../../environments/environment'; /** Environment Configuration */
import { Logger } from '../logger.service'; import { environment } from 'environments/environment';
import { AlertService } from '../alert.service';
/** Custom Services */
import { Logger } from '../logger/logger.service';
import { AlertService } from '../alert/alert.service';
/** Initialize Logger */
const log = new Logger('ErrorHandlerInterceptor'); const log = new Logger('ErrorHandlerInterceptor');
/** /**
* Adds a default error handler to all requests. * Http Request interceptor to add a default error handler to requests.
*/ */
@Injectable() @Injectable()
export class ErrorHandlerInterceptor implements HttpInterceptor { export class ErrorHandlerInterceptor implements HttpInterceptor {
/**
* @param {AlertService} alertService Alert Service.
*/
constructor(private alertService: AlertService) { } constructor(private alertService: AlertService) { }
/**
* Intercepts a Http request and adds a default error handler.
*/
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(catchError(error => this.handleError(error))); return next.handle(request).pipe(catchError(error => this.handleError(error)));
} }
// Customize the default error handler here if needed /**
* Error handler.
*/
private handleError(response: HttpErrorResponse): Observable<HttpEvent<any>> { private handleError(response: HttpErrorResponse): Observable<HttpEvent<any>> {
const status = response.status; const status = response.status;
let errorMessage = (response.error.developerMessage || response.message); let errorMessage = (response.error.developerMessage || response.message);
+152 -152
View File
@@ -1,194 +1,194 @@
import { TestBed, inject } from '@angular/core/testing'; // import { TestBed, inject } from '@angular/core/testing';
import { HttpResponse } from '@angular/common/http'; // import { HttpResponse } from '@angular/common/http';
import { HttpCacheService, HttpCacheEntry } from './http-cache.service'; // import { HttpCacheService, HttpCacheEntry } from './http-cache.service';
const cachePersistenceKey = 'httpCache'; // const cachePersistenceKey = 'httpCache';
describe('HttpCacheService', () => { // describe('HttpCacheService', () => {
let httpCacheService: HttpCacheService; // let httpCacheService: HttpCacheService;
let response: HttpResponse<any>; // let response: HttpResponse<any>;
beforeEach(() => { // beforeEach(() => {
TestBed.configureTestingModule({ // TestBed.configureTestingModule({
providers: [HttpCacheService] // providers: [HttpCacheService]
}); // });
// Start fresh // // Start fresh
window.sessionStorage.removeItem(cachePersistenceKey); // window.sessionStorage.removeItem(cachePersistenceKey);
window.localStorage.removeItem(cachePersistenceKey); // window.localStorage.removeItem(cachePersistenceKey);
}); // });
beforeEach(inject([HttpCacheService], (_httpCacheService: HttpCacheService) => { // beforeEach(inject([HttpCacheService], (_httpCacheService: HttpCacheService) => {
httpCacheService = _httpCacheService; // httpCacheService = _httpCacheService;
response = new HttpResponse({ body: 'data' }); // response = new HttpResponse({ body: 'data' });
})); // }));
afterEach(() => { // afterEach(() => {
httpCacheService.cleanCache(); // httpCacheService.cleanCache();
}); // });
describe('setCacheData', () => { // describe('setCacheData', () => {
it('should set cache data', () => { // it('should set cache data', () => {
// Act // // Act
httpCacheService.setCacheData('/popo', response); // httpCacheService.setCacheData('/popo', response);
// Assert // // Assert
expect(httpCacheService.getCacheData('/popo')).toEqual(response); // expect(httpCacheService.getCacheData('/popo')).toEqual(response);
}); // });
it('should replace existing data', () => { // it('should replace existing data', () => {
// Arrange // // Arrange
const newResponse = new HttpResponse({ body: 'new data' }); // const newResponse = new HttpResponse({ body: 'new data' });
// Act // // Act
httpCacheService.setCacheData('/popo', response); // httpCacheService.setCacheData('/popo', response);
httpCacheService.setCacheData('/popo', newResponse); // httpCacheService.setCacheData('/popo', newResponse);
// Assert // // Assert
expect(httpCacheService.getCacheData('/popo')).toEqual(newResponse); // expect(httpCacheService.getCacheData('/popo')).toEqual(newResponse);
}); // });
it('should set cache date correctly', () => { // it('should set cache date correctly', () => {
// Act // // Act
const date = new Date(123); // const date = new Date(123);
httpCacheService.setCacheData('/popo', response, date); // httpCacheService.setCacheData('/popo', response, date);
httpCacheService.setCacheData('/hoho', response); // httpCacheService.setCacheData('/hoho', response);
// Assert // // Assert
expect((<HttpCacheEntry>httpCacheService.getHttpCacheEntry('/popo')).lastUpdated).toBe(date); // expect((<HttpCacheEntry>httpCacheService.getHttpCacheEntry('/popo')).lastUpdated).toBe(date);
expect((<HttpCacheEntry>httpCacheService.getHttpCacheEntry('/hoho')).lastUpdated).not.toBe(date); // expect((<HttpCacheEntry>httpCacheService.getHttpCacheEntry('/hoho')).lastUpdated).not.toBe(date);
}); // });
}); // });
describe('getCacheData', () => { // describe('getCacheData', () => {
it('should return null if no cache', () => { // it('should return null if no cache', () => {
expect(httpCacheService.getCacheData('/hoho')).toBe(null); // expect(httpCacheService.getCacheData('/hoho')).toBe(null);
}); // });
it('should return cached data if exists', () => { // it('should return cached data if exists', () => {
// Act // // Act
httpCacheService.setCacheData('/hoho', response); // httpCacheService.setCacheData('/hoho', response);
// Assert // // Assert
expect(httpCacheService.getCacheData('/hoho')).toEqual(response); // expect(httpCacheService.getCacheData('/hoho')).toEqual(response);
}); // });
it('should return cached data with url parameters if exists', () => { // it('should return cached data with url parameters if exists', () => {
// Act // // Act
httpCacheService.setCacheData('/hoho?pif=paf', response); // httpCacheService.setCacheData('/hoho?pif=paf', response);
// Assert // // Assert
expect(httpCacheService.getCacheData('/hoho?pif=paf')).toEqual(response); // expect(httpCacheService.getCacheData('/hoho?pif=paf')).toEqual(response);
}); // });
}); // });
describe('getHttpCacheEntry', () => { // describe('getHttpCacheEntry', () => {
it('should return null if no cache', () => { // it('should return null if no cache', () => {
expect(httpCacheService.getHttpCacheEntry('/hoho')).toBe(null); // expect(httpCacheService.getHttpCacheEntry('/hoho')).toBe(null);
}); // });
it('should return cached data date if exists', () => { // it('should return cached data date if exists', () => {
// Arrange // // Arrange
const date = new Date(123); // const date = new Date(123);
// Act // // Act
httpCacheService.setCacheData('/hoho', response, date); // httpCacheService.setCacheData('/hoho', response, date);
const entry = httpCacheService.getHttpCacheEntry('/hoho'); // const entry = httpCacheService.getHttpCacheEntry('/hoho');
// Assert // // Assert
expect(entry).not.toBeNull(); // expect(entry).not.toBeNull();
expect((<HttpCacheEntry>entry).lastUpdated).toEqual(date); // expect((<HttpCacheEntry>entry).lastUpdated).toEqual(date);
expect((<HttpCacheEntry>entry).data).toEqual(response); // expect((<HttpCacheEntry>entry).data).toEqual(response);
}); // });
}); // });
describe('clearCacheData', () => { // describe('clearCacheData', () => {
it('should clear existing cache data', () => { // it('should clear existing cache data', () => {
// Set cache // // Set cache
httpCacheService.setCacheData('/hoho', response); // httpCacheService.setCacheData('/hoho', response);
expect(httpCacheService.getCacheData('/hoho')).toEqual(response); // expect(httpCacheService.getCacheData('/hoho')).toEqual(response);
// Clear cache // // Clear cache
httpCacheService.clearCache('/hoho'); // httpCacheService.clearCache('/hoho');
expect(httpCacheService.getCacheData('/hoho')).toBe(null); // expect(httpCacheService.getCacheData('/hoho')).toBe(null);
}); // });
it('should do nothing if no cache exists', () => { // it('should do nothing if no cache exists', () => {
expect(httpCacheService.getCacheData('/lolo')).toBe(null); // expect(httpCacheService.getCacheData('/lolo')).toBe(null);
httpCacheService.clearCache('/hoho'); // httpCacheService.clearCache('/hoho');
expect(httpCacheService.getCacheData('/lolo')).toBe(null); // expect(httpCacheService.getCacheData('/lolo')).toBe(null);
}); // });
}); // });
describe('cleanCache', () => { // describe('cleanCache', () => {
it('should clear all cache if no date is specified', () => { // it('should clear all cache if no date is specified', () => {
// Set cache // // Set cache
httpCacheService.setCacheData('/hoho', response); // httpCacheService.setCacheData('/hoho', response);
httpCacheService.setCacheData('/popo', response); // httpCacheService.setCacheData('/popo', response);
expect(httpCacheService.getCacheData('/hoho')).toBe(response); // expect(httpCacheService.getCacheData('/hoho')).toBe(response);
expect(httpCacheService.getCacheData('/popo')).toBe(response); // expect(httpCacheService.getCacheData('/popo')).toBe(response);
// Clean cache // // Clean cache
httpCacheService.cleanCache(); // httpCacheService.cleanCache();
expect(httpCacheService.getCacheData('/hoho')).toBe(null); // expect(httpCacheService.getCacheData('/hoho')).toBe(null);
expect(httpCacheService.getCacheData('/popo')).toBe(null); // expect(httpCacheService.getCacheData('/popo')).toBe(null);
}); // });
it('should clear existing since specified date', () => { // it('should clear existing since specified date', () => {
// Set cache // // Set cache
httpCacheService.setCacheData('/hoho', response); // httpCacheService.setCacheData('/hoho', response);
expect(httpCacheService.getCacheData('/hoho')).toBe(response); // expect(httpCacheService.getCacheData('/hoho')).toBe(response);
// Clean cache // // Clean cache
httpCacheService.cleanCache(new Date()); // httpCacheService.cleanCache(new Date());
expect(httpCacheService.getCacheData('/hoho')).toBe(null); // expect(httpCacheService.getCacheData('/hoho')).toBe(null);
}); // });
it('should not affect cache entries newer than specified date', () => { // it('should not affect cache entries newer than specified date', () => {
// Set cache // // Set cache
httpCacheService.setCacheData('/hoho', response); // httpCacheService.setCacheData('/hoho', response);
expect(httpCacheService.getCacheData('/hoho')).toBe(response); // expect(httpCacheService.getCacheData('/hoho')).toBe(response);
// Clean cache // // Clean cache
const date = new Date(); // const date = new Date();
httpCacheService.setCacheData('/lolo', response, new Date(date.getTime() + 10)); // httpCacheService.setCacheData('/lolo', response, new Date(date.getTime() + 10));
httpCacheService.cleanCache(date); // httpCacheService.cleanCache(date);
// Assert // // Assert
expect(httpCacheService.getCacheData('/hoho')).toBe(null); // expect(httpCacheService.getCacheData('/hoho')).toBe(null);
expect(httpCacheService.getCacheData('/lolo')).toBe(response); // expect(httpCacheService.getCacheData('/lolo')).toBe(response);
}); // });
}); // });
describe('setPersistence', () => { // describe('setPersistence', () => {
beforeEach(() => { // beforeEach(() => {
httpCacheService.setPersistence(); // httpCacheService.setPersistence();
httpCacheService.cleanCache = jasmine.createSpy('cleanCache'); // httpCacheService.cleanCache = jasmine.createSpy('cleanCache');
}); // });
it('should clear previous cache data when persistence value change', () => { // it('should clear previous cache data when persistence value change', () => {
httpCacheService.setPersistence('local'); // httpCacheService.setPersistence('local');
expect(httpCacheService.cleanCache).toHaveBeenCalledWith(); // expect(httpCacheService.cleanCache).toHaveBeenCalledWith();
}); // });
it('should persist cache to local storage', () => { // it('should persist cache to local storage', () => {
expect(localStorage.getItem(cachePersistenceKey)).toBeNull(); // expect(localStorage.getItem(cachePersistenceKey)).toBeNull();
httpCacheService.setPersistence('local'); // httpCacheService.setPersistence('local');
httpCacheService.setCacheData('/hoho', response); // httpCacheService.setCacheData('/hoho', response);
expect(localStorage.getItem(cachePersistenceKey)).not.toBeNull(); // expect(localStorage.getItem(cachePersistenceKey)).not.toBeNull();
}); // });
it('should persist cache to session storage', () => { // it('should persist cache to session storage', () => {
expect(sessionStorage.getItem(cachePersistenceKey)).toBeNull(); // expect(sessionStorage.getItem(cachePersistenceKey)).toBeNull();
httpCacheService.setPersistence('session'); // httpCacheService.setPersistence('session');
httpCacheService.setCacheData('/hoho', response); // httpCacheService.setCacheData('/hoho', response);
expect(sessionStorage.getItem(cachePersistenceKey)).not.toBeNull(); // expect(sessionStorage.getItem(cachePersistenceKey)).not.toBeNull();
}); // });
}); // });
}); // });
+11 -4
View File
@@ -1,13 +1,17 @@
/** Angular Imports */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpResponse } from '@angular/common/http'; import { HttpResponse } from '@angular/common/http';
/** Other Imports */
import { each } from 'lodash'; import { each } from 'lodash';
import { Logger } from '../logger.service'; /** Custom Services */
import { Logger } from '../logger/logger.service';
/** Initialize Logger */
const log = new Logger('HttpCacheService'); const log = new Logger('HttpCacheService');
const cachePersistenceKey = 'httpCache';
/** Http Cache Entry Model */
export interface HttpCacheEntry { export interface HttpCacheEntry {
lastUpdated: Date; lastUpdated: Date;
data: HttpResponse<any>; data: HttpResponse<any>;
@@ -19,6 +23,9 @@ export interface HttpCacheEntry {
@Injectable() @Injectable()
export class HttpCacheService { export class HttpCacheService {
/** Key to cache Http Requests in storage. */
private cachePersistenceStorageKey = 'mifosXHttpCache';
private cachedData: { [key: string]: HttpCacheEntry; } = {}; private cachedData: { [key: string]: HttpCacheEntry; } = {};
private storage: Storage | null = null; private storage: Storage | null = null;
@@ -107,12 +114,12 @@ export class HttpCacheService {
private saveCacheData() { private saveCacheData() {
if (this.storage) { if (this.storage) {
this.storage[cachePersistenceKey] = JSON.stringify(this.cachedData); this.storage[this.cachePersistenceStorageKey] = JSON.stringify(this.cachedData);
} }
} }
private loadCacheData() { private loadCacheData() {
const data = this.storage ? this.storage[cachePersistenceKey] : null; const data = this.storage ? this.storage[this.cachePersistenceStorageKey] : null;
this.cachedData = data ? JSON.parse(data) : {}; this.cachedData = data ? JSON.parse(data) : {};
} }
+122 -122
View File
@@ -1,140 +1,140 @@
import { TestBed, inject } from '@angular/core/testing'; // import { TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; // import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { HttpClient, HttpInterceptor } from '@angular/common/http'; // import { HttpClient, HttpInterceptor } from '@angular/common/http';
import { HttpService } from './http.service'; // import { HttpService } from './http.service';
import { HttpCacheService } from './http-cache.service'; // import { HttpCacheService } from './http-cache.service';
import { ErrorHandlerInterceptor } from './error-handler.interceptor'; // import { ErrorHandlerInterceptor } from './error-handler.interceptor';
import { CacheInterceptor } from './cache.interceptor'; // import { CacheInterceptor } from './cache.interceptor';
import { ApiPrefixInterceptor } from './api-prefix.interceptor'; // import { ApiPrefixInterceptor } from './api-prefix.interceptor';
describe('HttpService', () => { // describe('HttpService', () => {
let httpCacheService: HttpCacheService; // let httpCacheService: HttpCacheService;
let http: HttpClient; // let http: HttpClient;
let httpMock: HttpTestingController; // let httpMock: HttpTestingController;
beforeEach(() => { // beforeEach(() => {
TestBed.configureTestingModule({ // TestBed.configureTestingModule({
imports: [HttpClientTestingModule], // imports: [HttpClientTestingModule],
providers: [ // providers: [
ErrorHandlerInterceptor, // ErrorHandlerInterceptor,
CacheInterceptor, // CacheInterceptor,
ApiPrefixInterceptor, // ApiPrefixInterceptor,
HttpCacheService, // HttpCacheService,
{ // {
provide: HttpClient, // provide: HttpClient,
useClass: HttpService // useClass: HttpService
}, // },
] // ]
}); // });
}); // });
beforeEach(inject([ // beforeEach(inject([
HttpClient, // HttpClient,
HttpTestingController, // HttpTestingController,
HttpCacheService // HttpCacheService
], (_http: HttpClient, // ], (_http: HttpClient,
_httpMock: HttpTestingController, // _httpMock: HttpTestingController,
_httpCacheService: HttpCacheService) => { // _httpCacheService: HttpCacheService) => {
http = _http; // http = _http;
httpMock = _httpMock; // httpMock = _httpMock;
httpCacheService = _httpCacheService; // httpCacheService = _httpCacheService;
})); // }));
afterEach(() => { // afterEach(() => {
httpCacheService.cleanCache(); // httpCacheService.cleanCache();
httpMock.verify(); // httpMock.verify();
}); // });
it('should use error handler, API prefix and no cache by default', () => { // it('should use error handler, API prefix and no cache by default', () => {
// Arrange // // Arrange
let interceptors: HttpInterceptor[]; // let interceptors: HttpInterceptor[];
const realRequest = http.request; // const realRequest = http.request;
spyOn(HttpService.prototype, 'request').and.callFake(function(this: any) { // spyOn(HttpService.prototype, 'request').and.callFake(function(this: any) {
interceptors = this.interceptors; // interceptors = this.interceptors;
return realRequest.apply(this, arguments); // return realRequest.apply(this, arguments);
}); // });
// Act // // Act
const request = http.get('/toto'); // const request = http.get('/toto');
// Assert // // Assert
request.subscribe(() => { // request.subscribe(() => {
expect(http.request).toHaveBeenCalled(); // expect(http.request).toHaveBeenCalled();
expect(interceptors.some(i => i instanceof ApiPrefixInterceptor)).toBeTruthy(); // expect(interceptors.some(i => i instanceof ApiPrefixInterceptor)).toBeTruthy();
expect(interceptors.some(i => i instanceof ErrorHandlerInterceptor)).toBeTruthy(); // expect(interceptors.some(i => i instanceof ErrorHandlerInterceptor)).toBeTruthy();
expect(interceptors.some(i => i instanceof CacheInterceptor)).toBeFalsy(); // expect(interceptors.some(i => i instanceof CacheInterceptor)).toBeFalsy();
}); // });
httpMock.expectOne({}).flush({}); // httpMock.expectOne({}).flush({});
}); // });
it('should use cache', () => { // it('should use cache', () => {
// Arrange // // Arrange
let interceptors: HttpInterceptor[]; // let interceptors: HttpInterceptor[];
const realRequest = http.request; // const realRequest = http.request;
spyOn(HttpService.prototype, 'request').and.callFake(function(this: any) { // spyOn(HttpService.prototype, 'request').and.callFake(function(this: any) {
interceptors = this.interceptors; // interceptors = this.interceptors;
return realRequest.apply(this, arguments); // return realRequest.apply(this, arguments);
}); // });
// Act // // Act
const request = http // const request = http
.cache() // .cache()
.get('/toto'); // .get('/toto');
// Assert // // Assert
request.subscribe(() => { // request.subscribe(() => {
expect(interceptors.some(i => i instanceof ApiPrefixInterceptor)).toBeTruthy(); // expect(interceptors.some(i => i instanceof ApiPrefixInterceptor)).toBeTruthy();
expect(interceptors.some(i => i instanceof ErrorHandlerInterceptor)).toBeTruthy(); // expect(interceptors.some(i => i instanceof ErrorHandlerInterceptor)).toBeTruthy();
expect(interceptors.some(i => i instanceof CacheInterceptor)).toBeTruthy(); // expect(interceptors.some(i => i instanceof CacheInterceptor)).toBeTruthy();
}); // });
httpMock.expectOne({}).flush({}); // httpMock.expectOne({}).flush({});
}); // });
it('should skip error handler', () => { // it('should skip error handler', () => {
// Arrange // // Arrange
let interceptors: HttpInterceptor[]; // let interceptors: HttpInterceptor[];
const realRequest = http.request; // const realRequest = http.request;
spyOn(HttpService.prototype, 'request').and.callFake(function(this: any) { // spyOn(HttpService.prototype, 'request').and.callFake(function(this: any) {
interceptors = this.interceptors; // interceptors = this.interceptors;
return realRequest.apply(this, arguments); // return realRequest.apply(this, arguments);
}); // });
// Act // // Act
const request = http // const request = http
.skipErrorHandler() // .skipErrorHandler()
.get('/toto'); // .get('/toto');
// Assert // // Assert
request.subscribe(() => { // request.subscribe(() => {
expect(interceptors.some(i => i instanceof ApiPrefixInterceptor)).toBeTruthy(); // expect(interceptors.some(i => i instanceof ApiPrefixInterceptor)).toBeTruthy();
expect(interceptors.some(i => i instanceof ErrorHandlerInterceptor)).toBeFalsy(); // expect(interceptors.some(i => i instanceof ErrorHandlerInterceptor)).toBeFalsy();
expect(interceptors.some(i => i instanceof CacheInterceptor)).toBeFalsy(); // expect(interceptors.some(i => i instanceof CacheInterceptor)).toBeFalsy();
}); // });
httpMock.expectOne({}).flush({}); // httpMock.expectOne({}).flush({});
}); // });
it('should not use API prefix', () => { // it('should not use API prefix', () => {
// Arrange // // Arrange
let interceptors: HttpInterceptor[]; // let interceptors: HttpInterceptor[];
const realRequest = http.request; // const realRequest = http.request;
spyOn(HttpService.prototype, 'request').and.callFake(function(this: any) { // spyOn(HttpService.prototype, 'request').and.callFake(function(this: any) {
interceptors = this.interceptors; // interceptors = this.interceptors;
return realRequest.apply(this, arguments); // return realRequest.apply(this, arguments);
}); // });
// Act // // Act
const request = http // const request = http
.disableApiPrefix() // .disableApiPrefix()
.get('/toto'); // .get('/toto');
// Assert // // Assert
request.subscribe(() => { // request.subscribe(() => {
expect(interceptors.some(i => i instanceof ApiPrefixInterceptor)).toBeFalsy(); // expect(interceptors.some(i => i instanceof ApiPrefixInterceptor)).toBeFalsy();
expect(interceptors.some(i => i instanceof ErrorHandlerInterceptor)).toBeTruthy(); // expect(interceptors.some(i => i instanceof ErrorHandlerInterceptor)).toBeTruthy();
expect(interceptors.some(i => i instanceof CacheInterceptor)).toBeFalsy(); // expect(interceptors.some(i => i instanceof CacheInterceptor)).toBeFalsy();
}); // });
httpMock.expectOne({}).flush({}); // httpMock.expectOne({}).flush({});
}); // });
}); // });
+20 -9
View File
@@ -1,36 +1,43 @@
/** Angular Imports */
import { Inject, Injectable, InjectionToken, Injector, Optional } from '@angular/core'; import { Inject, Injectable, InjectionToken, Injector, Optional } from '@angular/core';
import { HttpClient, HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; import { HttpClient, HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
/** rxjs Imports */
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
/** Custom Interceptors */
import { ErrorHandlerInterceptor } from './error-handler.interceptor'; import { ErrorHandlerInterceptor } from './error-handler.interceptor';
import { CacheInterceptor } from './cache.interceptor'; import { CacheInterceptor } from './cache.interceptor';
import { ApiPrefixInterceptor } from './api-prefix.interceptor'; import { ApiPrefixInterceptor } from './api-prefix.interceptor';
// HttpClient is declared in a re-exported module, so we have to extend the original module to make it work properly /**
// (see https://github.com/Microsoft/TypeScript/issues/13897) * HttpClient is declared in a re-exported module, so we have to extend the original module to make it work properly.
* (see https://github.com/Microsoft/TypeScript/issues/13897)
*/
declare module '@angular/common/http/src/client' { declare module '@angular/common/http/src/client' {
// Augment HttpClient with the added configuration methods from HttpService, to allow in-place replacement of /**
// HttpClient with HttpService using dependency injection * Augment HttpClient with the added configuration methods from HttpService, to allow in-place replacement of
* HttpClient with HttpService using dependency injection.
*/
export interface HttpClient { export interface HttpClient {
/** /**
* Enables caching for this request. * Enables caching for this request.
* @param {boolean} forceUpdate Forces request to be made and updates cache entry. * @param {boolean} forceUpdate Forces request to be made and updates cache entry.
* @return {HttpClient} The new instance. * @returns {HttpClient} The new instance.
*/ */
cache(forceUpdate?: boolean): HttpClient; cache(forceUpdate?: boolean): HttpClient;
/** /**
* Skips default error handler for this request. * Skips default error handler for this request.
* @return {HttpClient} The new instance. * @returns {HttpClient} The new instance.
*/ */
skipErrorHandler(): HttpClient; skipErrorHandler(): HttpClient;
/** /**
* Do not use API prefix for this request. * Do not use API prefix for this request.
* @return {HttpClient} The new instance. * @returns {HttpClient} The new instance.
*/ */
disableApiPrefix(): HttpClient; disableApiPrefix(): HttpClient;
@@ -38,7 +45,9 @@ declare module '@angular/common/http/src/client' {
} }
// From @angular/common/http/src/interceptor: allows to chain interceptors /**
* From @angular/common/http/src/interceptor: allows to chain interceptors
*/
class HttpInterceptorHandler implements HttpHandler { class HttpInterceptorHandler implements HttpHandler {
constructor(private next: HttpHandler, private interceptor: HttpInterceptor) { } constructor(private next: HttpHandler, private interceptor: HttpInterceptor) { }
@@ -92,7 +101,9 @@ export class HttpService extends HttpClient {
return this.removeInterceptor(ApiPrefixInterceptor); return this.removeInterceptor(ApiPrefixInterceptor);
} }
// Override the original method to wire interceptors when triggering the request. /**
* Override the original method to wire interceptors when triggering the request.
*/
request(method?: any, url?: any, options?: any): any { request(method?: any, url?: any, options?: any): any {
const handler = this.interceptors.reduceRight( const handler = this.interceptors.reduceRight(
(next, interceptor) => new HttpInterceptorHandler(next, interceptor), (next, interceptor) => new HttpInterceptorHandler(next, interceptor),
-153
View File
@@ -1,153 +0,0 @@
import { TestBed, inject } from '@angular/core/testing';
import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
import { Subject } from 'rxjs';
import { extract, I18nService } from './i18n.service';
const defaultLanguage = 'en-US';
const supportedLanguages = ['eo', 'en-US', 'fr-FR'];
class MockTranslateService {
currentLang: string;
onLangChange = new Subject();
use(language: string) {
this.currentLang = language;
this.onLangChange.next({
lang: this.currentLang,
translations: {}
});
}
getBrowserCultureLang() {
return 'en-US';
}
setTranslation(lang: string, translations: Object, shouldMerge?: boolean) { }
}
describe('I18nService', () => {
let i18nService: I18nService;
let translateService: TranslateService;
let onLangChangeSpy: jasmine.Spy;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
I18nService,
{ provide: TranslateService, useClass: MockTranslateService },
]
});
});
beforeEach(inject([
I18nService,
TranslateService
], (_i18nService: I18nService,
_translateService: TranslateService) => {
i18nService = _i18nService;
translateService = _translateService;
// Create spies
onLangChangeSpy = jasmine.createSpy('onLangChangeSpy');
translateService.onLangChange
.subscribe((event: LangChangeEvent) => {
onLangChangeSpy(event.lang);
});
spyOn(translateService, 'use').and.callThrough();
}));
afterEach(() => {
// Cleanup
localStorage.removeItem('language');
});
describe('extract', () => {
it('should not modify string', () => {
expect(extract('Hello world !')).toEqual('Hello world !');
});
});
describe('init', () => {
it('should init with default language', () => {
// Act
i18nService.init(defaultLanguage, supportedLanguages);
// Assert
expect(translateService.use).toHaveBeenCalledWith(defaultLanguage);
expect(onLangChangeSpy).toHaveBeenCalledWith(defaultLanguage);
});
it('should init with save language', () => {
// Arrange
const savedLanguage = 'eo';
localStorage.setItem('language', savedLanguage);
// Act
i18nService.init(defaultLanguage, supportedLanguages);
// Assert
expect(translateService.use).toHaveBeenCalledWith(savedLanguage);
expect(onLangChangeSpy).toHaveBeenCalledWith(savedLanguage);
});
});
describe('set language', () => {
it('should change current language', () => {
// Arrange
const newLanguage = 'eo';
i18nService.init(defaultLanguage, supportedLanguages);
// Act
i18nService.language = newLanguage;
// Assert
expect(translateService.use).toHaveBeenCalledWith(newLanguage);
expect(onLangChangeSpy).toHaveBeenCalledWith(newLanguage);
});
it('should change current language without a region match', () => {
// Arrange
const newLanguage = 'fr-CA';
i18nService.init(defaultLanguage, supportedLanguages);
// Act
i18nService.language = newLanguage;
// Assert
expect(translateService.use).toHaveBeenCalledWith('fr-FR');
expect(onLangChangeSpy).toHaveBeenCalledWith('fr-FR');
});
it('should change current language to default if unsupported', () => {
// Arrange
const newLanguage = 'es';
i18nService.init(defaultLanguage, supportedLanguages);
// Act
i18nService.language = newLanguage;
// Assert
expect(translateService.use).toHaveBeenCalledWith(defaultLanguage);
expect(onLangChangeSpy).toHaveBeenCalledWith(defaultLanguage);
});
});
describe('get language', () => {
it('should return current language', () => {
// Arrange
i18nService.init(defaultLanguage, supportedLanguages);
// Act
const currentLanguage = i18nService.language;
// Assert
expect(currentLanguage).toEqual(defaultLanguage);
});
});
});
+153
View File
@@ -0,0 +1,153 @@
// import { TestBed, inject } from '@angular/core/testing';
// import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
// import { Subject } from 'rxjs';
// import { extract, I18nService } from './i18n.service';
// const defaultLanguage = 'en-US';
// const supportedLanguages = ['eo', 'en-US', 'fr-FR'];
// class MockTranslateService {
// currentLang: string;
// onLangChange = new Subject();
// use(language: string) {
// this.currentLang = language;
// this.onLangChange.next({
// lang: this.currentLang,
// translations: {}
// });
// }
// getBrowserCultureLang() {
// return 'en-US';
// }
// setTranslation(lang: string, translations: Object, shouldMerge?: boolean) { }
// }
// describe('I18nService', () => {
// let i18nService: I18nService;
// let translateService: TranslateService;
// let onLangChangeSpy: jasmine.Spy;
// beforeEach(() => {
// TestBed.configureTestingModule({
// providers: [
// I18nService,
// { provide: TranslateService, useClass: MockTranslateService },
// ]
// });
// });
// beforeEach(inject([
// I18nService,
// TranslateService
// ], (_i18nService: I18nService,
// _translateService: TranslateService) => {
// i18nService = _i18nService;
// translateService = _translateService;
// // Create spies
// onLangChangeSpy = jasmine.createSpy('onLangChangeSpy');
// translateService.onLangChange
// .subscribe((event: LangChangeEvent) => {
// onLangChangeSpy(event.lang);
// });
// spyOn(translateService, 'use').and.callThrough();
// }));
// afterEach(() => {
// // Cleanup
// localStorage.removeItem('language');
// });
// describe('extract', () => {
// it('should not modify string', () => {
// expect(extract('Hello world !')).toEqual('Hello world !');
// });
// });
// describe('init', () => {
// it('should init with default language', () => {
// // Act
// i18nService.init(defaultLanguage, supportedLanguages);
// // Assert
// expect(translateService.use).toHaveBeenCalledWith(defaultLanguage);
// expect(onLangChangeSpy).toHaveBeenCalledWith(defaultLanguage);
// });
// it('should init with save language', () => {
// // Arrange
// const savedLanguage = 'eo';
// localStorage.setItem('language', savedLanguage);
// // Act
// i18nService.init(defaultLanguage, supportedLanguages);
// // Assert
// expect(translateService.use).toHaveBeenCalledWith(savedLanguage);
// expect(onLangChangeSpy).toHaveBeenCalledWith(savedLanguage);
// });
// });
// describe('set language', () => {
// it('should change current language', () => {
// // Arrange
// const newLanguage = 'eo';
// i18nService.init(defaultLanguage, supportedLanguages);
// // Act
// i18nService.language = newLanguage;
// // Assert
// expect(translateService.use).toHaveBeenCalledWith(newLanguage);
// expect(onLangChangeSpy).toHaveBeenCalledWith(newLanguage);
// });
// it('should change current language without a region match', () => {
// // Arrange
// const newLanguage = 'fr-CA';
// i18nService.init(defaultLanguage, supportedLanguages);
// // Act
// i18nService.language = newLanguage;
// // Assert
// expect(translateService.use).toHaveBeenCalledWith('fr-FR');
// expect(onLangChangeSpy).toHaveBeenCalledWith('fr-FR');
// });
// it('should change current language to default if unsupported', () => {
// // Arrange
// const newLanguage = 'es';
// i18nService.init(defaultLanguage, supportedLanguages);
// // Act
// i18nService.language = newLanguage;
// // Assert
// expect(translateService.use).toHaveBeenCalledWith(defaultLanguage);
// expect(onLangChangeSpy).toHaveBeenCalledWith(defaultLanguage);
// });
// });
// describe('get language', () => {
// it('should return current language', () => {
// // Arrange
// i18nService.init(defaultLanguage, supportedLanguages);
// // Act
// const currentLanguage = i18nService.language;
// // Assert
// expect(currentLanguage).toEqual(defaultLanguage);
// });
// });
// });
@@ -1,14 +1,19 @@
/** Angular Imports */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
/** Translation Imports */
import { TranslateService, LangChangeEvent } from '@ngx-translate/core'; import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
/** Custom Services */
import { Logger } from '../logger/logger.service';
/** Other Imports */
import { includes } from 'lodash'; import { includes } from 'lodash';
import * as enUS from '../../../translations/en-US.json';
import * as frFR from '../../../translations/fr-FR.json';
import { Logger } from './logger.service'; /** Initialize Logger */
import * as enUS from '../../translations/en-US.json';
import * as frFR from '../../translations/fr-FR.json';
const log = new Logger('I18nService'); const log = new Logger('I18nService');
const languageKey = 'language';
/** /**
* Pass-through function to mark a string for translation extraction. * Pass-through function to mark a string for translation extraction.
@@ -20,11 +25,12 @@ export function extract(s: string) {
return s; return s;
} }
@Injectable({ @Injectable()
providedIn: 'root'
})
export class I18nService { export class I18nService {
/** Key to store current language of application in local storage. */
private languageStorageKey = 'mifosXLanguage';
defaultLanguage: string; defaultLanguage: string;
supportedLanguages: string[]; supportedLanguages: string[];
@@ -46,7 +52,7 @@ export class I18nService {
this.language = ''; this.language = '';
this.translateService.onLangChange this.translateService.onLangChange
.subscribe((event: LangChangeEvent) => { localStorage.setItem(languageKey, event.lang); }); .subscribe((event: LangChangeEvent) => { localStorage.setItem(this.languageStorageKey, event.lang); });
} }
/** /**
@@ -56,7 +62,7 @@ export class I18nService {
* @param {string} language The IETF language code to set. * @param {string} language The IETF language code to set.
*/ */
set language(language: string) { set language(language: string) {
language = language || localStorage.getItem(languageKey) || this.translateService.getBrowserCultureLang(); language = language || localStorage.getItem(this.languageStorageKey) || this.translateService.getBrowserCultureLang();
let isSupportedLanguage = includes(this.supportedLanguages, language); let isSupportedLanguage = includes(this.supportedLanguages, language);
// If no exact match is found, search without the region // If no exact match is found, search without the region
+2 -5
View File
@@ -1,13 +1,10 @@
export * from './core.module'; export * from './core.module';
export * from './authentication/authentication.service'; export * from './authentication/authentication.service';
export * from './authentication/authentication.guard'; export * from './authentication/authentication.guard';
export * from './i18n.service';
export * from './http/http.service'; export * from './http/http.service';
export * from './http/http-cache.service'; export * from './http/http-cache.service';
export * from './http/api-prefix.interceptor'; export * from './http/api-prefix.interceptor';
export * from './http/cache.interceptor'; export * from './http/cache.interceptor';
export * from './http/error-handler.interceptor'; export * from './http/error-handler.interceptor';
export * from './route.service'; export * from './i18n/i18n.service';
export * from './route-reusable-strategy'; export * from './route/route.service';
export * from './logger.service';
export * from './alert.service';
-72
View File
@@ -1,72 +0,0 @@
import { Logger, LogLevel, LogOutput } from './logger.service';
const logMethods = ['log', 'info', 'warn', 'error'];
describe('Logger', () => {
let savedConsole: Function[];
let savedLevel: LogLevel;
let savedOutputs: LogOutput[];
beforeAll(() => {
savedConsole = [];
logMethods.forEach((m) => {
savedConsole[m] = console[m];
console[m] = () => {};
});
savedLevel = Logger.level;
savedOutputs = Logger.outputs;
});
afterAll(() => {
logMethods.forEach((m) => { console[m] = savedConsole[m]; });
Logger.level = savedLevel;
Logger.outputs = savedOutputs;
});
it('should create an instance', () => {
expect(new Logger()).toBeTruthy();
});
it('should add a new LogOutput and receives log entries', () => {
// Arrange
const outputSpy = jasmine.createSpy('outputSpy');
const log = new Logger('test');
// Act
Logger.outputs.push(outputSpy);
log.debug('d');
log.info('i');
log.warn('w');
log.error('e', { error: true });
// Assert
expect(outputSpy).toHaveBeenCalled();
expect(outputSpy.calls.count()).toBe(4);
expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Debug, 'd');
expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Info, 'i');
expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Warning, 'w');
expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Error, 'e', { error: true });
});
it('should add a new LogOutput and receives only production log entries', () => {
// Arrange
const outputSpy = jasmine.createSpy('outputSpy');
const log = new Logger('test');
// Act
Logger.outputs.push(outputSpy);
Logger.enableProductionMode();
log.debug('d');
log.info('i');
log.warn('w');
log.error('e', { error: true });
// Assert
expect(outputSpy).toHaveBeenCalled();
expect(outputSpy.calls.count()).toBe(2);
expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Warning, 'w');
expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Error, 'e', { error: true });
});
});
@@ -0,0 +1,72 @@
// import { Logger, LogLevel, LogOutput } from './logger.service';
// const logMethods = ['log', 'info', 'warn', 'error'];
// describe('Logger', () => {
// let savedConsole: Function[];
// let savedLevel: LogLevel;
// let savedOutputs: LogOutput[];
// beforeAll(() => {
// savedConsole = [];
// logMethods.forEach((m) => {
// savedConsole[m] = console[m];
// console[m] = () => {};
// });
// savedLevel = Logger.level;
// savedOutputs = Logger.outputs;
// });
// afterAll(() => {
// logMethods.forEach((m) => { console[m] = savedConsole[m]; });
// Logger.level = savedLevel;
// Logger.outputs = savedOutputs;
// });
// it('should create an instance', () => {
// expect(new Logger()).toBeTruthy();
// });
// it('should add a new LogOutput and receives log entries', () => {
// // Arrange
// const outputSpy = jasmine.createSpy('outputSpy');
// const log = new Logger('test');
// // Act
// Logger.outputs.push(outputSpy);
// log.debug('d');
// log.info('i');
// log.warn('w');
// log.error('e', { error: true });
// // Assert
// expect(outputSpy).toHaveBeenCalled();
// expect(outputSpy.calls.count()).toBe(4);
// expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Debug, 'd');
// expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Info, 'i');
// expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Warning, 'w');
// expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Error, 'e', { error: true });
// });
// it('should add a new LogOutput and receives only production log entries', () => {
// // Arrange
// const outputSpy = jasmine.createSpy('outputSpy');
// const log = new Logger('test');
// // Act
// Logger.outputs.push(outputSpy);
// Logger.enableProductionMode();
// log.debug('d');
// log.info('i');
// log.warn('w');
// log.error('e', { error: true });
// // Assert
// expect(outputSpy).toHaveBeenCalled();
// expect(outputSpy.calls.count()).toBe(2);
// expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Warning, 'w');
// expect(outputSpy).toHaveBeenCalledWith('test', LogLevel.Error, 'e', { error: true });
// });
// });
@@ -9,7 +9,7 @@
* *
* Example usage: * Example usage:
* ``` * ```
* import { Logger } from 'app/core/logger.service'; * import { Logger } from 'app/core/logger/logger.service';
* *
* const log = new Logger('myFile'); * const log = new Logger('myFile');
* ... * ...
@@ -1,29 +1,48 @@
/** Angular Imports */
import { Injectable, EventEmitter } from '@angular/core'; import { Injectable, EventEmitter } from '@angular/core';
@Injectable({ /**
providedIn: 'root' * Progress bar service.
}) */
@Injectable()
export class ProgressBarService { export class ProgressBarService {
/** Progress bar update event. */
public updateProgressBar: EventEmitter<any>; public updateProgressBar: EventEmitter<any>;
/** Denotes the number of requests currently running. */
private requestsRunning = 0; private requestsRunning = 0;
/**
* Initializes progress bar update event.
*/
constructor() { constructor() {
this.updateProgressBar = new EventEmitter(); this.updateProgressBar = new EventEmitter();
} }
/**
* Returns the number of Http requests currently running.
* @returns {number} Number of Http requests currently running.
*/
public getRequestsRunning(): number { public getRequestsRunning(): number {
return this.requestsRunning; return this.requestsRunning;
} }
public increase(): void { /**
* Increments the number of Http requests currently running
* and emits a progress bar update event `indeterminate` when the first request is run.
*/
public increase() {
this.requestsRunning++; this.requestsRunning++;
if (this.requestsRunning === 1) { if (this.requestsRunning === 1) {
this.updateProgressBar.emit('indeterminate'); this.updateProgressBar.emit('indeterminate');
} }
} }
public decrease(): void { /**
* Decrements the number of Http requests currently running
* and emits a progress bar update event `none` when no request is running.
*/
public decrease() {
if (this.requestsRunning > 0) { if (this.requestsRunning > 0) {
this.requestsRunning--; this.requestsRunning--;
if (this.requestsRunning === 0) { if (this.requestsRunning === 0) {
@@ -1,16 +1,29 @@
/** Angular Imports */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http'; import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
/** rxjs Imports */
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { tap, catchError } from 'rxjs/operators'; import { tap, catchError } from 'rxjs/operators';
/** Custom Services */
import { ProgressBarService } from './progress-bar.service'; import { ProgressBarService } from './progress-bar.service';
/**
* Http Request interceptor to start/stop loading the progress bar.
*/
@Injectable() @Injectable()
export class ProgressInterceptor implements HttpInterceptor { export class ProgressInterceptor implements HttpInterceptor {
/**
* @param {ProgressBarService} progressBarService Progress Bar Service.
*/
constructor(private progressBarService: ProgressBarService) { } constructor(private progressBarService: ProgressBarService) { }
/**
* Intercepts a Http request to start loading the progress bar for a pending request
* and stop when a response or error is received.
*/
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.progressBarService.increase(); this.progressBarService.increase();
return next.handle(request) return next.handle(request)
-40
View File
@@ -1,40 +0,0 @@
import { TestBed, inject } from '@angular/core/testing';
import { AuthenticationService } from './authentication/authentication.service';
import { MockAuthenticationService } from './authentication/authentication.service.mock';
import { AuthenticationGuard } from './authentication/authentication.guard';
import { ShellComponent } from './shell/shell.component';
import { Route } from './route.service';
describe('Route', () => {
let route: Route;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
AuthenticationGuard,
{ provide: AuthenticationService, useClass: MockAuthenticationService },
Route
]
});
});
beforeEach(inject([Route], (_route: Route) => {
route = _route;
}));
describe('withShell', () => {
it('should create routes as children of shell', () => {
// Prepare
const testRoutes = [{ path: 'test' }];
// Act
const result = Route.withShell(testRoutes);
// Assert
expect(result.path).toBe('');
expect(result.children).toBe(testRoutes);
expect(result.component).toBe(ShellComponent);
});
});
});
@@ -1,3 +1,4 @@
/** Angular Imports */
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'; import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
/** /**
+40
View File
@@ -0,0 +1,40 @@
// import { TestBed, inject } from '@angular/core/testing';
// import { AuthenticationService } from '../authentication/authentication.service';
// import { MockAuthenticationService } from '../authentication/authentication.service.mock';
// import { AuthenticationGuard } from '../authentication/authentication.guard';
// import { ShellComponent } from './shell/shell.component';
// import { Route } from './route.service';
// describe('Route', () => {
// let route: Route;
// beforeEach(() => {
// TestBed.configureTestingModule({
// providers: [
// AuthenticationGuard,
// { provide: AuthenticationService, useClass: MockAuthenticationService },
// Route
// ]
// });
// });
// beforeEach(inject([Route], (_route: Route) => {
// route = _route;
// }));
// describe('withShell', () => {
// it('should create routes as children of shell', () => {
// // Prepare
// const testRoutes = [{ path: 'test' }];
// // Act
// const result = Route.withShell(testRoutes);
// // Assert
// expect(result.path).toBe('');
// expect(result.children).toBe(testRoutes);
// expect(result.component).toBe(ShellComponent);
// });
// });
// });
@@ -1,7 +1,11 @@
/** Angular Imports */
import { Route as ngRoute, Routes } from '@angular/router'; import { Route as ngRoute, Routes } from '@angular/router';
import { ShellComponent } from './shell/shell.component'; /** Custom Components */
import { AuthenticationGuard } from './authentication/authentication.guard'; import { ShellComponent } from '../shell/shell.component';
/** Custom Guards */
import { AuthenticationGuard } from '../authentication/authentication.guard';
/** /**
* Provides helper methods to create routes. * Provides helper methods to create routes.
+5 -2
View File
@@ -5,8 +5,11 @@ import { Router } from '@angular/router';
/** rxjs Imports */ /** rxjs Imports */
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
/** Custom Services and Models */ /** Custom Models */
import { AlertService, Alert } from '../core'; import { Alert } from '../core/alert/alert.model';
/** Custom Services */
import { AlertService } from '../core/alert/alert.service';
/** /**
* Login component. * Login component.
@@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
/** Custom Services */ /** Custom Services */
import { I18nService } from '../../core/i18n.service'; import { I18nService } from '../../core/i18n/i18n.service';
/** /**
* Language selector component. * Language selector component.
+3 -2
View File
@@ -8,9 +8,10 @@ import { merge } from 'rxjs';
import { filter, map, mergeMap } from 'rxjs/operators'; import { filter, map, mergeMap } from 'rxjs/operators';
import { environment } from 'environments/environment'; import { environment } from 'environments/environment';
import { Logger, I18nService } from './core'; import { I18nService } from './core/i18n/i18n.service';
import { Logger } from './core/logger/logger.service';
import { ThemeStorageService } from './shared/theme-picker/theme-storage.service'; import { ThemeStorageService } from './shared/theme-picker/theme-storage.service';
import { AlertService } from './core/alert.service'; import { AlertService } from './core/alert/alert.service';
const log = new Logger('MifosX'); const log = new Logger('MifosX');