Skip to content

Commit

Permalink
fix: added error handlers for customer ticketing (CXSPA-5035) (#18005)
Browse files Browse the repository at this point in the history
Co-authored-by: Hakwoo Kim <[email protected]>
  • Loading branch information
kimhw0630 and hakwookim authored Oct 24, 2023
1 parent 9090f16 commit 64e1954
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { I18nTestingModule, RoutingService } from '@spartacus/core';
import {
GlobalMessageEntities,
GlobalMessageService,
GlobalMessageType,
HttpErrorModel,
I18nTestingModule,
RoutingService,
Translatable,
TranslationService,
} from '@spartacus/core';
import {
CustomerTicketingFacade,
TicketStarter,
} from '@spartacus/customer-ticketing/root';
import { LaunchDialogService } from '@spartacus/storefront';
import { EMPTY, of } from 'rxjs';
import { EMPTY, Observable, of, throwError } from 'rxjs';
import { CustomerTicketingCreateDialogComponent } from './customer-ticketing-create-dialog.component';
import createSpy = jasmine.createSpy;

Expand Down Expand Up @@ -54,10 +63,25 @@ class MockCustomerTicketingFacade implements Partial<CustomerTicketingFacade> {
);
}

class MockGlobalMessageService implements Partial<GlobalMessageService> {
get(): Observable<GlobalMessageEntities> {
return of({});
}
add(_: string | Translatable, __: GlobalMessageType, ___?: number): void {}
remove(_: GlobalMessageType, __?: number): void {}
}

class MockTranslationService {
translate(): Observable<string> {
return of('translated string');
}
}

describe('CustomerTicketingCreateDialogComponent', () => {
let component: CustomerTicketingCreateDialogComponent;
let fixture: ComponentFixture<CustomerTicketingCreateDialogComponent>;
let customerTicketingFacade: CustomerTicketingFacade;
let globalMessageService: GlobalMessageService;

beforeEach(async () => {
await TestBed.configureTestingModule({
Expand All @@ -70,9 +94,14 @@ describe('CustomerTicketingCreateDialogComponent', () => {
useClass: MockCustomerTicketingFacade,
},
{ provide: RoutingService, useClass: MockRoutingService },
{ provide: GlobalMessageService, useClass: MockGlobalMessageService },
{ provide: TranslationService, useClass: MockTranslationService },
],
}).compileComponents();
customerTicketingFacade = TestBed.inject(CustomerTicketingFacade);
globalMessageService = TestBed.inject(GlobalMessageService);

spyOn(globalMessageService, 'add').and.callThrough();
});

beforeEach(() => {
Expand Down Expand Up @@ -109,5 +138,41 @@ describe('CustomerTicketingCreateDialogComponent', () => {
component.createTicketRequest();
expect(customerTicketingFacade.createTicket).not.toHaveBeenCalled();
});

it('should handle HttpErrorModel error correctly when creating a ticket', () => {
const expectedErrorMessage = 'mock-error-message';
const error = new HttpErrorModel();
error.details = [{ message: expectedErrorMessage }];
customerTicketingFacade.createTicket = createSpy().and.returnValue(
throwError(error)
);
component.form.get('message')?.setValue(mockTicketStarter.message);
component.form.get('subject')?.setValue(mockTicketStarter.subject);
component.form.get('ticketCategory')?.setValue(mockCategories);
component.form.get('associatedTo')?.setValue(mockTicketAssociatedObjects);
component.createTicketRequest();

expect(globalMessageService.add).toHaveBeenCalledWith(
{ raw: expectedErrorMessage },
GlobalMessageType.MSG_TYPE_ERROR
);
});

it('should handle other error correctly when creating a ticket', () => {
const expectedErrorMessage = 'error';
customerTicketingFacade.createTicket = createSpy().and.returnValue(
throwError(expectedErrorMessage)
);
component.form.get('message')?.setValue(mockTicketStarter.message);
component.form.get('subject')?.setValue(mockTicketStarter.subject);
component.form.get('ticketCategory')?.setValue(mockCategories);
component.form.get('associatedTo')?.setValue(mockTicketAssociatedObjects);
component.createTicketRequest();

expect(globalMessageService.add).toHaveBeenCalledWith(
{ raw: 'translated string' },
GlobalMessageType.MSG_TYPE_ERROR
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Component, Input, OnDestroy, OnInit, inject } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import {
AssociatedObject,
Expand All @@ -14,8 +14,15 @@ import {
TicketStarter,
} from '@spartacus/customer-ticketing/root';
import { FormUtils } from '@spartacus/storefront';
import { Observable, Subscription } from 'rxjs';
import { Observable, Subscription, of } from 'rxjs';
import { CustomerTicketingDialogComponent } from '../../../shared/customer-ticketing-dialog/customer-ticketing-dialog.component';
import {
GlobalMessageService,
GlobalMessageType,
HttpErrorModel,
TranslationService,
} from '@spartacus/core';
import { catchError, first } from 'rxjs/operators';
@Component({
selector: 'cx-customer-ticketing-create-dialog',
templateUrl: './customer-ticketing-create-dialog.component.html',
Expand All @@ -27,7 +34,12 @@ export class CustomerTicketingCreateDialogComponent
ticketCategories$: Observable<Category[]> =
this.customerTicketingFacade.getTicketCategories();
ticketAssociatedObjects$: Observable<AssociatedObject[]> =
this.customerTicketingFacade.getTicketAssociatedObjects();
this.customerTicketingFacade.getTicketAssociatedObjects().pipe(
catchError((error: any) => {
this.handleError(error);
return of([]);
})
);
subscription: Subscription;

@Input()
Expand All @@ -38,6 +50,10 @@ export class CustomerTicketingCreateDialogComponent

attachment: File;

protected globalMessage = inject(GlobalMessageService);

protected translationService = inject(TranslationService);

protected getCreateTicketPayload(form: FormGroup): TicketStarter {
return {
message: form?.get('message')?.value,
Expand Down Expand Up @@ -108,13 +124,37 @@ export class CustomerTicketingCreateDialogComponent
complete: () => {
this.onComplete();
},
error: () => {
this.onError();
error: (error: any) => {
this.handleError(error);
},
});
}
}

protected handleError(error: any): void {
if (error instanceof HttpErrorModel) {
(error.details ?? []).forEach((err) => {
if (err.message) {
this.globalMessage.add(
{ raw: err.message },
GlobalMessageType.MSG_TYPE_ERROR
);
}
});
} else {
this.translationService
.translate('httpHandlers.unknownError')
.pipe(first())
.subscribe((text) => {
this.globalMessage.add(
{ raw: text },
GlobalMessageType.MSG_TYPE_ERROR
);
});
}
this.onError();
}

protected onComplete(): void {
this.close('Ticket created successfully');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
TicketReopenedEvent,
TicketStarter,
} from '@spartacus/customer-ticketing/root';
import { of } from 'rxjs';
import { of, throwError } from 'rxjs';
import { take } from 'rxjs/operators';
import { CustomerTicketingConnector } from '../connectors';
import { CustomerTicketingService } from './customer-ticketing.service';
Expand Down Expand Up @@ -346,6 +346,27 @@ describe('CustomerTicketingService', () => {
done();
});
});

it('should handle error response', () => {
const errorResponse = {
loading: false,
data: null,
error: 'Some error message',
};

spyOn(service, 'getTicketAssociatedObjectsState').and.returnValue(
throwError(errorResponse.error)
);

service.getTicketAssociatedObjects().subscribe(
() => {
fail('Should not reach here');
},
(error) => {
expect(error).toEqual(errorResponse.error);
}
);
});
});

describe('createTicketEvent', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
CommandService,
CommandStrategy,
EventService,
HttpErrorModel,
Query,
QueryNotifier,
QueryService,
Expand Down Expand Up @@ -40,8 +41,9 @@ import {
TicketStarter,
UploadAttachmentSuccessEvent,
} from '@spartacus/customer-ticketing/root';
import { combineLatest, Observable } from 'rxjs';
import { combineLatest, Observable, of, throwError } from 'rxjs';
import {
concatMap,
distinctUntilChanged,
map,
switchMap,
Expand Down Expand Up @@ -295,6 +297,9 @@ export class CustomerTicketingService implements CustomerTicketingFacade {
}
getTicketAssociatedObjects(): Observable<AssociatedObject[]> {
return this.getTicketAssociatedObjectsState().pipe(
concatMap((state) =>
state?.error ? throwError(state.error as HttpErrorModel) : of(state)
),
map((state) => state.data ?? [])
);
}
Expand Down

0 comments on commit 64e1954

Please sign in to comment.