Skip to content

Commit

Permalink
test: Add more coverage for connect card validators (#3411)
Browse files Browse the repository at this point in the history
  • Loading branch information
bistaastha authored Jan 9, 2025
1 parent e2d3cd5 commit 24fb103
Show file tree
Hide file tree
Showing 16 changed files with 374 additions and 106 deletions.
42 changes: 40 additions & 2 deletions src/app/auth/switch-org/switch-org.page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ import { DeepLinkService } from 'src/app/core/services/deep-link.service';
import { platformExpenseData } from 'src/app/core/mock-data/platform/v1/expense.data';
import { transformedExpenseData } from 'src/app/core/mock-data/transformed-expense.data';
import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service';
import { OrgSettingsService } from 'src/app/core/services/org-settings.service';
import { orgSettingsData } from 'src/app/core/test-data/org-settings.service.spec.data';
import { SpenderOnboardingService } from 'src/app/core/services/spender-onboarding.service';
import { onboardingStatusData } from 'src/app/core/mock-data/onboarding-status.data';
import { OnboardingState } from 'src/app/core/models/onboarding-state.enum';

const roles = ['OWNER', 'USER', 'FYLER'];
const email = '[email protected]';
Expand Down Expand Up @@ -73,6 +78,8 @@ describe('SwitchOrgPage', () => {
let transactionService: jasmine.SpyObj<TransactionService>;
let expensesService: jasmine.SpyObj<ExpensesService>;
let deepLinkService: jasmine.SpyObj<DeepLinkService>;
let orgSettingsService: jasmine.SpyObj<OrgSettingsService>;
let spenderOnboardingService: jasmine.SpyObj<SpenderOnboardingService>;

beforeEach(waitForAsync(() => {
const platformSpy = jasmine.createSpyObj('Platform', ['is']);
Expand Down Expand Up @@ -110,6 +117,8 @@ describe('SwitchOrgPage', () => {
const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseById']);
const deepLinkServiceSpy = jasmine.createSpyObj('DeepLinkService', ['getExpenseRoute']);
const ldSpy = jasmine.createSpyObj('LaunchDarklyService', ['initializeUser']);
const orgSettingsServiceSpy = jasmine.createSpyObj('OrgSettingsService', ['get']);
const spenderOnboardingServiceSpy = jasmine.createSpyObj('SpenderOnboardingSettings', ['getOnboardingSettings']);

TestBed.configureTestingModule({
declarations: [SwitchOrgPage, ActiveOrgCardComponent, OrgCardComponent, FyZeroStateComponent],
Expand Down Expand Up @@ -150,6 +159,14 @@ describe('SwitchOrgPage', () => {
provide: LoaderService,
useValue: loaderServiceSpy,
},
{
provide: OrgSettingsService,
useValue: orgSettingsServiceSpy,
},
{
provide: SpenderOnboardingService,
useValue: spenderOnboardingServiceSpy,
},
{
provide: UserService,
useValue: userServiceSpy,
Expand Down Expand Up @@ -256,11 +273,14 @@ describe('SwitchOrgPage', () => {
deepLinkService = TestBed.inject(DeepLinkService) as jasmine.SpyObj<DeepLinkService>;
transactionService = TestBed.inject(TransactionService) as jasmine.SpyObj<TransactionService>;
expensesService = TestBed.inject(ExpensesService) as jasmine.SpyObj<ExpensesService>;
spenderOnboardingService = TestBed.inject(SpenderOnboardingService) as jasmine.SpyObj<SpenderOnboardingService>;
orgSettingsService = TestBed.inject(OrgSettingsService) as jasmine.SpyObj<OrgSettingsService>;

component.searchRef = fixture.debugElement.query(By.css('#search'));
component.searchOrgsInput = fixture.debugElement.query(By.css('.smartlook-show'));
component.contentRef = fixture.debugElement.query(By.css('.switch-org__content-container__content-block'));
fixture.detectChanges();
spyOn(component, 'navigateToDashboard').and.callThrough();
}));

it('should create', () => {
Expand Down Expand Up @@ -642,16 +662,34 @@ describe('SwitchOrgPage', () => {
});

describe('navigateBasedOnUserStatus(): ', () => {
it('should navigate to dashboard if status is active', (done) => {
it('should navigate to dashboard if status is active', fakeAsync(() => {
const config = {
isPendingDetails: false,
roles,
eou: apiEouRes,
};

orgSettingsService.get.and.returnValue(of(orgSettingsData));
spenderOnboardingService.getOnboardingStatus.and.returnValue(
of({ ...onboardingStatusData, state: OnboardingState.COMPLETED })
);
tick();
component.navigateBasedOnUserStatus(config).subscribe((res) => {
expect(res).toBeNull();
expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'my_dashboard']);
});
}));

it('should navigate to spender onboarding if status not COMPLETE', (done) => {
const config = {
isPendingDetails: false,
roles,
eou: apiEouRes,
};
orgSettingsService.get.and.returnValue(of(orgSettingsData));
spenderOnboardingService.getOnboardingStatus.and.returnValue(of(onboardingStatusData));
component.navigateBasedOnUserStatus(config).subscribe((res) => {
expect(res).toBeNull();
expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'spender_onboarding']);
done();
});
});
Expand Down
8 changes: 6 additions & 2 deletions src/app/auth/switch-org/switch-org.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import { TransactionService } from 'src/app/core/services/transaction.service';
import { DeepLinkService } from 'src/app/core/services/deep-link.service';
import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service';
import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service';
import { OrgSettings } from 'src/app/core/models/org-settings.model';
import { OrgSettingsService } from 'src/app/core/services/org-settings.service';
import { SpenderOnboardingService } from 'src/app/core/services/spender-onboarding.service';
import { OnboardingState } from 'src/app/core/models/onboarding-state.enum';
Expand Down Expand Up @@ -321,7 +320,12 @@ export class SwitchOrgPage implements OnInit, AfterViewChecked {
navigateToDashboard(openOptInDialog?: boolean): void {
forkJoin([this.orgSettingsService.get(), this.spenderOnboardingService.getOnboardingStatus()]).subscribe(
([orgSettings, onboardingStatus]) => {
if (onboardingStatus.state !== OnboardingState.COMPLETED) {
if (
(orgSettings.visa_enrollment_settings.enabled ||
orgSettings.mastercard_enrollment_settings.enabled ||
orgSettings.amex_feed_enrollment_settings.enabled) &&
onboardingStatus.state !== OnboardingState.COMPLETED
) {
this.router.navigate(['/', 'enterprise', 'spender_onboarding']);
} else {
this.router.navigate([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@
role="button"
(click)="enrollCards()"
appFormButtonValidation
[disabled]="!fg.valid"
[loading]="cardsEnrolling"
[loadingText]="'Continue'"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, flush, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { CorporateCreditCardExpenseService } from 'src/app/core/services/corporate-credit-card-expense.service';
import { SpenderOnboardingConnectCardStepComponent } from './spender-onboarding-connect-card-step.component';
import { RealTimeFeedService } from 'src/app/core/services/real-time-feed.service';
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { IonicModule, PopoverController } from '@ionic/angular';
import { By } from '@angular/platform-browser';
import { CardNetworkType } from 'src/app/core/enums/card-network-type';
import { NgxMaskModule } from 'ngx-mask';
import { HttpErrorResponse } from '@angular/common/http';
import { of, throwError } from 'rxjs';
import { statementUploadedCard } from 'src/app/core/mock-data/platform-corporate-card.data';
import { SimpleChange, SimpleChanges } from '@angular/core';
import { SimpleChanges } from '@angular/core';
import { orgSettingsData } from 'src/app/core/test-data/org-settings.service.spec.data';
import { PopupAlertComponent } from 'src/app/shared/components/popup-alert/popup-alert.component';

describe('SpenderOnboardingConnectCardStepComponent', () => {
let component: SpenderOnboardingConnectCardStepComponent;
Expand All @@ -25,7 +25,7 @@ describe('SpenderOnboardingConnectCardStepComponent', () => {
const corporateCreditCardExpenseServiceSpy = jasmine.createSpyObj('CorporateCreditCardExpenseService', [
'getCorporateCards',
]);
const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['dismiss']);
const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['create', 'dismiss']);
const realTimeFeedServiceSpy = jasmine.createSpyObj('RealTimeFeedService', [
'enroll',
'getCardTypeFromNumber',
Expand Down Expand Up @@ -53,14 +53,23 @@ describe('SpenderOnboardingConnectCardStepComponent', () => {
fb = TestBed.inject(FormBuilder);
component.fg = fb.group({});
popoverController = TestBed.inject(PopoverController) as jasmine.SpyObj<PopoverController>;
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(null);
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([]));
realTimeFeedService.isCardNumberValid.and.returnValue(true);
realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.VISA);
}));

it('ngOnInit(): ', () => {
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([]));
it('ngOnInit(): should setup card form controls', () => {
component.enrollableCards = [{ ...statementUploadedCard, card_number: '1111' }];
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(
of([{ ...statementUploadedCard, card_number: '1111' }])
);
realTimeFeedService.isCardNumberValid.and.returnValue(false);
realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.OTHERS);
component.ngOnInit();
component.fg.controls.card_number_bacc15bbrRGWzf.setValue('4111111111');
fixture.detectChanges();

expect(component.fg.controls.card_number_bacc15bbrRGWzf.errors.invalidCardNumber).toBeTrue();
});

it('ngOnChanges(): should update isVisaRTFEnabled and isMastercardRTFEnabled when orgSettings changes', () => {
Expand Down Expand Up @@ -123,6 +132,19 @@ describe('SpenderOnboardingConnectCardStepComponent', () => {
});
});

it('handleEnrollmentFailures(): should set generic error message for no error message from API', () => {
const mockError = { status: 404 };

component.enrollableCards = [];
component.ngOnInit();
fixture.detectChanges();

//@ts-ignore
component.handleEnrollmentFailures(mockError);
expect(component.fg.controls.card_number.errors.enrollmentError).toBeTrue();
expect(component.singularEnrollmentFailure).toEqual('Something went wrong. Please try after some time.');
});

describe('enrollCards(): ', () => {
it('should call enrollMultipleCards if enrollableCards has items', () => {
component.enrollableCards = [statementUploadedCard];
Expand Down Expand Up @@ -152,6 +174,46 @@ describe('SpenderOnboardingConnectCardStepComponent', () => {
});
});

describe('generateMessage(): ', () => {
beforeEach(() => {
component.cardsList = {
successfulCards: [],
failedCards: [],
};
});

it('should return the message for successful cards with failed cards present', () => {
component.cardsList.successfulCards = ['**** 5678'];
component.cardsList.failedCards = ['**** 1234'];

const message = component.generateMessage();

expect(message).toBe(
'We ran into an issue while processing your request. You can cancel and retry connecting the failed card or proceed to the next step.'
);
});

it('should return the message for multiple failed cards', () => {
component.cardsList.failedCards = ['**** 1234', '**** 5678', '**** 9012'];
fixture.detectChanges();
const message = component.generateMessage();
expect(message).toBe(
`We ran into an issue while processing your request for the cards **** 1234, **** 5678 and **** 9012. You can cancel and retry connecting the failed card or proceed to the next step.`
);
});

it('should return the message for a single failed card', () => {
component.cardsList.failedCards = ['**** 1234'];

fixture.detectChanges();
const message = component.generateMessage();

expect(message).toBe(
`We ran into an issue while processing your request for the card **** 1234. You can cancel and retry connecting the failed card or proceed to the next step.`
);
});
});

describe('enrollMultipleCards(): ', () => {
it('should handle successful card enrollment', fakeAsync(() => {
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(
Expand Down Expand Up @@ -230,4 +292,67 @@ describe('SpenderOnboardingConnectCardStepComponent', () => {
expect(showErrorPopoverSpy).toHaveBeenCalledTimes(1);
}));
});

it('showErrorPopover(): should display a popover and handle its actions', () => {
const popoverSpy = jasmine.createSpyObj('popover', ['present', 'onWillDismiss']);
spyOn(component, 'generateMessage').and.returnValue('Error message');
popoverSpy.onWillDismiss.and.resolveTo({
data: {
action: 'close',
},
});
popoverController.create.and.resolveTo(popoverSpy);
component.cardsList = {
successfulCards: [],
failedCards: ['**** 1111'],
};
fixture.detectChanges();
component.showErrorPopover();

expect(popoverController.create).toHaveBeenCalledOnceWith({
componentProps: {
title: 'Failed connecting',
message: 'Error message',
primaryCta: {
text: 'Proceed anyway',
action: 'close',
},
secondaryCta: {
text: 'Cancel',
action: 'cancel',
},
cardsList: {},
},
component: PopupAlertComponent,
cssClass: 'pop-up-in-center',
});
});

describe('onCardNumberUpdate(): ', () => {
it('should update card_type for the given card or singleEnrollableCardDetails', () => {
realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.VISA);
component.enrollableCards = [];
component.ngOnInit();
component.fg.controls.card_number.setValue('41111111111111111');

component.onCardNumberUpdate();
expect(component.singleEnrollableCardDetails.card_type).toBe(CardNetworkType.VISA);
});

it('should update card_type for the given card or singleEnrollableCardDetails', () => {
realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.VISA);
component.cardValuesMap.bacc15bbrRGWzf = {
last_four: '1111',
card_type: CardNetworkType.VISA,
};
component.enrollableCards = [statementUploadedCard, { ...statementUploadedCard, id: 'bacc15bbrRGWzg' }];
component.fg = fb.group({
card_number_bacc15bbrRGWzf: ['', Validators.required],
});
component.fg.controls.card_number_bacc15bbrRGWzf.setValue('41111111111111111');

component.onCardNumberUpdate(statementUploadedCard);
expect(component.cardValuesMap.bacc15bbrRGWzf.card_type).toBe(CardNetworkType.VISA);
});
});
});
Loading

0 comments on commit 24fb103

Please sign in to comment.