Skip to content
Snippets Groups Projects
Commit 60bb6ba7 authored by OZGCloud's avatar OZGCloud
Browse files

OZG-959 adjust/change existing error handling for vorgang;

parent e0a8ea68
No related branches found
No related tags found
No related merge requests found
Showing
with 334 additions and 95 deletions
export * from './lib/date.util';
export * from './lib/decorator/catch-http-error.decorator';
export * from './lib/decorator/skip-error-interceptor.decorator';
export * from './lib/error/error.handler';
export * from './lib/http.util';
export * from './lib/pipe/convert-for-data-test.pipe';
export * from './lib/pipe/enum-to-label.pipe';
......
import { CatchHttpError } from '@goofy-client/tech-shared';
import { Mock, mock, useFromMock } from '@goofy-client/test-utils';
import { HttpErrorHandler } from '../error/error.handler';
import { catchHttpErrorHandleErrorResponse, handleDefaultErrorHandling, handleDefaultRetryHandling } from './catch-http-error.decorator';
describe('CatchHttpError Decorator', () => {
let httpErrorHandler: Mock<HttpErrorHandler>;
beforeEach(() => {
httpErrorHandler = mock(HttpErrorHandler);
})
it('should be created', () => {
expect(CatchHttpError).toBeTruthy();
})
describe('handleDefaultErrorHandling', () => {
it('should call http error handler if is true', () => {
handleDefaultErrorHandling(true, useFromMock(httpErrorHandler));
expect(httpErrorHandler.disableDefaultHandling).toHaveBeenCalled();
})
it('should do nothing if is false', () => {
handleDefaultErrorHandling(false, useFromMock(httpErrorHandler));
expect(httpErrorHandler.disableDefaultHandling).not.toHaveBeenCalled();
})
})
describe('handleDefaultRetryHandling', () => {
it('should call http error handler if is true', () => {
handleDefaultRetryHandling(true, useFromMock(httpErrorHandler));
expect(httpErrorHandler.disableRetry).toHaveBeenCalled();
})
it('should do nothing if is false', () => {
handleDefaultRetryHandling(false, useFromMock(httpErrorHandler));
expect(httpErrorHandler.disableRetry).not.toHaveBeenCalled();
})
})
describe('catchHttpErrorCatchError', () => {
const response = {};
const name: string = 'registeredMethodName';
it('should enable default error handling', () => {
catchHttpErrorHandleErrorResponse(useFromMock(httpErrorHandler), response, name);
expect(httpErrorHandler.enableDefaultHandling).toHaveBeenCalled();
})
it('should enable default retry handling', () => {
catchHttpErrorHandleErrorResponse(useFromMock(httpErrorHandler), response, name);
expect(httpErrorHandler.enableRetry).toHaveBeenCalled();
})
it('should execute post error method from http error handler', () => {
catchHttpErrorHandleErrorResponse(useFromMock(httpErrorHandler), response, name);
expect(httpErrorHandler.doAfterErrorReceived).toHaveBeenCalledWith(name, response);
})
})
})
\ No newline at end of file
import { catchError } from 'rxjs/operators';
import { HttpErrorHandler } from '../error/error.handler';
import { enableInterceptorDefaultHandling, injectHttpErrorHandler } from './error.decorator.util';
export function CatchHttpError(name: string, disableRetry: boolean = false, disableDefaultHandling: boolean = false): MethodDecorator {
return function (target: any, propertyName: any, descriptor: any) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any) {
const httpErrorHandler: HttpErrorHandler = injectHttpErrorHandler();
handleDefaultErrorHandling(disableDefaultHandling, httpErrorHandler);
handleDefaultRetryHandling(disableRetry, httpErrorHandler);
return originalMethod.apply(this, args).pipe(
catchError(errorResponse => catchHttpErrorHandleErrorResponse(httpErrorHandler, errorResponse, name)));
};
return descriptor;
}
}
export function handleDefaultErrorHandling(disableDefaultHandling: boolean, httpErrorHandler: HttpErrorHandler): void {
if (disableDefaultHandling) {
httpErrorHandler.disableDefaultHandling();
}
}
export function handleDefaultRetryHandling(disableRetry: boolean, httpErrorHandler: HttpErrorHandler): void {
if (disableRetry) {
httpErrorHandler.disableRetry();
}
}
export function catchHttpErrorHandleErrorResponse(httpErrorHandler: HttpErrorHandler, errorResponse: any, name: string): any {
enableInterceptorDefaultHandling(httpErrorHandler);
httpErrorHandler.doAfterErrorReceived(name, errorResponse);
return errorResponse;
}
\ No newline at end of file
import { Mock, mock, useFromMock } from '@goofy-client/test-utils';
import { HttpErrorHandler } from '../error/error.handler';
import { disableInterceptorDefaultHandling, enableInterceptorDefaultHandling } from './error.decorator.util';
describe('Error decorator util', () => {
let httpErrorHandler: Mock<HttpErrorHandler>;
beforeEach(() => {
httpErrorHandler = mock(HttpErrorHandler);
})
describe('enableInterceptorDefaultHandling', () => {
it('should enable default error handling', () => {
enableInterceptorDefaultHandling(useFromMock(httpErrorHandler));
expect(httpErrorHandler.enableDefaultHandling).toHaveBeenCalled();
})
it('should enable default retry handling', () => {
enableInterceptorDefaultHandling(useFromMock(httpErrorHandler));
expect(httpErrorHandler.enableRetry).toHaveBeenCalled();
})
})
describe('disableInterceptorDefaultHandling', () => {
it('should disable default error handling', () => {
disableInterceptorDefaultHandling(useFromMock(httpErrorHandler));
expect(httpErrorHandler.disableDefaultHandling).toHaveBeenCalled();
})
it('should disable default retry handling', () => {
disableInterceptorDefaultHandling(useFromMock(httpErrorHandler));
expect(httpErrorHandler.disableRetry).toHaveBeenCalled();
})
})
})
\ No newline at end of file
import { HttpErrorHandler } from '../error/error.handler';
import { TechSharedModule } from '../tech-shared.module';
export function injectHttpErrorHandler(): HttpErrorHandler {
return TechSharedModule.injector.get(HttpErrorHandler);
}
export function enableInterceptorDefaultHandling(httpErrorHandler: HttpErrorHandler): void {
httpErrorHandler.enableDefaultHandling();
httpErrorHandler.enableRetry();
}
export function disableInterceptorDefaultHandling(httpErrorHandler: HttpErrorHandler): void {
httpErrorHandler.disableDefaultHandling();
httpErrorHandler.disableRetry();
}
\ No newline at end of file
import { Mock, mock } from '@goofy-client/test-utils';
import { HttpErrorHandler } from '../error/error.handler';
import { SkipInterceptor } from './skip-error-interceptor.decorator';
describe('SkipInterceptor Decorator', () => {
let httpErrorHandler: Mock<HttpErrorHandler>;
beforeEach(() => {
httpErrorHandler = mock(HttpErrorHandler);
})
it('should be created', () => {
expect(SkipInterceptor).toBeTruthy();
})
})
\ No newline at end of file
import { HttpErrorHandler } from '@goofy-client/tech-shared';
import { finalize } from 'rxjs/operators';
import { disableInterceptorDefaultHandling, enableInterceptorDefaultHandling, injectHttpErrorHandler } from './error.decorator.util';
export function SkipInterceptor(): MethodDecorator {
return function (target: any, propertyName: any, descriptor: any) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any) {
const httpErrorHandler: HttpErrorHandler = injectHttpErrorHandler();
disableInterceptorDefaultHandling(httpErrorHandler);
return originalMethod.apply(this, args).pipe(finalize(() => enableInterceptorDefaultHandling(httpErrorHandler)));
};
return descriptor;
}
}
\ No newline at end of file
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { isNil } from 'lodash';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class HttpErrorHandler {
private readonly defaultHandling: BehaviorSubject<boolean> = new BehaviorSubject(true);
private readonly retryOnConnectionTimeout: BehaviorSubject<boolean> = new BehaviorSubject(true);
private postErrorActionByName = {};
public registerCustomHandling(name: string, doAfterErrorReceived) {
if (isNil(this.postErrorActionByName[name])) {
this.postErrorActionByName[name] = doAfterErrorReceived;
} else {
console.error(`HttpErrorHandler: duplicate post error action registration of ${name}`);
}
}
public doAfterErrorReceived(name: string, errorResponse: HttpErrorResponse) {
if (!isNil(this.postErrorActionByName[name])) {
this.postErrorActionByName[name](errorResponse);
} else {
console.error(`HttpErrorHandler: no post error action defined for ${name}`);
}
}
public enableDefaultHandling(): void {
this.defaultHandling.next(true);
}
public shouldDoDefaultHandling(): boolean {
return this.defaultHandling.value;
}
public disableDefaultHandling() {
this.defaultHandling.next(false);
}
public enableRetry(): void {
this.retryOnConnectionTimeout.next(true);
}
public shouldDoRetry(): boolean {
return this.retryOnConnectionTimeout.value;
}
public disableRetry() {
this.retryOnConnectionTimeout.next(false);
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ import { existRequestHeader, isChangingDataRequest } from '../http.util';
@Injectable()
export class HttpXsrfInterceptor implements HttpInterceptor {
private readonly X_XSRF_TOKEN_HEADER: string = 'X-XSRF-TOKEN';
static readonly X_XSRF_TOKEN_HEADER: string = 'X-XSRF-TOKEN';
constructor(private tokenExtractor: HttpXsrfTokenExtractor) { }
......@@ -20,8 +20,8 @@ export class HttpXsrfInterceptor implements HttpInterceptor {
private handleRequest(request: HttpRequest<unknown>): HttpRequest<unknown> {
let token: string = this.getToken();
if (token !== null && !existRequestHeader(request, this.X_XSRF_TOKEN_HEADER)) {
request = addRequestHeader(request, this.X_XSRF_TOKEN_HEADER, token);
if (token !== null && !existRequestHeader(request, HttpXsrfInterceptor.X_XSRF_TOKEN_HEADER)) {
request = addRequestHeader(request, HttpXsrfInterceptor.X_XSRF_TOKEN_HEADER, token);
}
return request;
}
......
import { CommonModule } from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { Injector, NgModule } from '@angular/core';
import { HttpXsrfInterceptor } from './interceptor/http-xsrf.interceptor';
import { ConvertForDataTestPipe } from './pipe/convert-for-data-test.pipe';
import { EnumToLabelPipe } from './pipe/enum-to-label.pipe';
......@@ -47,4 +47,10 @@ import { ToTrafficLightPipe } from './pipe/to-traffic-light.pipe';
}
]
})
export class TechSharedModule { }
export class TechSharedModule {
public static injector: Injector;
constructor(private injector: Injector) {
TechSharedModule.injector = this.injector;
}
}
\ No newline at end of file
......@@ -14,6 +14,7 @@ describe('TechUtil', () => {
})
describe('replace placeholder', () => {
it('should replace with value', () => {
const text = 'replace {this}';
const placeholder = 'this';
......@@ -46,6 +47,7 @@ describe('TechUtil', () => {
});
describe('replace placeholders', () => {
it('should replace all placeholder', () => {
const text = 'replace {here} and {there}'
const placeholders = { here: 'this', there: 'that' };
......
......@@ -29,3 +29,8 @@ export function replacePlaceholder(text: string, placeholder: string, value: str
export function hasExceptionId(apiError: ApiError): boolean {
return !isNil(apiError) && !isNil(apiError.issues) && !isNil(apiError.issues[0].exceptionId);
}
export function sleep(delayInMs: number) {
var start = new Date().getTime();
while (new Date().getTime() < start + delayInMs);
}
\ No newline at end of file
......@@ -31,3 +31,7 @@ export function mockComponent(options: Component): Component {
return Component(metadata)(Mock as any);
}
export function mockClass(clazz: any): Mock<any> {
return clazz as jest.Mocked<typeof clazz>;
}
\ No newline at end of file
import { Mock, mock, useFromMock } from '@goofy-client/test-utils';
import { VorgangErrorHandler } from './vorgang-error.handler';
import { VorgangService } from './vorgang.service';
describe('Vorgang Error Handler', () => {
let errorhandler: VorgangErrorHandler;
let vorgangService: Mock<VorgangService>;
beforeEach(() => {
vorgangService = mock(VorgangService);
errorhandler = new VorgangErrorHandler(useFromMock(vorgangService));
})
describe('isVorgangHttpResponse', () => {
it('should be true on vorgang', () => {
errorhandler.isVorgangHttpResponse(<any>{ error: { error: { url: '/api/vorgangs' } } });
})
it('should be false on other', () => {
errorhandler.isVorgangHttpResponse(<any>{ error: { error: { url: '/api/quatsch' } } });
})
})
describe('isGetVorgangWithEingangResponse', () => {
it('should be true on vorgang sub url', () => {
errorhandler.isGetVorgangWithEingangResponse('/api/vorgangs/');
})
it('should be false on other sub url', () => {
errorhandler.isGetVorgangWithEingangResponse('/api/quatsch/');
})
})
describe('handleGetVorgangWithEingangErrorResponse', () => {
it('should set emptyStateResource as vorgangWithEingang', () => {
errorhandler.handleGetVorgangWithEingangErrorResponse();
expect(vorgangService.clearVorgang).toHaveBeenCalled();
})
})
})
\ No newline at end of file
import { HttpErrorResponse } from "@angular/common/http";
import { ErrorHandler, Injectable } from "@angular/core";
import { isNil } from "lodash-es";
import { VorgangService } from "./vorgang.service";
@Injectable()
export class VorgangErrorHandler implements ErrorHandler {
private readonly BASE_PATH = '/api/vorgangs';
constructor(private vorgangService: VorgangService) { }
handleError(errorEvent: any): void {
if (!this.isVorgangHttpResponse(errorEvent.error)) {
return;
}
if (this.isGetVorgangWithEingangResponse(errorEvent.error.url)) {
this.handleGetVorgangWithEingangErrorResponse();
}
}
isVorgangHttpResponse(response: HttpErrorResponse): boolean {
return !isNil(response) && !isNil(response.url) && response.url.includes(this.BASE_PATH);
}
isGetVorgangWithEingangResponse(url: string): boolean {
return url.includes(this.BASE_PATH + '/');
}
handleGetVorgangWithEingangErrorResponse() {
this.vorgangService.clearVorgang();
}
}
import { HttpErrorHandler } from '@goofy-client/tech-shared';
import { Mock, mock, useFromMock } from '@goofy-client/test-utils';
import { VorgangHttpErrorService } from './vorgang-http-error.service';
import { VorgangRepository } from './vorgang.repository';
import { VorgangService } from './vorgang.service';
describe('VorgangHttpErrorService', () => {
let service: VorgangHttpErrorService;
let errorHandler: Mock<HttpErrorHandler> = mock(HttpErrorHandler);
let vorgangService: Mock<VorgangService> = mock(VorgangService);
beforeEach(() => {
service = new VorgangHttpErrorService(useFromMock(errorHandler), useFromMock(vorgangService));
})
describe('register', () => {
describe('getVorgang', () => {
it('should call error handler', () => {
service.register();
expect(errorHandler.registerCustomHandling).toHaveBeenCalledWith(VorgangRepository.GET_VORGANG, expect.anything());
})
})
})
})
\ No newline at end of file
import { Injectable } from '@angular/core';
import { HttpErrorHandler } from '@goofy-client/tech-shared';
import { VorgangRepository } from './vorgang.repository';
import { VorgangService } from './vorgang.service';
@Injectable({ providedIn: 'root' })
export class VorgangHttpErrorService {
constructor(private errorHandler: HttpErrorHandler, private vorgangService: VorgangService) { }
register(): void {
this.errorHandler.registerCustomHandling(VorgangRepository.GET_VORGANG, () => this.vorgangService.clearVorgang());
}
}
\ No newline at end of file
import { CommonModule } from '@angular/common';
import { ErrorHandler, NgModule } from '@angular/core';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommandSharedModule } from '@goofy-client/command-shared';
import { OzgFileSharedModule } from '@goofy-client/ozg-file-shared';
import { PostfachSharedModule } from '@goofy-client/postfach-shared';
import { TechSharedModule } from '@goofy-client/tech-shared';
import { RestModule } from '@ngxp/rest';
import { VorgangErrorHandler } from './vorgang-error.handler';
import { VorgangHttpErrorService } from './vorgang-http-error.service';
@NgModule({
imports: [
......@@ -17,12 +17,8 @@ import { VorgangErrorHandler } from './vorgang-error.handler';
OzgFileSharedModule,
PostfachSharedModule,
RouterModule
],
providers: [
{
provide: ErrorHandler,
useClass: VorgangErrorHandler
}
],
]
})
export class VorgangSharedModule { }
export class VorgangSharedModule {
constructor(private vorgangErrorSevice: VorgangHttpErrorService) { }
}
\ No newline at end of file
import { ApiRootLinkRel, ApiRootResource } from '@goofy-client/api-root-shared';
import { mock, useFromMock } from '@goofy-client/test-utils';
import { HttpErrorHandler } from '@goofy-client/tech-shared';
import { mock, mockClass, useFromMock } from '@goofy-client/test-utils';
import { getUrl, ResourceFactory } from '@ngxp/rest';
import { cold, hot } from 'jest-marbles';
import { createApiRootResource } from 'libs/api-root-shared/test/api-root';
import { TechSharedModule } from 'libs/tech-shared/src/lib/tech-shared.module';
import { createVorgangListResource, createVorgangResource, createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang';
import { VorgangHeaderLinkRel, VorgangListLinkRel, VorgangWithEingangLinkRel } from './vorgang.linkrel';
import { VorgangListResource, VorgangResource, VorgangWithEingangResource } from './vorgang.model';
......@@ -81,6 +83,9 @@ describe('VorgangRepository', () => {
const vorgangWithEingangResource: VorgangWithEingangResource = createVorgangWithEingangResource();
beforeEach(() => {
const classMock = mockClass(TechSharedModule);
classMock.injector = <any>{ get: () => useFromMock(mock(HttpErrorHandler)) };
resourceWrapper.get.mockReturnValue(hot('a', { a: vorgangWithEingangResource }));
})
......
import { Injectable } from '@angular/core';
import { ApiRootLinkRel, ApiRootResource } from '@goofy-client/api-root-shared';
import { OzgFileListResource } from '@goofy-client/ozg-file-shared';
import { CatchHttpError } from '@goofy-client/tech-shared';
import { getUrl, ResourceFactory, ResourceUri } from '@ngxp/rest';
import { Observable } from 'rxjs/internal/Observable';
import { VorgangListLinkRel, VorgangWithEingangLinkRel } from './vorgang.linkrel';
......@@ -11,6 +12,8 @@ export class VorgangRepository {
static SEARCH_PARAM: string = 'searchBy';
static GET_VORGANG: string = 'getVorgang';
constructor(private resourceFactory: ResourceFactory) { }
public loadVorgangList(apiRootResource: ApiRootResource): Observable<VorgangListResource> {
......@@ -21,6 +24,7 @@ export class VorgangRepository {
return this.resourceFactory.from(vorgangListResource).get(VorgangListLinkRel.NEXT);
}
@CatchHttpError(VorgangRepository.GET_VORGANG)
public getVorgang(vorgangWithEingangUrl: ResourceUri): Observable<VorgangWithEingangResource> {
return this.resourceFactory.fromId(vorgangWithEingangUrl).get();
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment