From c08a8af2ad430d27bb7e57f94b23a49ad7411584 Mon Sep 17 00:00:00 2001 From: Deepika Mahindroo Date: Tue, 3 Sep 2024 10:40:04 +0530 Subject: [PATCH 1/3] fix(test cases): added test cases added test cases GH-^\& --- .../saas-ui/src/app/app.component.spec.ts | 9 - .../add-plan/add-plan.component.spec.ts | 676 ++++++++---------- .../billing-plan.component.spec.ts | 146 ++-- .../button-renderer.component.spec.ts | 106 ++- .../lead-list/lead-list.component.spec.ts | 137 +++- .../manage-plans.component.spec.ts | 159 +++- .../onboarding-tenant-list.component.spec.ts | 218 +++--- .../tenant-registration.component.spec.ts | 197 ++++- .../src/app/main/home/home.component.spec.ts | 3 + .../src/app/main/main.component.spec.ts | 95 ++- .../adapters/get-plan-adapter.service.spec.ts | 31 + .../commands/add-lead-command.spec.ts | 25 + .../commands/add-tenant-lead.command.spec.ts | 40 ++ .../commands/get-lead-by-id.command.spec.ts | 32 + .../commands/get-lead-command.spec.ts | 24 + .../commands/get-plan.command.spec.ts | 60 ++ .../commands/verify-email.command.spec.ts | 69 ++ .../add-lead/add-lead.component.spec.ts | 112 ++- .../add-tenant/add-tenant.component.spec.ts | 142 +++- .../guards/email-verify.guard.spec.ts | 41 ++ .../on-boarding/on-boarding.component.spec.ts | 10 +- .../shared/auth/login/login.component.spec.ts | 57 +- .../app/shared/enum/plan-tier.enum.spec.ts | 13 + .../enum/subscription-status.enum.spec.ts | 23 + .../shared/enum/tenant-status.enum.spec.ts | 12 + .../interfaces/features.interface.spec.ts | 39 + .../app/shared/models/address.model.spec.ts | 20 + .../models/feature-values.model.spec.ts | 43 ++ .../app/shared/models/feature.model.spec.ts | 43 ++ .../src/app/shared/models/lead.model.spec.ts | 66 ++ .../src/app/shared/models/plan.model.spec.ts | 34 + .../models/plans-features.model.spec.ts | 77 ++ .../app/shared/models/tenant.model.spec.ts | 61 ++ .../shared/models/tenantLead.model.spec.ts | 99 +++ .../services/billing-plan-service.spec.ts | 185 +++++ .../services/feature-list-service.spec.ts | 135 ++++ .../services/on-boarding-service.spec.ts | 250 +++++++ 37 files changed, 2899 insertions(+), 590 deletions(-) create mode 100644 projects/saas-ui/src/app/on-boarding/adapters/get-plan-adapter.service.spec.ts create mode 100644 projects/saas-ui/src/app/on-boarding/commands/add-lead-command.spec.ts create mode 100644 projects/saas-ui/src/app/on-boarding/commands/add-tenant-lead.command.spec.ts create mode 100644 projects/saas-ui/src/app/on-boarding/commands/get-lead-by-id.command.spec.ts create mode 100644 projects/saas-ui/src/app/on-boarding/commands/get-lead-command.spec.ts create mode 100644 projects/saas-ui/src/app/on-boarding/commands/get-plan.command.spec.ts create mode 100644 projects/saas-ui/src/app/on-boarding/commands/verify-email.command.spec.ts create mode 100644 projects/saas-ui/src/app/on-boarding/guards/email-verify.guard.spec.ts create mode 100644 projects/saas-ui/src/app/shared/enum/plan-tier.enum.spec.ts create mode 100644 projects/saas-ui/src/app/shared/enum/subscription-status.enum.spec.ts create mode 100644 projects/saas-ui/src/app/shared/enum/tenant-status.enum.spec.ts create mode 100644 projects/saas-ui/src/app/shared/interfaces/features.interface.spec.ts create mode 100644 projects/saas-ui/src/app/shared/models/address.model.spec.ts create mode 100644 projects/saas-ui/src/app/shared/models/feature-values.model.spec.ts create mode 100644 projects/saas-ui/src/app/shared/models/feature.model.spec.ts create mode 100644 projects/saas-ui/src/app/shared/models/lead.model.spec.ts create mode 100644 projects/saas-ui/src/app/shared/models/plan.model.spec.ts create mode 100644 projects/saas-ui/src/app/shared/models/plans-features.model.spec.ts create mode 100644 projects/saas-ui/src/app/shared/models/tenant.model.spec.ts create mode 100644 projects/saas-ui/src/app/shared/models/tenantLead.model.spec.ts create mode 100644 projects/saas-ui/src/app/shared/services/billing-plan-service.spec.ts create mode 100644 projects/saas-ui/src/app/shared/services/feature-list-service.spec.ts create mode 100644 projects/saas-ui/src/app/shared/services/on-boarding-service.spec.ts diff --git a/projects/saas-ui/src/app/app.component.spec.ts b/projects/saas-ui/src/app/app.component.spec.ts index 15a02f09..21d9a718 100644 --- a/projects/saas-ui/src/app/app.component.spec.ts +++ b/projects/saas-ui/src/app/app.component.spec.ts @@ -21,13 +21,4 @@ describe('AppComponent', () => { const app = fixture.componentInstance; expect(app.title).toEqual('saas-ui'); }); - - it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('.content span')?.textContent).toContain( - 'saas-ui app is running!', - ); - }); }); diff --git a/projects/saas-ui/src/app/main/components/add-plan/add-plan.component.spec.ts b/projects/saas-ui/src/app/main/components/add-plan/add-plan.component.spec.ts index f5754090..13af80b6 100644 --- a/projects/saas-ui/src/app/main/components/add-plan/add-plan.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/add-plan/add-plan.component.spec.ts @@ -1,366 +1,310 @@ -// import {ComponentFixture, TestBed} from '@angular/core/testing'; -// import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'; -// import {ActivatedRoute, Router} from '@angular/router'; -// import {NbDialogService, NbToastrService} from '@nebular/theme'; -// import {of, throwError} from 'rxjs'; // Import 'of' and 'throwError' from 'rxjs' -// import {OnBoardingService} from '../../../shared/services/on-boarding-service'; -// import {BillingPlanService} from '../../../shared/services/billing-plan-service'; -// import {FeatureListService} from '../../../shared/services/feature-list-service'; -// import {AddPlanComponent} from './add-plan.component'; -// import {AddFeaturesDialogComponent} from '../add-features-dialog/add-features-dialog.component'; -// import {ApiService} from '@project-lib/core/api/api.service'; -// import {HttpClientTestingModule} from '@angular/common/http/testing'; -// import {AnyAdapter} from '@project-lib/core/api/adapters'; -// import {InjectionToken} from '@angular/core'; -// import {ThemeModule} from '@project-lib/theme/theme.module'; - -// export const APP_CONFIG = new InjectionToken('Application config'); - -// describe('AddPlanComponent', () => { -// let component: AddPlanComponent; -// let fixture: ComponentFixture; -// let fb: FormBuilder; -// let featureListService: FeatureListService; -// let onboardingService: OnBoardingService; -// let toasterService: NbToastrService; -// let billingplanService: BillingPlanService; -// let dialogService: NbDialogService; -// let router: Router; -// let activateRoute: ActivatedRoute; - -// const mockAppConfig = { -// currency: 'USD', -// billingCycles: ['Monthly', 'Yearly'], -// }; - -// beforeEach(async () => { -// await TestBed.configureTestingModule({ -// declarations: [AddPlanComponent], -// imports: [HttpClientTestingModule, ThemeModule], -// providers: [ -// FormBuilder, -// OnBoardingService, -// NbToastrService, -// AnyAdapter, -// ApiService, -// {provide: APP_CONFIG, useValue: mockAppConfig}, -// { -// provide: BillingPlanService, -// useValue: { -// addPlan: jasmine.createSpy('addPlan').and.returnValue(of(null)), -// editPlan: jasmine.createSpy('editPlan').and.returnValue(of(null)), -// getPlanById: jasmine -// .createSpy('getPlanById') -// .and.returnValue(of({})), -// getCurrencyDetails: jasmine -// .createSpy('getCurrencyDetails') -// .and.returnValue(of([])), -// getBillingCycles: jasmine -// .createSpy('getBillingCycles') -// .and.returnValue(of([])), -// }, // Mock BillingPlanService -// }, -// { -// provide: NbDialogService, -// useValue: { -// open: jasmine.createSpy('open').and.returnValue({onClose: of([])}), -// }, // Mock NbDialogService -// }, -// { -// provide: Router, -// useValue: {navigate: jasmine.createSpy('navigate')}, -// }, -// { -// provide: ActivatedRoute, -// useValue: {snapshot: {params: {id: '1'}}}, -// }, -// { -// provide: featureListService, -// useValue: {snapshot: {params: {id: '1'}}}, -// }, -// ], -// }).compileComponents(); -// }); - -// beforeEach(() => { -// fixture = TestBed.createComponent(AddPlanComponent); -// component = fixture.componentInstance; -// fb = TestBed.inject(FormBuilder); -// featureListService = TestBed.inject(FeatureListService); -// onboardingService = TestBed.inject(OnBoardingService); -// toasterService = TestBed.inject(NbToastrService); -// billingplanService = TestBed.inject(BillingPlanService); -// dialogService = TestBed.inject(NbDialogService); -// router = TestBed.inject(Router); -// activateRoute = TestBed.inject(ActivatedRoute); -// fixture.detectChanges(); -// }); - -// it('should create', () => { -// expect(component).toBeTruthy(); -// }); - -// describe('ngOnInit', () => { -// it('should call getCurrencyDetails and getBillingCycleDetails', () => { -// spyOn(component, 'getCurrencyDetails'); -// spyOn(component, 'getBillingCycleDetails'); - -// component.ngOnInit(); - -// expect(component.getCurrencyDetails).toHaveBeenCalled(); -// expect(component.getBillingCycleDetails).toHaveBeenCalled(); -// }); - -// it('should set isEditMode to true and call getPlanbyId if activateRoute.snapshot.params.id is truthy', () => { -// activateRoute.snapshot.params.id = '1'; -// spyOn(component, 'getPlanbyId'); - -// component.ngOnInit(); - -// expect(component.isEditMode).toBeTrue(); -// expect(component.getPlanbyId).toHaveBeenCalled(); -// }); - -// it('should call createFeatureControls', () => { -// spyOn(component, 'createFeatureControls'); - -// component.ngOnInit(); - -// expect(component.createFeatureControls).toHaveBeenCalled(); -// }); -// }); - -// describe('addPlan', () => { -// beforeEach(() => { -// component.addPlanForm = fb.group({ -// name: ['Test Plan', Validators.required], -// billingCycleId: [1, Validators.required], -// price: ['10.99'], -// currencyId: ['USD', Validators.required], -// description: ['Test Description', Validators.required], -// tier: [0, Validators.required], -// storage: [null], -// features: fb.group({}), -// }); -// }); - -// it('should do nothing if addPlanForm is invalid', () => { -// component.addPlanForm.get('name')?.setValue(''); - -// component.addPlan(); - -// expect(billingplanService.addPlan).not.toHaveBeenCalled(); -// expect(toasterService.show).not.toHaveBeenCalled(); -// expect(router.navigate).not.toHaveBeenCalled(); -// }); -// }); - -// describe('createFeatureControls', () => { -// it('should create a form control for each feature in featureOptions', () => { -// component.featureOptions = [ -// { -// name: 'Video Call', -// description: 'Whether to allow Video Call', -// key: 'VIDEO_CALL', -// value_type: 'boolean', -// default_value: true, -// }, -// { -// name: 'Consultant Limit', -// description: 'Maximum number of participants', -// key: 'MAX_PARTICIPANTS', -// value_type: 'number', -// default_value: null, -// }, -// ]; - -// component.createFeatureControls(); - -// expect( -// component.addPlanForm.get('features')?.get('VIDEO_CALL'), -// ).toBeInstanceOf(FormControl); -// expect( -// component.addPlanForm.get('features')?.get('MAX_PARTICIPANTS'), -// ).toBeInstanceOf(FormControl); -// }); - -// it('should set the default value of the number form control to the default_value property of the feature', () => { -// component.featureOptions = [ -// { -// name: 'Consultant Limit', -// description: 'Maximum number of participants', -// key: 'MAX_PARTICIPANTS', -// value_type: 'number', -// default_value: 5, -// }, -// ]; - -// component.createFeatureControls(); - -// expect( -// component.addPlanForm.get('features')?.get('MAX_PARTICIPANTS')?.value, -// ).toBe(5); -// }); - -// it('should set the validators of the number form control to Validators.pattern(/^[0-9]+$/)', () => { -// component.featureOptions = [ -// { -// name: 'Consultant Limit', -// description: 'Maximum number of participants', -// key: 'MAX_PARTICIPANTS', -// value_type: 'number', -// default_value: null, -// }, -// ]; - -// component.createFeatureControls(); - -// expect( -// component.addPlanForm.get('features')?.get('MAX_PARTICIPANTS') -// ?.validator, -// ).toBe(Validators.pattern(/^[0-9]+$/)); -// }); - -// it('should create an empty form control for value_type "boolean"', () => { -// component.featureOptions = [ -// { -// name: 'Video Call', -// description: 'Whether to allow Video Call', -// key: 'VIDEO_CALL', -// value_type: 'boolean', -// default_value: true, -// }, -// ]; - -// component.createFeatureControls(); - -// expect( -// component.addPlanForm.get('features')?.get('VIDEO_CALL')?.value, -// ).toBeTrue(); -// }); - -// it('should create an empty form control for value_type "string"', () => { -// component.featureOptions = [ -// { -// name: 'Welcome Message', -// description: 'Message displayed to users on login', -// key: 'WELCOME_MESSAGE', -// value_type: 'string', -// default_value: 'Welcome!', -// }, -// ]; - -// component.createFeatureControls(); - -// expect( -// component.addPlanForm.get('features')?.get('WELCOME_MESSAGE')?.value, -// ).toBe('Welcome!'); -// }); - -// it('should create an empty form control for value_type "object"', () => { -// component.featureOptions = [ -// { -// name: 'Contact Info', -// description: 'Contact Information', -// key: 'CONTACT_INFO', -// value_type: 'object', -// default_value: {email: 'test@example.com'}, -// }, -// ]; - -// component.createFeatureControls(); - -// expect( -// component.addPlanForm.get('features')?.get('CONTACT_INFO')?.value, -// ).toEqual({email: 'test@example.com'}); -// }); -// }); - -// describe('showAddFeaturesPopup', () => { -// it('should open AddFeaturesDialogComponent', () => { -// component.showAddFeaturesPopup(); - -// expect(dialogService.open).toHaveBeenCalledWith( -// AddFeaturesDialogComponent, -// { -// context: { -// selectedFeatures: component.selectedFeatures, -// featureOptions: component.featureOptions, -// }, -// }, -// ); -// }); -// }); - -// describe('update', () => { -// it('should update the completion status of task and subtasks if index is undefined', () => { -// component.task = { -// name: 'Main Task', -// completed: false, -// subtasks: [ -// {name: 'Subtask 1', completed: false}, -// {name: 'Subtask 2', completed: false}, -// {name: 'Subtask 3', completed: false}, -// ], -// }; - -// component.update(true); - -// expect(component.task.completed).toBeTrue(); -// expect(component.task.subtasks[0].completed).toBeTrue(); -// expect(component.task.subtasks[1].completed).toBeTrue(); -// expect(component.task.subtasks[2].completed).toBeTrue(); -// }); - -// it('should call updateParentCompletion if an index is provided', () => { -// component.task = { -// name: 'Main Task', -// completed: false, -// subtasks: [ -// {name: 'Subtask 1', completed: false}, -// {name: 'Subtask 2', completed: false}, -// {name: 'Subtask 3', completed: false}, -// ], -// }; - -// spyOn(component, 'updateParentCompletion'); - -// component.update(true, 1); - -// expect(component.updateParentCompletion).toHaveBeenCalled(); -// }); -// }); - -// describe('savePlan', () => { -// beforeEach(() => { -// component.addPlanForm = fb.group({ -// name: ['Test Plan', Validators.required], -// billingCycleId: [1, Validators.required], -// price: ['10.99'], -// currencyId: ['USD', Validators.required], -// description: ['Test Description', Validators.required], -// tier: [0, Validators.required], -// storage: [null], -// features: fb.group({}), -// }); -// }); - -// it('should call addPlan if isEditMode is false', () => { -// spyOn(component, 'addPlan'); -// component.isEditMode = false; - -// component.savePlan(); - -// expect(component.addPlan).toHaveBeenCalled(); -// }); - -// it('should call editPlan if isEditMode is true', () => { -// spyOn(component, 'editPlan'); -// component.isEditMode = true; - -// component.savePlan(); - -// expect(component.editPlan).toHaveBeenCalled(); -// }); -// }); -// }); +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import { + FormBuilder, + FormControl, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import {RouterTestingModule} from '@angular/router/testing'; +import { + NbCardModule, + NbDialogService, + NbFocusMonitor, + NbInputModule, + NbLayoutModule, + NbPositionBuilderService, + NbStatusService, + NbThemeModule, + NbToastrService, +} from '@nebular/theme'; +import {async, of, throwError} from 'rxjs'; +import {AddPlanComponent} from './add-plan.component'; +import {BillingPlanService} from '../../../shared/services/billing-plan-service'; +import {FeatureListService} from '../../../shared/services/feature-list-service'; +import {OnBoardingService} from '../../../shared/services/on-boarding-service'; +import {APP_CONFIG} from '@project-lib/app-config'; +import {IAnyObject} from '../../../../../../arc-lib/src/lib/core/i-any-object'; +import {ThemeModule} from '@project-lib/theme/theme.module'; +import {DOCUMENT} from '@angular/common'; +import {ActivatedRoute, Router} from '@angular/router'; +import {Features} from '../../../shared/models/feature.model'; + +// Mock services +class MockBillingPlanService { + getCurrencyDetails() { + return of([{id: 'USD'}]); + } + getBillingCycles() { + return of([{id: 'monthly'}]); + } + addPlan() { + return of({id: 'plan123'}); + } + getPlanById() { + return of({id: 'plan123', tier: 'Basic'}); + } + editPlan() { + return of({}); + } +} + +class MockFeatureListService { + getFeatures() { + return of([{key: 'feature1', type: 'string', defaultValue: ''}]); + } + getFeatureById() { + return of({features: [{key: 'feature1', value: 'test'}]}); + } + addFeatures() { + return of({}); + } + editFeatures() { + return of({}); + } +} + +class MockOnBoardingService {} + +const mockAppConfig: IAnyObject = {}; + +describe('AddPlanComponent', () => { + let component: AddPlanComponent; + let fixture: ComponentFixture; + let activateRoute: ActivatedRoute; + let fb: FormBuilder; + let router: Router; + let mockRouter; + let mockBillingPlanService; + let mockFeatureListService; + let mockToastrService; + let featureListService: FeatureListService; + let onboardingService: OnBoardingService; + let toasterService: NbToastrService; + let billingplanService: BillingPlanService; + + beforeEach(async () => { + const routerSpy = jasmine.createSpyObj('Router', ['navigate']); + mockBillingPlanService = jasmine.createSpyObj('BillingPlanService', [ + 'addPlan', + 'editPlan', + 'getCurrencyDetails', + 'getBillingCycles', + 'getPlanById', + ]); + mockFeatureListService = jasmine.createSpyObj('FeatureListService', [ + 'getFeatures', + 'getFeatureById', + 'addFeatures', + 'editFeatures', + ]); + const getCurrencydetails = + mockBillingPlanService.getCurrencyDetails.and.returnValue(of([])); + const getBillingCycledetails = + mockBillingPlanService.getBillingCycles.and.returnValue(of([])); + mockFeatureListService.getFeatures.and.returnValue(of([])); + + await TestBed.configureTestingModule({ + declarations: [AddPlanComponent], + imports: [ + ReactiveFormsModule, + RouterTestingModule, + NbThemeModule.forRoot(), + ThemeModule, + NbCardModule, + NbInputModule, + NbLayoutModule, + ], + providers: [ + {provide: APP_CONFIG, useValue: mockAppConfig}, + {provide: BillingPlanService, useValue: mockBillingPlanService}, + {provide: FeatureListService, useValue: mockFeatureListService}, + {provide: OnBoardingService, useClass: MockOnBoardingService}, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AddPlanComponent); + component = fixture.componentInstance; + activateRoute = TestBed.inject(ActivatedRoute); + fb = TestBed.inject(FormBuilder); + mockRouter = jasmine.createSpyObj(['navigate']); + featureListService = TestBed.inject(FeatureListService); + onboardingService = TestBed.inject(OnBoardingService); + toasterService = TestBed.inject(NbToastrService); + mockToastrService = jasmine.createSpyObj(['show']); + billingplanService = TestBed.inject( + BillingPlanService, + ) as jasmine.SpyObj; + featureListService = TestBed.inject( + FeatureListService, + ) as jasmine.SpyObj; + router = TestBed.inject(Router); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('ngOnInit', () => { + it('should call getCurrencyDetails and getBillingCycleDetails', () => { + spyOn(component, 'getCurrencyDetails'); + spyOn(component, 'getBillingCycleDetails'); + + component.ngOnInit(); + + expect(component.getCurrencyDetails).toHaveBeenCalled(); + expect(component.getBillingCycleDetails).toHaveBeenCalled(); + }); + + it('should set isEditMode to true and call getPlanbyId if activateRoute.snapshot.params.id is truthy', () => { + activateRoute.snapshot.params.id = '1'; + spyOn(component, 'getPlanbyId'); + + component.ngOnInit(); + + expect(component.isEditMode).toBeTrue(); + expect(component.getPlanbyId).toHaveBeenCalled(); + }); + }); + + it('should call createFeatureControls', () => { + spyOn(component, 'createFeatureControls'); + + component.ngOnInit(); + + expect(component.createFeatureControls).toHaveBeenCalled(); + }); + + it('should update form controls when tier changes', () => { + component.onTierChange('STANDARD'); + expect(component.showStorageSize).toBe(true); + + component.onTierChange('BASIC'); + expect(component.showStorageSize).toBe(false); + expect(component.addPlanForm.get('size')?.value).toBeNull(); + }); + + it('should create feature controls based on feature options', () => { + const features = [ + {key: 'feature1', type: 'boolean'}, + {key: 'feature2', type: 'number', defaultValue: 5}, + {key: 'feature3', type: 'string'}, + ] as Features[]; + + component.featureOption = features; + component.createFeatureControls(); + + const featuresGroup = component.addPlanForm.get('features') as FormGroup; + + expect(featuresGroup.contains('feature1')).toBe(true); + expect(featuresGroup.contains('feature2')).toBe(true); + expect(featuresGroup.contains('feature3')).toBe(true); + }); + + it('should remove feature from form controls', () => { + const feature = {key: 'feature1', type: 'boolean'} as Features; + + component.featureOption = [feature]; + component.createFeatureControls(); + component.removeFeature(feature); + + const featuresGroup = component.addPlanForm.get('features') as FormGroup; + + expect(featuresGroup.contains('feature1')).toBe(false); + }); + + it('should add a plan when addPlan is called', () => { + const addPlanSpy = mockBillingPlanService.addPlan.and.returnValue( + of({id: 'planId'}), + ); + const addFeaturesSpy = mockFeatureListService.addFeatures.and.returnValue( + of({}), + ); + + component.addPlanForm.patchValue({ + name: 'Test Plan', + billingCycleId: '123', + price: '100', + currencyId: 'USD', + description: 'Test Plan Description', + tier: 'BASIC', + size: 'SMALL', + }); + spyOn(router, 'navigate'); + spyOn(toasterService, 'show'); + component.addPlan(); + expect(addPlanSpy).toHaveBeenCalled(); + expect(addFeaturesSpy).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalledWith(['/main/plans']); + expect(toasterService.show).toHaveBeenCalledWith('Plan added Successfully'); + }); + + it('should call getPlanbyId and populate form with data', () => { + const mockPlan = { + name: 'Test Plan', + billingCycleId: 'monthly', + price: 100, + currencyId: 'USD', + description: 'Test description', + tier: 'STANDARD', + size: 'MEDIUM', + }; + const mockFeature = { + features: [{key: 'feature1', value: {value: 'value1'}}], + }; + mockBillingPlanService.getPlanById.and.returnValue(of(mockPlan)); + mockFeatureListService.getFeatureById.and.returnValue(of(mockFeature)); + + component.getPlanbyId(); + + expect(billingplanService.getPlanById).toHaveBeenCalled(); + expect(featureListService.getFeatureById).toHaveBeenCalled(); + expect(component.addPlanForm.get('name').value).toBe(mockPlan.name); + expect(component.addPlanForm.get('billingCycleId').value).toBe( + mockPlan.billingCycleId, + ); + }); + + it('should call editPlan and navigate to plans on success', () => { + const mockResponse = {id: 'plan1'}; + component.addPlanForm.patchValue({ + name: 'Test Plan', + billingCycleId: '123', + price: '100', + currencyId: 'USD', + description: 'Test Plan Description', + tier: 'BASIC', + size: 'SMALL', + }); + spyOn(router, 'navigate'); + mockBillingPlanService.editPlan.and.returnValue(of(mockResponse)); + mockFeatureListService.editFeatures.and.returnValue(of({})); + component.editPlan(); + expect(mockBillingPlanService.editPlan).toHaveBeenCalled(); + expect(mockFeatureListService.editFeatures).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalledWith(['/main/plans']); + }); + + it('should call addPlan and navigate to plans on success', () => { + const mockResponse = {id: 'plan1'}; + mockBillingPlanService.addPlan.and.returnValue(of(mockResponse)); + mockFeatureListService.addFeatures.and.returnValue(of({})); + spyOn(router, 'navigate'); + spyOn(toasterService, 'show'); + + component.addPlan(); + expect(mockBillingPlanService.addPlan).toHaveBeenCalled(); + expect(mockFeatureListService.addFeatures).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalledWith(['/main/plans']); + expect(toasterService.show).toHaveBeenCalledWith('Plan added Successfully'); + }); + + it('should cancel edit and navigate to plans', () => { + spyOn(router, 'navigate'); + component.cancelEdit(); + expect(router.navigate).toHaveBeenCalledWith(['/main/plans']); + }); +}); diff --git a/projects/saas-ui/src/app/main/components/billing-plan/billing-plan.component.spec.ts b/projects/saas-ui/src/app/main/components/billing-plan/billing-plan.component.spec.ts index bdf8ae9d..8a25005e 100644 --- a/projects/saas-ui/src/app/main/components/billing-plan/billing-plan.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/billing-plan/billing-plan.component.spec.ts @@ -4,7 +4,7 @@ import {Location} from '@angular/common'; import {BillingPlanComponent} from './billing-plan.component'; import {BillingPlanService} from '../../../shared/services/billing-plan-service'; import {SubscriptionStatus} from '../../../shared/enum/subscription-status.enum'; -import {Observable, of} from 'rxjs'; +import {Observable, of, throwError} from 'rxjs'; import { GridApi, GridOptions, @@ -16,6 +16,9 @@ import {ThemeModule} from '@project-lib/theme/theme.module'; import {MainModule} from '../../main.module'; import {TenantFacadeService} from '../../../shared/services'; import {NbStatusService} from '@nebular/theme'; +import {Count} from '@project-lib/core/api/models'; +import {BackendFilter, AnyObject} from '@project-lib/core/api'; +import {Plan} from '../../../shared/models'; describe('BillingPlanComponent', () => { let component: BillingPlanComponent; @@ -25,6 +28,15 @@ describe('BillingPlanComponent', () => { let route: ActivatedRoute; let router: Router; + class MockBillingPlanService { + getBillingDetails(filter: BackendFilter): Observable { + return of([]); + } + getTotalBillingPlan(): Observable { + return of({count: 0}); + } + } + beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [BillingPlanComponent], @@ -35,7 +47,7 @@ describe('BillingPlanComponent', () => { {provide: Location, useValue: location}, {provide: ActivatedRoute, useValue: route}, {provide: Router, useValue: router}, - {provide: BillingPlanService, useValue: billingplanService}, + {provide: BillingPlanService, useClass: MockBillingPlanService}, ], }).compileComponents(); }); @@ -54,64 +66,90 @@ describe('BillingPlanComponent', () => { expect(component).toBeTruthy(); }); - describe('getPaginatedBillPlans', () => { - it('should return paginated bill plans', () => { - const page = 1; - const limit = 5; - const filter = {offset: 0, limit: 5}; - const billingDetails = [ + it('should initialize grid options correctly', () => { + expect(component.gridOptions.pagination).toBeTrue(); + expect(component.gridOptions.rowModelType).toBe('infinite'); + expect(component.gridOptions.paginationPageSize).toBe(component.limit); + }); + + it('should call getTotal and return count', () => { + const mockCount: Count = {count: 10}; + spyOn(billingplanService, 'getTotalBillingPlan').and.returnValue( + of(mockCount), + ); + + component.getTotal().subscribe(count => { + expect(count).toEqual(mockCount); + }); + }); + + it('should call getPaginatedBillPlans and return transformed data', () => { + const mockPlans = [ + { + companyName: 'Company A', + userName: 'User A', + planName: 'Plan A', + startDate: '2024-01-01', + endDate: '2024-12-31', + status: 'Active', + }, + { + companyName: 'Company B', + userName: 'User B', + planName: 'Plan B', + startDate: '2024-01-01', + endDate: '2024-12-31', + status: 'Inactive', + }, + ]; + + spyOn(billingplanService, 'getBillingDetails').and.returnValue( + of(mockPlans), + ); + + component.getPaginatedBillPlans(1, component.limit).subscribe(data => { + expect(data).toEqual([ { - companyName: 'Company 1', - userName: 'User 1', - planName: 'Plan 1', - startDate: '2021-01-01', - endDate: '2021-12-31', - status: SubscriptionStatus.ACTIVE, + companyName: 'Company A', + userName: 'User A', + planName: 'Plan A', + startDate: '2024-01-01', + endDate: '2024-12-31', + status: SubscriptionStatus['Active'], }, { - companyName: 'Company 2', - userName: 'User 2', - planName: 'Plan 2', - startDate: '2021-01-01', - endDate: '2021-12-31', - status: SubscriptionStatus.INACTIVE, + companyName: 'Company B', + userName: 'User B', + planName: 'Plan B', + startDate: '2024-01-01', + endDate: '2024-12-31', + status: SubscriptionStatus['Inactive'], }, - ]; - spyOn(billingplanService, 'getBillingDetails').and.returnValue( - of(billingDetails), - ); - component.getPaginatedBillPlans(page, limit).subscribe(data => { - expect(data).toEqual([ - { - companyName: 'Company 1', - userName: 'User 1', - planName: 'Plan 1', - startDate: '2021-01-01', - endDate: '2021-12-31', - status: 'ACTIVE', - }, - { - companyName: 'Company 2', - userName: 'User 2', - planName: 'Plan 2', - startDate: '2021-01-01', - endDate: '2021-12-31', - status: 'INACTIVE', - }, - ]); - }); + ]); }); }); + it('should handle errors in getPaginatedBillPlans', () => { + spyOn(billingplanService, 'getBillingDetails').and.returnValue( + throwError('Error'), + ); - describe('getTotal', () => { - it('should return total billing plan count', () => { - const count = {count: 10}; - spyOn(billingplanService, 'getTotalBillingPlan').and.returnValue( - of(count), - ); - component.getTotal().subscribe(data => { - expect(data).toEqual(count); - }); - }); + component.getPaginatedBillPlans(1, component.limit).subscribe( + () => fail('expected an error, not data'), + error => expect(error).toBe('Error'), + ); + }); + + it('should set dataSource in grid when onGridReady is called', () => { + const mockApi = { + setDatasource: jasmine.createSpy('setDatasource'), + }; + + component.gridApi = mockApi as unknown as GridApi; + + component.onGridReady({ + api: mockApi, + } as any); + + expect(mockApi.setDatasource).toHaveBeenCalled(); }); }); diff --git a/projects/saas-ui/src/app/main/components/button-renderer/button-renderer.component.spec.ts b/projects/saas-ui/src/app/main/components/button-renderer/button-renderer.component.spec.ts index b6f97db2..020ba6c5 100644 --- a/projects/saas-ui/src/app/main/components/button-renderer/button-renderer.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/button-renderer/button-renderer.component.spec.ts @@ -3,31 +3,89 @@ import {RouterTestingModule} from '@angular/router/testing'; import {ButtonRendererComponent} from './button-renderer.component'; import {ToasterService} from '@project-lib/theme/toaster'; import {BillingPlanService} from '../../../shared/services/billing-plan-service'; -import {FormBuilder, Validators} from '@angular/forms'; +import { + FormBuilder, + FormsModule, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; import {Location} from '@angular/common'; -import {GridApi} from 'ag-grid-community'; +import { + Column, + GridApi, + ICellRendererParams, + IRowNode, + SelectionEventSourceType, +} from 'ag-grid-community'; import {MainModule} from '../../main.module'; -import {NbThemeModule, NbOverlayModule, NbOverlayService} from '@nebular/theme'; +import { + NbThemeModule, + NbOverlayModule, + NbOverlayService, + NbStatusService, + NbToastrService, + NbToastrModule, +} from '@nebular/theme'; import {ThemeModule} from '@project-lib/theme/theme.module'; +import {Router} from '@angular/router'; +import {of, throwError} from 'rxjs'; +import {AnyAdapter, ApiService} from '@project-lib/core/api'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {APP_CONFIG} from '@project-lib/app-config'; +import {GetPlanAdapter} from '../../../on-boarding/adapters'; +import {RowNodeEventType} from 'ag-grid-community/dist/types/core/interfaces/iRowNode'; +import {Plan} from '../../../shared/models'; describe('ButtonRendererComponent', () => { let component: ButtonRendererComponent; - let toastrService: ToasterService; - let billingPlanService: BillingPlanService; + let mockRouter: Router; + let mockToasterService: jasmine.SpyObj; + let mockBillingPlanService: jasmine.SpyObj; + let mockLocation: Location; let fb: FormBuilder; - let location: Location; + const toastrServiceSpy = jasmine.createSpyObj('NbToastrService', ['error']); + const billingPlanServiceSpy = jasmine.createSpyObj('BillingPlanService', [ + 'deletePlan', + ]); beforeEach(() => { + mockRouter = jasmine.createSpyObj('Router', ['navigate']); + mockToasterService = jasmine.createSpyObj('ToasterService', [ + 'success', + 'error', + ]); + mockBillingPlanService = jasmine.createSpyObj('BillingPlanService', [ + 'deletePlan', + ]); + mockLocation = jasmine.createSpyObj('Location', ['reload']); TestBed.configureTestingModule({ - imports: [RouterTestingModule, MainModule, ThemeModule, NbOverlayModule], + imports: [ + RouterTestingModule, + NbThemeModule, + MainModule, + ThemeModule, + NbOverlayModule, + HttpClientTestingModule, + FormsModule, + ReactiveFormsModule, + NbToastrModule.forRoot(), + ], providers: [ - BillingPlanService, FormBuilder, Location, + AnyAdapter, + GetPlanAdapter, + ApiService, + NbStatusService, + {provide: NbToastrService, useValue: mockToasterService}, + {provide: BillingPlanService, useValue: mockBillingPlanService}, + {provide: Router, useValue: mockRouter}, + {provide: Location, useValue: mockLocation}, { provide: NbOverlayService, useValue: {snapshot: {params: {id: '1'}}}, }, + {provide: APP_CONFIG, useValue: {}}, { provide: ToasterService, useValue: {snapshot: {params: {id: '1'}}}, @@ -40,13 +98,14 @@ describe('ButtonRendererComponent', () => { beforeEach(() => { const fixture = TestBed.createComponent(ButtonRendererComponent); component = fixture.componentInstance; - toastrService = TestBed.inject(ToasterService); - billingPlanService = TestBed.inject(BillingPlanService); fb = TestBed.inject(FormBuilder); - location = TestBed.inject(Location); fixture.detectChanges(); }); + afterEach(() => { + (mockBillingPlanService.deletePlan as jasmine.Spy).calls.reset(); // Reset spy after each test + }); + it('should create', () => { expect(component).toBeTruthy(); }); @@ -62,6 +121,31 @@ describe('ButtonRendererComponent', () => { }); }); + it('should initialize the params on agInit', () => { + const params: ICellRendererParams = { + node: { + data: { + id: 1, + }, + }, + } as any; + component.agInit(params); + expect(component.params).toEqual(params); + }); + + it('should navigate to edit plan on editPlan', () => { + const params: ICellRendererParams = { + node: { + data: { + id: 1, + }, + }, + } as any; + component.agInit(params); + component.editPlan(null); + expect(mockRouter.navigate).toHaveBeenCalledWith(['/main/edit-plan/1']); + }); + it('should return true on refresh method', () => { const params = {} as any; const result = component.refresh(params); diff --git a/projects/saas-ui/src/app/main/components/lead-list/lead-list.component.spec.ts b/projects/saas-ui/src/app/main/components/lead-list/lead-list.component.spec.ts index 230946f3..8bbe45da 100644 --- a/projects/saas-ui/src/app/main/components/lead-list/lead-list.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/lead-list/lead-list.component.spec.ts @@ -2,22 +2,70 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {LeadListComponent} from './lead-list.component'; import {ActivatedRoute} from '@angular/router'; -import {NbToastrService} from '@nebular/theme'; +import {NbCardModule, NbStatusService, NbToastrService} from '@nebular/theme'; import {of} from 'rxjs'; +import {ApiService} from '@project-lib/core/api/api.service'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {OnBoardingService} from '../../../shared/services'; +import {RouterTestingModule} from '@angular/router/testing'; +import {BrowserModule} from '@angular/platform-browser'; +import {ThemeModule} from '@project-lib/theme/theme.module'; +import {AgGridModule} from 'ag-grid-angular'; +import {GridApi, IGetRowsParams} from 'ag-grid-community'; describe('TenantComponent', () => { let component: LeadListComponent; let fixture: ComponentFixture; + let onboardingService: jasmine.SpyObj; + let mockGridApi: jasmine.SpyObj; beforeEach(async () => { + // const onboardingServiceSpy = jasmine.createSpyObj('OnBoardingService', ['getLeadList', 'getTotalLead']); + onboardingService = jasmine.createSpyObj('OnBoardingService', [ + 'getLeadList', + 'getTotalLead', + ]); + mockGridApi = jasmine.createSpyObj('GridApi', ['setDatasource']); + onboardingService.getTotalLead.and.returnValue(of({count: 2})); + onboardingService.getLeadList.and.returnValue( + of([ + { + firstName: 'John', + lastName: 'Doe', + companyName: 'Company A', + email: 'john.doe@example.com', + isValidated: false, + address: {country: 'USA'}, + }, + { + firstName: 'Jane', + lastName: 'Doe', + companyName: 'Company B', + email: 'jane.doe@example.com', + isValidated: true, + address: {country: 'Canada'}, + }, + ]), + ); + await TestBed.configureTestingModule({ declarations: [LeadListComponent], + imports: [ + RouterTestingModule, + HttpClientTestingModule, + NbCardModule, + ThemeModule, + AgGridModule, + ], providers: [ + NbStatusService, NbToastrService, + ApiService, { provide: ActivatedRoute, - useValue: {paramMap: of(new Map())}, // Mock ActivatedRoute + useValue: {paramMap: of(new Map())}, }, + {provide: OnBoardingService, useValue: onboardingService}, ], }).compileComponents(); @@ -29,4 +77,89 @@ describe('TenantComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should set up grid options correctly on initialization', () => { + expect(component.gridOptions).toBeDefined(); + expect(component.gridOptions.paginationPageSize).toBe(component.limit); + expect(component.gridOptions.paginationPageSizeSelector).toEqual([ + component.limit, + 10, + 20, + 50, + 100, + ]); + }); + + it('should set the datasource on grid ready', () => { + const params = {api: mockGridApi} as any; + component.onGridReady(params); + expect(component.gridApi.setDatasource).toHaveBeenCalled(); + }); + + it('should call getPaginatedLeads and getTotal on getRows', () => { + const params: IGetRowsParams = { + startRow: 0, + endRow: component.limit, + successCallback: jasmine.createSpy(), + failCallback: jasmine.createSpy(), + } as any; + + onboardingService.getLeadList.and.returnValue(of([])); + onboardingService.getTotalLead.and.returnValue(of({count: 0})); + + component.onGridReady({api: mockGridApi}); + const dataSource = mockGridApi.setDatasource.calls.mostRecent().args[0]; + dataSource.getRows(params); + + expect(onboardingService.getLeadList).toHaveBeenCalled(); + expect(onboardingService.getTotalLead).toHaveBeenCalled(); + }); + + it('should return paginated leads on getPaginatedLeads', () => { + const mockLeads = [ + { + firstName: 'John', + lastName: 'Doe', + companyName: 'ABC Corp', + email: 'john.doe@example.com', + isValidated: false, + address: {country: 'USA'}, + }, + ]; + + onboardingService.getLeadList.and.returnValue(of(mockLeads)); + + component.getPaginatedLeads(1, 5).subscribe(leads => { + expect(leads.length).toBe(1); + expect(leads[0].firstName).toBe('John'); + expect(leads[0].country).toBe('USA'); + }); + }); + + it('should correctly transform and paginate data', () => { + component.getPaginatedLeads(1, component.limit).subscribe(data => { + expect(data).toEqual([ + { + firstName: 'John', + lastName: 'Doe', + companyName: 'Company A', + email: 'john.doe@example.com', + country: 'USA', + }, + { + firstName: 'Jane', + lastName: 'Doe', + companyName: 'Company B', + email: 'jane.doe@example.com', + country: 'Canada', + }, + ]); + }); + }); + + it('should get total count from OnBoardingService', () => { + component.getTotal().subscribe(count => { + expect(count.count).toBeGreaterThan(0); + }); + }); }); diff --git a/projects/saas-ui/src/app/main/components/manage-plans/manage-plans.component.spec.ts b/projects/saas-ui/src/app/main/components/manage-plans/manage-plans.component.spec.ts index 585ee827..cc7c1cec 100644 --- a/projects/saas-ui/src/app/main/components/manage-plans/manage-plans.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/manage-plans/manage-plans.component.spec.ts @@ -1,43 +1,168 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - +import {TestBed, ComponentFixture} from '@angular/core/testing'; +import {RouterTestingModule} from '@angular/router/testing'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {ReactiveFormsModule} from '@angular/forms'; +import {AgGridModule} from 'ag-grid-angular'; import {ManagePlansComponent} from './manage-plans.component'; -import {ActivatedRoute} from '@angular/router'; -import {NbToastrService} from '@nebular/theme'; -import {of} from 'rxjs'; -import {TenantFacadeService} from '../../../shared/services'; -import {ThemeModule} from '@project-lib/theme/theme.module'; -import {ToasterService} from '@project-lib/theme/toaster/toaster.service'; +import {ButtonRendererComponent} from '../button-renderer/button-renderer.component'; +import {ToasterService} from '@project-lib/theme/toaster'; +import {BillingPlanService} from '../../../shared/services/billing-plan-service'; +import {OnBoardingService} from '../../../shared/services/on-boarding-service'; +import {NbStatusService, NbToastrService} from '@nebular/theme'; +import {Location} from '@angular/common'; +import {ActivatedRoute, Router} from '@angular/router'; +import {of, throwError} from 'rxjs'; import {ApiService} from '@project-lib/core/api/api.service'; +import {AnyAdapter} from '@project-lib/core/api/adapters'; +import {APP_CONFIG} from '@project-lib/app-config'; +import {GetPlanAdapter} from '../../../on-boarding/adapters'; +import {ThemeModule} from '@project-lib/theme/theme.module'; +import {GridApi} from 'ag-grid-community'; +import {BackendFilter} from '@project-lib/core/api'; +import {Plan} from '../../../shared/models'; describe('ManagePlansComponent', () => { let component: ManagePlansComponent; let fixture: ComponentFixture; + let billingPlanServiceMock: jasmine.SpyObj; + let toasterService: ToasterService; + let onboardingService: OnBoardingService; + let router: Router; + let mockToasterService: jasmine.SpyObj; + let mockApiService: jasmine.SpyObj; + let gridApiSpy: jasmine.SpyObj; + let gridApiMock: jasmine.SpyObj; beforeEach(async () => { + billingPlanServiceMock = jasmine.createSpyObj('BillingPlanService', [ + 'getPlanOptions', + 'getTotalPlan', + ]); + mockToasterService = jasmine.createSpyObj('ToasterService', [ + 'showSuccess', + ]); + mockApiService = jasmine.createSpyObj('ApiService', [ + 'get', + 'post', + 'put', + 'delete', + ]); + gridApiMock = jasmine.createSpyObj('GridApi', [ + 'setDatasource', + 'updateGridOptions', + ]); + await TestBed.configureTestingModule({ - declarations: [ManagePlansComponent], - imports: [ThemeModule], + declarations: [ManagePlansComponent, ButtonRendererComponent], + imports: [ + RouterTestingModule, + HttpClientTestingModule, + ReactiveFormsModule, + ThemeModule, + AgGridModule, + ], providers: [ + OnBoardingService, + ToasterService, NbToastrService, - TenantFacadeService, - ApiService, + NbStatusService, + Location, + GetPlanAdapter, + AnyAdapter, + {provide: BillingPlanService, useValue: billingPlanServiceMock}, { provide: ActivatedRoute, - useValue: {paramMap: of(new Map())}, // Mock ActivatedRoute - }, - { - provide: ToasterService, - useValue: {paramMap: of(new Map())}, + useValue: { + snapshot: { + params: {}, + }, + }, }, + {provide: APP_CONFIG, useValue: {}}, + {provide: ToasterService, useValue: mockToasterService}, + {provide: ApiService, useValue: mockApiService}, ], }).compileComponents(); fixture = TestBed.createComponent(ManagePlansComponent); component = fixture.componentInstance; + component.gridApi = gridApiMock; + toasterService = TestBed.inject(ToasterService); + onboardingService = TestBed.inject(OnBoardingService); + router = TestBed.inject(Router); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize gridOptions on component creation', () => { + expect(component.gridOptions).toBeTruthy(); + expect(component.gridOptions.paginationPageSize).toBe(5); + expect(component.gridOptions.rowHeight).toBe(60); + }); + + it('should initialize grid options correctly', () => { + expect(component.gridOptions.pagination).toBeTrue(); + expect(component.gridOptions.rowModelType).toBe('infinite'); + expect(component.gridOptions.paginationPageSize).toBe(component.limit); + expect(component.gridOptions.cacheBlockSize).toBe(component.limit); + expect(component.gridOptions.rowHeight).toBe(60); + expect(component.gridOptions.defaultColDef.flex).toBe(1); + }); + + it('should set the datasource on grid ready', () => { + const setDatasourceSpy = jasmine.createSpy('setDatasource'); + const mockApi = {setDatasource: setDatasourceSpy}; + const params = {api: mockApi} as any; + + component.onGridReady(params); + + expect(setDatasourceSpy).toHaveBeenCalled(); + }); + + it('should navigate to the add-plan route', () => { + const navigateSpy = spyOn(router, 'navigate'); + component.showManagePlan(); + expect(navigateSpy).toHaveBeenCalledWith(['/main/add-plan']); + }); + + it('should set gridApi on grid ready', () => { + const params = { + api: jasmine.createSpyObj('GridApi', ['setDatasource']), + }; + component.onGridReady(params); + expect(component.gridApi).toBe(params.api); + expect(params.api.setDatasource).toHaveBeenCalled(); + }); + + it('should get paginated plans', () => { + const mockPlans = [ + { + id: '1', + name: 'Basic Plan', + description: 'Basic Plan Description', + cycleName: 'Monthly', + currencyName: 'USD', + billingCycleId: '123', + currencyId: '342', + tier: 1, + price: 10, + }, + ]; + billingPlanServiceMock.getPlanOptions.and.returnValue(of(mockPlans)); + + component.getPaginatedPlans(1, 5).subscribe(plans => { + expect(plans.length).toBe(1); + expect(plans[0].name).toBe('Basic Plan'); + }); + + const filter = { + offset: 0, + limit: 5, + include: [{relation: 'currency'}, {relation: 'billingCycle'}], + }; + expect(billingPlanServiceMock.getPlanOptions).toHaveBeenCalledWith(filter); + }); }); diff --git a/projects/saas-ui/src/app/main/components/onboarding-tenant-list/onboarding-tenant-list.component.spec.ts b/projects/saas-ui/src/app/main/components/onboarding-tenant-list/onboarding-tenant-list.component.spec.ts index beff6e0c..5f9a3416 100644 --- a/projects/saas-ui/src/app/main/components/onboarding-tenant-list/onboarding-tenant-list.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/onboarding-tenant-list/onboarding-tenant-list.component.spec.ts @@ -1,99 +1,89 @@ -import { - ComponentFixture, - TestBed, - fakeAsync, - tick, -} from '@angular/core/testing'; -import {Router} from '@angular/router'; -import {HttpClientTestingModule} from '@angular/common/http/testing'; -import {ActivatedRoute} from '@angular/router'; -import {Location} from '@angular/common'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {OnboardingTenantListComponent} from './onboarding-tenant-list.component'; +import {ActivatedRoute, Router} from '@angular/router'; +import {NbCardModule, NbStatusService, NbToastrService} from '@nebular/theme'; import {of} from 'rxjs'; import {TenantFacadeService} from '../../../shared/services/tenant-list-facade.service'; import {OnboardingTenantListComponent} from './onboarding-tenant-list.component'; import {APP_CONFIG} from '@project-lib/app-config'; -import {GridApi} from 'ag-grid-community'; import {ThemeModule} from '@project-lib/theme/theme.module'; -import {NbThemeModule} from '@nebular/theme'; import {AgGridModule} from 'ag-grid-angular'; +import {TenantStatus} from '../../../shared/enum/tenant-status.enum'; +import {GridApi} from 'ag-grid-community'; +import {RouterTestingModule} from '@angular/router/testing'; +import {TenantRegistrationComponent} from '../tenant-registration/tenant-registration.component'; +const mockAppConfig = {baseApiUrl: 'https://api.example.com/'}; describe('OnboardingTenantListComponent', () => { let component: OnboardingTenantListComponent; let fixture: ComponentFixture; let tenantFacadeService: jasmine.SpyObj; - let router: jasmine.SpyObj; - let mockGridApi: jasmine.SpyObj; - - const mockAppConfig = { - baseApiUrl: 'https://api.example.com', - }; - - const mockTenantData = [ - { - id: 1, - name: 'Company A', - firstName: 'John', - lastName: 'Doe', - email: 'john.doe@companyA.com', - address: { - zip: '12345', - country: 'USA', - }, - subscription: { - plan: { - name: 'Premium', - }, - status: 'Active', - startDate: '2022-01-01T00:00:00Z', - endDate: '2023-01-01T00:00:00Z', - }, - }, - ]; + let router: Router; + let routerSpy: jasmine.SpyObj; + let tenantFacadeSpy: jasmine.SpyObj; + let gridApiMock: jasmine.SpyObj; beforeEach(async () => { - const tenantFacadeServiceSpy = jasmine.createSpyObj('TenantFacadeService', [ - 'getTenantDetails', - ]); - const routerSpy = jasmine.createSpyObj('Router', ['navigate']); - const gridApiSpy = jasmine.createSpyObj('GridApi', [ - 'setDatasource', - 'refreshCells', + tenantFacadeSpy = jasmine.createSpyObj('TenantFacadeService', [ + 'getTenantList', + 'getTotalTenant', ]); + routerSpy = jasmine.createSpyObj('Router', ['navigate']); + // Define what the spy should return when the methods are called + tenantFacadeSpy.getTenantList.and.returnValue( + of([ + { + name: 'Company A', + key: 'company-a', + domains: ['domain1.com'], + address: {zip: '12345', country: 'USA'}, + status: TenantStatus.ACTIVE, + }, + { + name: 'Company B', + key: 'company-b', + domains: ['domain2.com'], + address: {zip: '67890', country: 'Canada'}, + status: TenantStatus.INACTIVE, + }, + ]), + ); + tenantFacadeSpy.getTotalTenant.and.returnValue(of({count: 2})); await TestBed.configureTestingModule({ declarations: [OnboardingTenantListComponent], imports: [ HttpClientTestingModule, ThemeModule, - NbThemeModule.forRoot(), + NbCardModule, AgGridModule, + RouterTestingModule.withRoutes([ + { + path: 'main/create-tenant', + component: TenantRegistrationComponent, + }, + ]), ], providers: [ + NbToastrService, + NbStatusService, + ApiService, {provide: APP_CONFIG, useValue: mockAppConfig}, - { - provide: TenantFacadeService, - useValue: { - getTenantDetails: jasmine - .createSpy('getTenantDetails') - .and.returnValue(of(mockTenantData)), - getTotalTenant: jasmine - .createSpy('getTotalTenant') - .and.returnValue(of()), - }, - }, - {provide: Router, useValue: routerSpy}, - {provide: GridApi, useValue: gridApiSpy}, { provide: ActivatedRoute, - useValue: {snapshot: {paramMap: {get: () => '1'}}}, + useValue: {paramMap: of(new Map())}, }, - Location, + {provide: TenantFacadeService, useValue: tenantFacadeSpy}, ], }).compileComponents(); fixture = TestBed.createComponent(OnboardingTenantListComponent); - router = TestBed.inject(Router) as jasmine.SpyObj; - mockGridApi = TestBed.inject(GridApi) as jasmine.SpyObj; + tenantFacadeService = TestBed.inject( + TenantFacadeService, + ) as jasmine.SpyObj; + gridApiMock = jasmine.createSpyObj('GridApi', ['updateGridOptions']); + router = TestBed.inject(Router); component = fixture.componentInstance; tenantFacadeService = TestBed.inject( TenantFacadeService, @@ -106,26 +96,94 @@ describe('OnboardingTenantListComponent', () => { it('should initialize grid options correctly', () => { expect(component.gridOptions.pagination).toBe(true); - expect(component.gridOptions.paginationPageSize).toBe(10); - expect(component.gridOptions.rowHeight).toBe(60); + expect(component.gridOptions.rowModelType).toBe('infinite'); + expect(component.gridOptions.paginationPageSize).toBe(component.limit); + expect(component.gridOptions.paginationPageSizeSelector).toEqual([ + component.limit, + 10, + 20, + 50, + 100, + ]); + }); + + it('should transform and paginate data correctly', () => { + component.getPaginatedTenants(1, component.limit).subscribe(data => { + expect(data).toEqual([ + { + name: 'Company A', + key: 'company-a', + domains: 'domain1.com', + address: ' 12345, USA', + status: 'ACTIVE', + }, + { + name: 'Company B', + key: 'company-b', + domains: 'domain2.com', + address: ' 67890, Canada', + status: 'INACTIVE', + }, + ]); + }); }); - it('should navigate to tenant registration page', () => { - component.registerTenantPage(); - expect(router.navigate).toHaveBeenCalledWith(['main/create-tenant']); + it('should return the total count from the tenant facade', () => { + component.getTotal().subscribe(count => { + expect(count).toEqual({count: 2}); + }); }); - it('should handle error in `getPaginatedTenantDetails` method', () => { - spyOn(console, 'error'); - (tenantFacadeService.getTenantDetails as jasmine.Spy).and.returnValue( - of(undefined), - ); - component.getPaginatedTenantDetails(1, 10).subscribe(data => { - expect(console.error).toHaveBeenCalledWith( - 'Error processing response:', - jasmine.anything(), + describe('onGridReady', () => { + it('should set the grid API and configure the datasource', () => { + const params = {api: gridApiMock}; + component.onGridReady(params); + expect(component.gridApi).toBe(gridApiMock); + expect(gridApiMock.updateGridOptions).toHaveBeenCalled(); + }); + }); + + describe('getPaginatedTenants', () => { + it('should fetch and map tenant data correctly', () => { + const mockTenants = [ + { + name: 'Tenant1', + key: 'tenant1', + domains: ['example.com'], + address: { + zip: '12345', + country: 'Country', + }, + status: 'ACTIVE', + }, + ]; + + tenantFacadeService.getTenantList.and.returnValue(of(mockTenants)); + + component.getPaginatedTenants(1, 5).subscribe(data => { + expect(data.length).toBe(1); + expect(data[0].name).toBe('Tenant1'); + expect(data[0].address).toBe(' 12345, Country'); + }); + }); + }); + + describe('createCompanyLink', () => { + it('should create a company link correctly', () => { + const params = {data: {key: 'tenant1'}, value: 'Company1'}; + const link = component.createCompanyLink(params); + console.log(link); + expect(link).toBe( + 'Company1', ); - expect(data).toEqual([]); + }); + }); + + describe('registerTenantPage', () => { + it('should navigate to create-tenant page', () => { + spyOn(router, 'navigate'); + component.registerTenantPage(); + expect(router.navigate).toHaveBeenCalledWith(['main/create-tenant']); }); }); }); diff --git a/projects/saas-ui/src/app/main/components/tenant-registration/tenant-registration.component.spec.ts b/projects/saas-ui/src/app/main/components/tenant-registration/tenant-registration.component.spec.ts index 66fd322b..6c656c78 100644 --- a/projects/saas-ui/src/app/main/components/tenant-registration/tenant-registration.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/tenant-registration/tenant-registration.component.spec.ts @@ -1,23 +1,206 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TenantRegistrationComponent } from './tenant-registration.component'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ReactiveFormsModule, FormBuilder, FormsModule} from '@angular/forms'; +import {RouterTestingModule} from '@angular/router/testing'; +import {ActivatedRoute, Router} from '@angular/router'; +import {of} from 'rxjs'; +import { + NbCardModule, + NbDynamicOverlay, + NbFocusMonitor, + NbInputModule, + NbLayoutDirectionService, + NbLayoutModule, + NbOverlayContainer, + NbOverlayService, + NbPositionBuilderService, + NbStatusService, + NbThemeModule, + NbToastrService, + NbTriggerStrategyBuilderService, +} from '@nebular/theme'; +import {TenantRegistrationComponent} from './tenant-registration.component'; +import {BillingPlanService, OnBoardingService} from '../../../shared/services'; +import {ThemeModule} from '@project-lib/theme/theme.module'; +import {DOCUMENT} from '@angular/common'; describe('TenantRegistrationComponent', () => { let component: TenantRegistrationComponent; let fixture: ComponentFixture; + let toastrService: NbToastrService; + let onBoardingService: OnBoardingService; + let billingPlanService: BillingPlanService; + let router: Router; beforeEach(async () => { + const toastrServiceMock = { + show: jasmine.createSpy('show'), + }; + + const billingPlanServiceMock = { + getPlanOptions: jasmine.createSpy('getPlanOptions').and.returnValue( + of([ + {id: 1, name: 'Plan A'}, + {id: 2, name: 'Plan B'}, + ]), + ), + }; + + const onBoardingServiceMock = { + registerTenant: jasmine + .createSpy('registerTenant') + .and.returnValue(of({})), + }; + + const overlayServiceMock = {}; + + const triggerStrategyBuilderServiceMock = {}; + + class MockNbDynamicOverlay { + create() { + return { + setPositionStrategy: () => ({ + pipe: () => of({}), + }), + }; + } + } + + const positionBuilderServiceMock = { + connectedTo: jasmine.createSpy('connectedTo').and.returnValue({ + position: jasmine.createSpy('position').and.returnValue({ + adjustment: jasmine.createSpy('adjustment').and.returnValue({ + offset: jasmine.createSpy('offset').and.returnValue({ + direction: jasmine.createSpy('direction').and.returnValue({}), + }), + }), + }), + }), + }; + await TestBed.configureTestingModule({ - declarations: [ TenantRegistrationComponent ] - }) - .compileComponents(); + imports: [ + ReactiveFormsModule, + FormsModule, + NbThemeModule.forRoot(), + ThemeModule, + NbCardModule, + NbInputModule, + NbLayoutModule, + ], + declarations: [TenantRegistrationComponent], + providers: [ + {provide: OnBoardingService, useValue: onBoardingServiceMock}, + {provide: BillingPlanService, useValue: billingPlanServiceMock}, + {provide: Router, useValue: {navigate: jasmine.createSpy('navigate')}}, + {provide: NbToastrService, useValue: toastrServiceMock}, + {provide: ActivatedRoute, useValue: {params: of({leadId: '123'})}}, + ], + }).compileComponents(); fixture = TestBed.createComponent(TenantRegistrationComponent); component = fixture.componentInstance; + toastrService = TestBed.inject(NbToastrService); + onBoardingService = TestBed.inject(OnBoardingService); + billingPlanService = TestBed.inject(BillingPlanService); + router = TestBed.inject(Router); fixture.detectChanges(); }); - it('should create', () => { + it('should create the component', () => { expect(component).toBeTruthy(); }); + + it('should initialize form and fetch radio options on init', () => { + spyOn(component, 'getRadioOptions').and.callThrough(); + component.ngOnInit(); + + expect(component.tenantRegForm).toBeDefined(); + expect(component.leadId).toBe('123'); + expect(component.getRadioOptions).toHaveBeenCalled(); + }); + + it('should auto-fill the domain field based on email input', () => { + component.ngOnInit(); + const emailControl = component.tenantRegForm.get('email'); + emailControl.setValue('test@example.com'); + + expect(component.tenantRegForm.get('domains').value).toBe('example.com'); + }); + + it('should validate form with proper values', () => { + component.tenantRegForm.setValue({ + firstName: 'John', + lastName: 'Doe', + name: 'Company', + email: 'test@example.com', + address: '123 Street', + country: 'USA', + zip: '12345', + key: 'tenantKey1', + domains: 'example.com', + planId: '1', + }); + + expect(component.tenantRegForm.valid).toBeTrue(); + }); + + it('should mark form as invalid if email domain does not match', () => { + component.tenantRegForm.setValue({ + firstName: 'John', + lastName: 'Doe', + name: 'Company', + email: 'test@wrongdomain.com', + address: '123 Street', + country: 'USA', + zip: '12345', + key: 'tenantKey1', + domains: 'example.com', + planId: '1', + }); + + expect(component.tenantRegForm.valid).toBeFalse(); + expect(component.tenantRegForm.errors).toEqual({domainMismatch: true}); + }); + it('should call registerTenant on valid form submission', () => { + component.tenantRegForm.setValue({ + firstName: 'John', + lastName: 'Doe', + name: 'Company', + email: 'test@example.com', + address: '123 Street', + country: 'USA', + zip: 12345, + key: 'tenantKey1', + domains: 'example.com', + planId: '1', + }); + + component.onSubmit(); + + expect(onBoardingService.registerTenant).toHaveBeenCalledWith({ + name: 'Company', + contact: { + firstName: 'John', + lastName: 'Doe', + email: 'test@example.com', + isPrimary: true, + }, + address: '123 Street', + zip: 12345, + country: 'USA', + key: 'tenantKey1', + domains: ['example.com'], + planId: '1', + }); + expect(toastrService.show).toHaveBeenCalledWith( + 'Tenant Added , successfully', + ); + expect(router.navigate).toHaveBeenCalledWith(['main/onboard-tenant-list']); + }); + + it('should navigate back to the previous page', () => { + component.backToPriviousPage(); + + expect(router.navigate).toHaveBeenCalledWith(['main/onboard-tenant-list']); + }); }); diff --git a/projects/saas-ui/src/app/main/home/home.component.spec.ts b/projects/saas-ui/src/app/main/home/home.component.spec.ts index ca3f95b7..8795aaa1 100644 --- a/projects/saas-ui/src/app/main/home/home.component.spec.ts +++ b/projects/saas-ui/src/app/main/home/home.component.spec.ts @@ -1,6 +1,8 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HomeComponent} from './home.component'; +import {NbThemeModule} from '@nebular/theme'; +import {ThemeModule} from '@project-lib/theme/theme.module'; describe('HomeComponent', () => { let component: HomeComponent; @@ -9,6 +11,7 @@ describe('HomeComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [HomeComponent], + imports: [ThemeModule, NbThemeModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(HomeComponent); diff --git a/projects/saas-ui/src/app/main/main.component.spec.ts b/projects/saas-ui/src/app/main/main.component.spec.ts index 5149d5d2..9f0d86da 100644 --- a/projects/saas-ui/src/app/main/main.component.spec.ts +++ b/projects/saas-ui/src/app/main/main.component.spec.ts @@ -1,36 +1,101 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {MainComponent} from './main.component'; +import {Router, RouterModule} from '@angular/router'; +import {Location} from '@angular/common'; import {ActivatedRoute} from '@angular/router'; -import {of} from 'rxjs'; +import { + NbMenuService, + NbSidebarService, + NbMenuItem, + NbThemeService, + NbSpinnerService, + NbOverlayContainer, + NbThemeModule, + NbCardModule, + NbInputModule, + NbLayoutModule, + NbSidebarModule, + NbRestoreScrollTopHelper, +} from '@nebular/theme'; +import {of, Subject, throwError} from 'rxjs'; +import {concatMap, takeUntil} from 'rxjs/operators'; +import {MainComponent} from './main.component'; +import {AuthService, LoggedInUserDM} from '@project-lib/core/auth'; +import {IconPacksManagerService} from '@project-lib/theme/services'; +import {RouteComponentBaseDirective} from '@project-lib/core/route-component-base'; +import {ThemeModule} from '@project-lib/theme/theme.module'; +import {APP_CONFIG} from '@project-lib/app-config'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {RouterTestingModule} from '@angular/router/testing'; describe('MainComponent', () => { let component: MainComponent; let fixture: ComponentFixture; + let authService: AuthService; + let menuService: NbMenuService; + let sidebarServiceMock: NbSidebarService; + let iconMgr: IconPacksManagerService; + let router: Router; + let routerMock; - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [MainComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(MainComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); + // Mock services + const authServiceMock = { + currentUser: jasmine + .createSpy('currentUser') + .and.returnValue(of(new LoggedInUserDM())), + logout: jasmine.createSpy('logout').and.returnValue(of(void 0)), + logoutCognito: jasmine + .createSpy('logoutCognito') + .and.returnValue(of(void 0)), + }; beforeEach(async () => { + const nbRestoreScrollTopHelperMock = { + shouldRestore: jasmine + .createSpy('shouldRestore') + .and.returnValue(of(true)), // mock the observable + }; await TestBed.configureTestingModule({ declarations: [MainComponent], + imports: [ + ReactiveFormsModule, + FormsModule, + RouterTestingModule.withRoutes([]), + NbThemeModule.forRoot(), + NbLayoutModule, + NbCardModule, + NbSidebarModule, + NbInputModule, + ThemeModule, + ], providers: [ + {provide: AuthService, useValue: authServiceMock}, + {provide: Location, useValue: {}}, + {provide: APP_CONFIG, useValue: {}}, + {provide: ActivatedRoute, useValue: {params: of({})}}, { - provide: ActivatedRoute, - useValue: {paramMap: of(new Map())}, // Mock ActivatedRoute + provide: NbRestoreScrollTopHelper, + useValue: nbRestoreScrollTopHelperMock, }, ], }).compileComponents(); + + fixture = TestBed.createComponent(MainComponent); + component = fixture.componentInstance; + authService = TestBed.inject(AuthService); + menuService = TestBed.inject(NbMenuService); + sidebarServiceMock = jasmine.createSpyObj('NbSidebarService', ['toggle']); + router = TestBed.inject(Router); + routerMock = jasmine.createSpyObj('Router', ['navigate']); + fixture.detectChanges(); }); - it('should create', () => { + it('should create the component', () => { expect(component).toBeTruthy(); }); + + it('should navigate to correct link on menu click', () => { + spyOn(router, 'navigate'); + component.navigate('link'); + expect(router.navigate).toHaveBeenCalledWith(['link']); + }); }); diff --git a/projects/saas-ui/src/app/on-boarding/adapters/get-plan-adapter.service.spec.ts b/projects/saas-ui/src/app/on-boarding/adapters/get-plan-adapter.service.spec.ts new file mode 100644 index 00000000..2e1d77f8 --- /dev/null +++ b/projects/saas-ui/src/app/on-boarding/adapters/get-plan-adapter.service.spec.ts @@ -0,0 +1,31 @@ +import {TestBed} from '@angular/core/testing'; +import {Plan} from '../../shared/models'; +import {GetPlanAdapter} from './get-plan-adapter.service'; + +describe('GetPlanAdapter', () => { + let adapter: GetPlanAdapter; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [GetPlanAdapter], + }); + adapter = TestBed.inject(GetPlanAdapter); + }); + + describe('adaptToModel', () => { + it('should handle empty response', () => { + const rawResponse: any[] = []; + const expectedPlans: Plan[] = []; + const result = adapter.adaptToModel(rawResponse); + expect(result).toEqual(expectedPlans); + }); + }); + + describe('adaptFromModel', () => { + it('should return data as-is', () => { + const data = {key: 'value'}; + const result = adapter.adaptFromModel(data); + expect(result).toEqual(data); + }); + }); +}); diff --git a/projects/saas-ui/src/app/on-boarding/commands/add-lead-command.spec.ts b/projects/saas-ui/src/app/on-boarding/commands/add-lead-command.spec.ts new file mode 100644 index 00000000..4de8882c --- /dev/null +++ b/projects/saas-ui/src/app/on-boarding/commands/add-lead-command.spec.ts @@ -0,0 +1,25 @@ +import {ApiService, IAdapter, PostAPICommand} from '@project-lib/core/api'; +import {Lead} from '../../shared/models'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {AddLeadCommand} from './add-lead-command'; + +describe('AddLeadCommand', () => { + let addLeadCommand: AddLeadCommand; + let apiService: ApiService; + let adapter: IAdapter; + let appConfig: IAnyObject; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['post']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = jasmine.createSpyObj('IAnyObject', [ + 'baseApiUrl', + 'tenantMgmtFacadeUrl', + ]); + addLeadCommand = new AddLeadCommand(apiService, adapter, appConfig); + }); + + it('should create an instance of AddLeadCommand', () => { + expect(addLeadCommand).toBeTruthy(); + }); +}); diff --git a/projects/saas-ui/src/app/on-boarding/commands/add-tenant-lead.command.spec.ts b/projects/saas-ui/src/app/on-boarding/commands/add-tenant-lead.command.spec.ts new file mode 100644 index 00000000..ba62b0eb --- /dev/null +++ b/projects/saas-ui/src/app/on-boarding/commands/add-tenant-lead.command.spec.ts @@ -0,0 +1,40 @@ +import {TestBed} from '@angular/core/testing'; +import {ApiService, IAdapter, PostAPICommand} from '@project-lib/core/api'; + +import {Tenant} from '../../shared/models'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {of} from 'rxjs'; +import {AddTenantFromLeadCommand} from './add-tenant-lead.command'; + +describe('AddTenantFromLeadCommand', () => { + let apiServiceMock: jasmine.SpyObj; + let adapterMock: jasmine.SpyObj>; + let appConfigMock: IAnyObject; + let command: AddTenantFromLeadCommand; + const leadId = 'testLeadId'; + + beforeEach(() => { + apiServiceMock = jasmine.createSpyObj('ApiService', ['post']); + adapterMock = jasmine.createSpyObj('IAdapter', ['transform']); + appConfigMock = { + baseApiUrl: 'https://api.example.com', + tenantMgmtFacadeUrl: '/tenants', + }; + command = new AddTenantFromLeadCommand( + apiServiceMock, + adapterMock, + leadId, + appConfigMock, + ); + }); + + it('should create an instance of AddTenantFromLeadCommand', () => { + expect(command).toBeTruthy(); + }); + + it('should set parameters correctly', () => { + const tenant: Tenant = {id: '1', name: 'Tenant 1'}; + command.parameters = {data: tenant}; + expect(command.parameters).toEqual({data: tenant}); + }); +}); diff --git a/projects/saas-ui/src/app/on-boarding/commands/get-lead-by-id.command.spec.ts b/projects/saas-ui/src/app/on-boarding/commands/get-lead-by-id.command.spec.ts new file mode 100644 index 00000000..29038bc3 --- /dev/null +++ b/projects/saas-ui/src/app/on-boarding/commands/get-lead-by-id.command.spec.ts @@ -0,0 +1,32 @@ +import {GetAPICommand, IAdapter, IApiService} from '@project-lib/core/api'; +import {Lead} from '../../shared/models'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {GetLeadByIdCommand} from './get-lead-by-id.command'; + +describe('GetLeadByIdCommand', () => { + let getLeadByIdCommand: GetLeadByIdCommand; + let apiServiceMock: jasmine.SpyObj; + let adapterMock: jasmine.SpyObj>; + let leadId: string; + let appConfig: IAnyObject; + + beforeEach(() => { + apiServiceMock = jasmine.createSpyObj('IApiService', ['get']); + adapterMock = jasmine.createSpyObj('IAdapter', ['adapt']); + leadId = '123'; + appConfig = { + baseApiUrl: 'http://example.com/api', + tenantmgmtServiceUrl: '/tenantmgmt', + }; + getLeadByIdCommand = new GetLeadByIdCommand( + apiServiceMock, + adapterMock, + leadId, + appConfig, + ); + }); + + it('should create an instance of GetLeadByIdCommand', () => { + expect(getLeadByIdCommand).toBeTruthy(); + }); +}); diff --git a/projects/saas-ui/src/app/on-boarding/commands/get-lead-command.spec.ts b/projects/saas-ui/src/app/on-boarding/commands/get-lead-command.spec.ts new file mode 100644 index 00000000..cd35bfb2 --- /dev/null +++ b/projects/saas-ui/src/app/on-boarding/commands/get-lead-command.spec.ts @@ -0,0 +1,24 @@ +import {ApiService, GetListAPICommand, IAdapter} from '@project-lib/core/api'; +import {Lead} from '../../shared/models'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {GetLeadListCommand} from './get-lead-command'; + +describe('GetLeadListCommand', () => { + let apiService: ApiService; + let adapter: IAdapter; + let appConfig: IAnyObject; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['get']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = jasmine.createSpyObj('IAnyObject', [ + 'baseApiUrl', + 'tenantmgmtServiceUrl', + ]); + }); + + it('should create an instance of GetLeadListCommand', () => { + const command = new GetLeadListCommand(apiService, adapter, appConfig); + expect(command).toBeTruthy(); + }); +}); diff --git a/projects/saas-ui/src/app/on-boarding/commands/get-plan.command.spec.ts b/projects/saas-ui/src/app/on-boarding/commands/get-plan.command.spec.ts new file mode 100644 index 00000000..844c7216 --- /dev/null +++ b/projects/saas-ui/src/app/on-boarding/commands/get-plan.command.spec.ts @@ -0,0 +1,60 @@ +import {TestBed} from '@angular/core/testing'; +import {IApiService, IAdapter} from '@project-lib/core/api'; +import {Plan} from '../../shared/models'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {Observable, of} from 'rxjs'; +import {GetPlanCommand} from './get-plan.command'; + +// Mock implementations +class MockApiService implements IApiService { + get(url: string, params?: any): Observable { + return of([]); // Mock data for GET requests + } + + post(url: string, body: any, params?: any): Observable { + return of({}); // Mock data for POST requests + } + + patch(url: string, body: any, params?: any): Observable { + return of({}); // Mock data for PATCH requests + } + + put(url: string, body: any, params?: any): Observable { + return of({}); // Mock data for PUT requests + } + + delete(url: string, params?: any): Observable { + return of({}); // Mock data for DELETE requests + } +} + +class MockAdapter implements IAdapter { + adaptToModel(data: Plan[]): Plan[] { + return data; + } + + adaptFromModel(data: Plan[]): Plan[] { + return data; + } +} + +const mockAppConfig: IAnyObject = { + baseApiUrl: 'https://api.example.com', + subscriptionServiceUrl: '/v1/subscriptions', +}; + +describe('GetPlanCommand', () => { + let apiService: IApiService; + let adapter: IAdapter; + let command: GetPlanCommand; + + beforeEach(() => { + apiService = new MockApiService(); + adapter = new MockAdapter(); + command = new GetPlanCommand(apiService, adapter, mockAppConfig); + }); + + it('should create an instance of GetPlanCommand', () => { + expect(command).toBeTruthy(); + }); +}); diff --git a/projects/saas-ui/src/app/on-boarding/commands/verify-email.command.spec.ts b/projects/saas-ui/src/app/on-boarding/commands/verify-email.command.spec.ts new file mode 100644 index 00000000..ab9688ac --- /dev/null +++ b/projects/saas-ui/src/app/on-boarding/commands/verify-email.command.spec.ts @@ -0,0 +1,69 @@ +import {IApiService, IAdapter, PostAPICommand} from '@project-lib/core/api'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {Observable} from 'rxjs'; +import {VerifyEmailCommand} from './verify-email.command'; + +class MockApiService implements IApiService { + get(url: string, options?: object): Observable { + throw new Error('Method not implemented.'); + } + post(url: string, payload: any, options?: object): Observable { + throw new Error('Method not implemented.'); + } + patch(url: string, payload: any, options?: object): Observable { + throw new Error('Method not implemented.'); + } + put(url: string, payload: any, options?: object): Observable { + throw new Error('Method not implemented.'); + } + delete(url: string, options?: object): Observable { + throw new Error('Method not implemented.'); + } +} +class MockAdapter implements IAdapter { + adaptToModel(resp: any): T { + throw new Error('Method not implemented.'); + } + adaptFromModel(data: Partial) { + throw new Error('Method not implemented.'); + } +} + +describe('VerifyEmailCommand', () => { + let apiService: IApiService; + let adapter: IAdapter; + let appConfig: IAnyObject; + let leadId: string; + + beforeEach(() => { + apiService = new MockApiService(); + adapter = new MockAdapter(); + leadId = '12345'; + appConfig = { + baseApiUrl: 'https://api.example.com', + tenantMgmtFacadeUrl: '/tenant-management', + }; + }); + + it('should create an instance of VerifyEmailCommand', () => { + const command = new VerifyEmailCommand( + apiService, + adapter, + leadId, + appConfig, + ); + expect(command).toBeTruthy(); + expect(command).toBeInstanceOf(PostAPICommand); + }); + + it('should set parameters correctly', () => { + const command = new VerifyEmailCommand( + apiService, + adapter, + leadId, + appConfig, + ); + command.parameters = {key: 'value'}; // Example parameters + expect(command.parameters).toEqual({key: 'value'}); + }); +}); diff --git a/projects/saas-ui/src/app/on-boarding/components/add-lead/add-lead.component.spec.ts b/projects/saas-ui/src/app/on-boarding/components/add-lead/add-lead.component.spec.ts index 74cda5b8..21c399f2 100644 --- a/projects/saas-ui/src/app/on-boarding/components/add-lead/add-lead.component.spec.ts +++ b/projects/saas-ui/src/app/on-boarding/components/add-lead/add-lead.component.spec.ts @@ -1,36 +1,120 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; - +import {ReactiveFormsModule, FormsModule} from '@angular/forms'; +import {ActivatedRoute, Router} from '@angular/router'; +import { + NbCardModule, + NbInputModule, + NbThemeModule, + NbToastrService, +} from '@nebular/theme'; +import {Location} from '@angular/common'; +import {of, throwError} from 'rxjs'; import {AddLeadComponent} from './add-lead.component'; -import {ActivatedRoute} from '@angular/router'; -import {NbToastrService} from '@nebular/theme'; -import {of} from 'rxjs'; -import {ApiService} from '@project-lib/core/api'; -import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {OnBoardingService} from '../../../shared/services/on-boarding-service'; +import {ThemeModule} from '@project-lib/theme/theme.module'; +import {Lead} from '../../../shared/models/lead.model'; describe('AddLeadComponent', () => { let component: AddLeadComponent; let fixture: ComponentFixture; + let mockOnBoardingService: jasmine.SpyObj; + let mockRouter: jasmine.SpyObj; + let mockToastrService: jasmine.SpyObj; beforeEach(async () => { + const onBoardingServiceSpy = jasmine.createSpyObj('OnBoardingService', [ + 'addLead', + ]); + const routerSpy = jasmine.createSpyObj('Router', ['navigate']); + const toastrServiceSpy = jasmine.createSpyObj('NbToastrService', ['show']); + await TestBed.configureTestingModule({ declarations: [AddLeadComponent], - imports: [HttpClientTestingModule], + imports: [ + ReactiveFormsModule, + FormsModule, + NbThemeModule.forRoot(), + NbCardModule, + NbInputModule, + ThemeModule, + ], providers: [ - NbToastrService, - ApiService, - { - provide: ActivatedRoute, - useValue: {paramMap: of(new Map())}, // Mock ActivatedRoute - }, + {provide: OnBoardingService, useValue: onBoardingServiceSpy}, + {provide: Router, useValue: routerSpy}, + {provide: NbToastrService, useValue: toastrServiceSpy}, + {provide: Location, useValue: {}}, + {provide: ActivatedRoute, useValue: {paramMap: of(new Map())}}, ], }).compileComponents(); fixture = TestBed.createComponent(AddLeadComponent); component = fixture.componentInstance; + mockOnBoardingService = TestBed.inject( + OnBoardingService, + ) as jasmine.SpyObj; + mockRouter = TestBed.inject(Router) as jasmine.SpyObj; + mockToastrService = TestBed.inject( + NbToastrService, + ) as jasmine.SpyObj; + fixture.detectChanges(); }); - it('should create', () => { + it('should create the component', () => { expect(component).toBeTruthy(); }); + + it('should have an invalid form when fields are empty', () => { + expect(component.addLeadForm.valid).toBeFalse(); + }); + + it('should validate the form fields correctly', () => { + const firstName = component.addLeadForm.controls['firstName']; + const lastName = component.addLeadForm.controls['lastName']; + const email = component.addLeadForm.controls['email']; + const zip = component.addLeadForm.controls['zip']; + + firstName.setValue(''); + lastName.setValue(''); + email.setValue('invalid-email'); + zip.setValue('invalid-zip'); + + expect(firstName.valid).toBeFalse(); + expect(lastName.valid).toBeFalse(); + expect(email.valid).toBeFalse(); + expect(zip.valid).toBeFalse(); + + firstName.setValue('John'); + lastName.setValue('Doe'); + email.setValue('john.doe@example.com'); + zip.setValue('12345'); + + expect(firstName.valid).toBeTrue(); + expect(lastName.valid).toBeTrue(); + expect(email.valid).toBeTrue(); + expect(zip.valid).toBeTrue(); + }); + + it('should show a failure message when adding lead fails', () => { + const errorMessage = 'Error occurred'; + mockOnBoardingService.addLead.and.returnValue(throwError(errorMessage)); + + component.addLeadForm.setValue({ + firstName: 'John', + lastName: 'Doe', + companyName: 'Example Inc.', + email: 'john.doe@example.com', + address: '123 Main St', + zip: '12345', + country: 'USA', + }); + + component.onSubmit(); + + expect(mockOnBoardingService.addLead).toHaveBeenCalled(); + expect(mockToastrService.show).toHaveBeenCalledWith( + 'Unable to add lead. Please check your input and try again.', + 'Failure', + ); + }); }); diff --git a/projects/saas-ui/src/app/on-boarding/components/add-tenant/add-tenant.component.spec.ts b/projects/saas-ui/src/app/on-boarding/components/add-tenant/add-tenant.component.spec.ts index 79fd9fa8..359bddb1 100644 --- a/projects/saas-ui/src/app/on-boarding/components/add-tenant/add-tenant.component.spec.ts +++ b/projects/saas-ui/src/app/on-boarding/components/add-tenant/add-tenant.component.spec.ts @@ -1,34 +1,156 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; - +import {ReactiveFormsModule, FormsModule} from '@angular/forms'; +import {ActivatedRoute, Router} from '@angular/router'; +import { + NbToastrService, + NbThemeModule, + NbCardModule, + NbInputModule, + NbLayoutModule, + NbFocusMonitor, + NbOverlayService, + NbPositionBuilderService, + NbOverlayContainer, + NbLayoutDirectionService, + NbRestoreScrollTopHelper, + NbLayoutComponent, + NbTagModule, + NbRadioModule, +} from '@nebular/theme'; +import {Location} from '@angular/common'; +import {of, throwError} from 'rxjs'; import {AddTenantComponent} from './add-tenant.component'; -import {ActivatedRoute} from '@angular/router'; -import {of} from 'rxjs'; -import {NbToastrModule, NbToastrService} from '@nebular/theme'; +import {OnBoardingService} from '../../../shared/services/on-boarding-service'; +import {BillingPlanService} from '../../../shared/services/billing-plan-service'; +import {Lead, Plan} from '../../../shared/models'; +import {ThemeModule} from '@project-lib/theme/theme.module'; +import {RouterTestingModule} from '@angular/router/testing'; +const mockActivatedRoute = { + params: of({leadId: '12345'}), +}; describe('AddTenantComponent', () => { let component: AddTenantComponent; let fixture: ComponentFixture; + let mockOnBoardingService: jasmine.SpyObj; + let mockBillingPlanService: jasmine.SpyObj; + let mockRouter: jasmine.SpyObj; + let mockToastrService: jasmine.SpyObj; beforeEach(async () => { + const nbRestoreScrollTopHelperMock = { + shouldRestore: jasmine + .createSpy('shouldRestore') + .and.returnValue(of(true)), // mock the observable + }; + const onBoardingServiceSpy = jasmine.createSpyObj('OnBoardingService', [ + 'addTenant', + 'getLeadDetails', + ]); + const billingPlanServiceSpy = jasmine.createSpyObj('BillingPlanService', [ + 'getPlanOptions', + ]); + const routerSpy = jasmine.createSpyObj('Router', ['navigate']); + const toastrServiceSpy = jasmine.createSpyObj('NbToastrService', [ + 'danger', + ]); + await TestBed.configureTestingModule({ declarations: [AddTenantComponent], - imports: [NbToastrModule.forRoot()], + imports: [ + ReactiveFormsModule, + FormsModule, + RouterTestingModule, + NbThemeModule.forRoot(), + ThemeModule, + NbLayoutModule, + NbCardModule, + NbInputModule, + NbTagModule, + NbRadioModule, + ], providers: [ - NbToastrService, - NbToastrService, + {provide: OnBoardingService, useValue: onBoardingServiceSpy}, + {provide: BillingPlanService, useValue: billingPlanServiceSpy}, + {provide: Router, useValue: routerSpy}, + {provide: Location, useValue: {}}, + {provide: ActivatedRoute, useValue: mockActivatedRoute}, { - provide: ActivatedRoute, - useValue: {paramMap: of(new Map())}, // Mock ActivatedRoute + provide: NbRestoreScrollTopHelper, + useValue: nbRestoreScrollTopHelperMock, }, ], }).compileComponents(); fixture = TestBed.createComponent(AddTenantComponent); component = fixture.componentInstance; + mockOnBoardingService = TestBed.inject( + OnBoardingService, + ) as jasmine.SpyObj; + mockBillingPlanService = TestBed.inject( + BillingPlanService, + ) as jasmine.SpyObj; + mockRouter = TestBed.inject(Router) as jasmine.SpyObj; + mockToastrService = TestBed.inject( + NbToastrService, + ) as jasmine.SpyObj; + mockBillingPlanService.getPlanOptions.and.returnValue(of([])); fixture.detectChanges(); }); - it('should create', () => { + it('should create the component', () => { expect(component).toBeTruthy(); }); + + it('should initialize the form correctly', () => { + const form = component.addTenantForm; + expect(form.get('key')).toBeTruthy(); + expect(form.get('domains')).toBeTruthy(); + expect(form.get('planId')).toBeTruthy(); + }); + + it('should fetch lead data if leadId is present', () => { + const leadData: Lead = { + email: 'test@example.com', + firstName: 'John', + lastName: 'Doe', + companyName: 'Example Corp', + isValidated: true, + address: { + city: 'Anytown', + state: 'CA', + zip: '12345', + country: 'USA', + }, + }; + + mockOnBoardingService.getLeadDetails.and.returnValue(of(leadData)); + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.leadData).toEqual(leadData); + expect(component.addTenantForm.get('domains').value).toBe('example.com'); + }); + + it('should get subscription plans', () => { + const plans: Plan[] = [ + { + id: '1', + name: 'Basic', + description: 'Basic Plan', + tier: 1, + price: 10, + billingCycleId: '12', + currencyId: '123', + }, + ]; + + mockBillingPlanService.getPlanOptions.and.returnValue(of(plans)); + + component.getRadioOptions(); + fixture.detectChanges(); + + expect(component.subscriptionPlans).toEqual(plans); + }); }); diff --git a/projects/saas-ui/src/app/on-boarding/guards/email-verify.guard.spec.ts b/projects/saas-ui/src/app/on-boarding/guards/email-verify.guard.spec.ts new file mode 100644 index 00000000..1742e8a2 --- /dev/null +++ b/projects/saas-ui/src/app/on-boarding/guards/email-verify.guard.spec.ts @@ -0,0 +1,41 @@ +import {TestBed} from '@angular/core/testing'; +import {ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router'; +import {of, throwError} from 'rxjs'; +import {EmailVerifyGuard} from './email-verify.guard'; +import {OnBoardingService} from '../../shared/services/on-boarding-service'; +import {UserSessionStoreService} from '@project-lib/core/index'; + +describe('EmailVerifyGuard', () => { + let guard: EmailVerifyGuard; + let onboardingService: jasmine.SpyObj; + let store: jasmine.SpyObj; + + beforeEach(() => { + const onboardingServiceSpy = jasmine.createSpyObj('OnBoardingService', [ + 'validateEmail', + ]); + const storeSpy = jasmine.createSpyObj('UserSessionStoreService', [ + 'saveAccessToken', + ]); + + TestBed.configureTestingModule({ + providers: [ + EmailVerifyGuard, + {provide: OnBoardingService, useValue: onboardingServiceSpy}, + {provide: UserSessionStoreService, useValue: storeSpy}, + ], + }); + + guard = TestBed.inject(EmailVerifyGuard); + onboardingService = TestBed.inject( + OnBoardingService, + ) as jasmine.SpyObj; + store = TestBed.inject( + UserSessionStoreService, + ) as jasmine.SpyObj; + }); + + it('should be created', () => { + expect(guard).toBeTruthy(); + }); +}); diff --git a/projects/saas-ui/src/app/on-boarding/on-boarding.component.spec.ts b/projects/saas-ui/src/app/on-boarding/on-boarding.component.spec.ts index 08b92adf..a91ab517 100644 --- a/projects/saas-ui/src/app/on-boarding/on-boarding.component.spec.ts +++ b/projects/saas-ui/src/app/on-boarding/on-boarding.component.spec.ts @@ -1,8 +1,9 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {OnBoardingComponent} from './on-boarding.component'; -import {NbLayoutModule} from '@nebular/theme'; +import {NbLayoutModule, NbThemeModule} from '@nebular/theme'; import {ThemeModule} from '@project-lib/theme/theme.module'; +import {RouterTestingModule} from '@angular/router/testing'; describe('OnBoardingComponent', () => { let component: OnBoardingComponent; @@ -11,7 +12,12 @@ describe('OnBoardingComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [OnBoardingComponent], - imports: [NbLayoutModule, ThemeModule], + imports: [ + NbLayoutModule, + ThemeModule, + NbThemeModule.forRoot(), + RouterTestingModule, + ], }).compileComponents(); fixture = TestBed.createComponent(OnBoardingComponent); diff --git a/projects/saas-ui/src/app/shared/auth/login/login.component.spec.ts b/projects/saas-ui/src/app/shared/auth/login/login.component.spec.ts index 4df6e3de..245a69fc 100644 --- a/projects/saas-ui/src/app/shared/auth/login/login.component.spec.ts +++ b/projects/saas-ui/src/app/shared/auth/login/login.component.spec.ts @@ -3,14 +3,38 @@ import {RouterTestingModule} from '@angular/router/testing'; import {AuthModule} from '../auth.module'; import {LoginComponent} from './login.component'; - +import { + AuthService, + LoggedInUserAdapterService, + LoginAdapterService, +} from '@project-lib/core/auth'; +import {UserSessionStoreService} from '@project-lib/core/store'; +import {AnyAdapter, ApiService} from '@project-lib/core/api'; +import {ThemeModule} from '@project-lib/theme/theme.module'; +import {SignUpAdapter} from '@project-lib/core/auth/adapters/signup-adapter.service'; +import {NgxPermissionsService} from 'ngx-permissions'; +import {APP_CONFIG} from '@project-lib/app-config'; +import {Router} from '@angular/router'; +const mockAppConfig = {}; describe('LoginComponent', () => { let component: LoginComponent; let fixture: ComponentFixture; + let router: jasmine.SpyObj; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AuthModule, RouterTestingModule], + imports: [AuthModule, RouterTestingModule, ThemeModule], + providers: [ + AuthService, + ApiService, + SignUpAdapter, + AnyAdapter, + {provide: UserSessionStoreService, useValue: {}}, + {provide: LoggedInUserAdapterService, useValue: {}}, + {provide: LoginAdapterService, useValue: {}}, + {provide: NgxPermissionsService, useValue: {}}, + {provide: APP_CONFIG, useValue: mockAppConfig}, + ], declarations: [LoginComponent], }).compileComponents(); }); @@ -21,7 +45,34 @@ describe('LoginComponent', () => { fixture.detectChanges(); }); - xit('should create', () => { + it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize form with empty values', () => { + expect(component.loginForm.value).toEqual({ + email: '', + password: '', + }); + }); + + it('should validate email field as required and email pattern', () => { + const email = component.loginForm.get('email'); + email.setValue(''); + expect(email.valid).toBeFalsy(); + + email.setValue('invalid-email'); + expect(email.valid).toBeFalsy(); + + email.setValue('valid@example.com'); + expect(email.valid).toBeTruthy(); + }); + + it('should toggle password visibility', () => { + expect(component.getInputType()).toBe('password'); + component.toggleShowPassword(); + expect(component.getInputType()).toBe('text'); + component.toggleShowPassword(); + expect(component.getInputType()).toBe('password'); + }); }); diff --git a/projects/saas-ui/src/app/shared/enum/plan-tier.enum.spec.ts b/projects/saas-ui/src/app/shared/enum/plan-tier.enum.spec.ts new file mode 100644 index 00000000..841d0ebb --- /dev/null +++ b/projects/saas-ui/src/app/shared/enum/plan-tier.enum.spec.ts @@ -0,0 +1,13 @@ +import {PlanTier} from './plan-tier.enum'; + +describe('PlanTier Enum', () => { + it('should have the correct values for each enum member', () => { + expect(PlanTier.POOLED).toBe(0); + expect(PlanTier.SILO).toBe(1); + }); + + it('should have the correct names for each enum member', () => { + expect(PlanTier[PlanTier.POOLED]).toBe('POOLED'); + expect(PlanTier[PlanTier.SILO]).toBe('SILO'); + }); +}); diff --git a/projects/saas-ui/src/app/shared/enum/subscription-status.enum.spec.ts b/projects/saas-ui/src/app/shared/enum/subscription-status.enum.spec.ts new file mode 100644 index 00000000..8eb7d958 --- /dev/null +++ b/projects/saas-ui/src/app/shared/enum/subscription-status.enum.spec.ts @@ -0,0 +1,23 @@ +import {SubscriptionStatus} from './subscription-status.enum'; + +describe('SubscriptionStatus', () => { + it('should have PENDING value', () => { + expect(SubscriptionStatus.PENDING).toBe(0); + }); + + it('should have ACTIVE value', () => { + expect(SubscriptionStatus.ACTIVE).toBe(1); + }); + + it('should have INACTIVE value', () => { + expect(SubscriptionStatus.INACTIVE).toBe(2); + }); + + it('should have CANCELLED value', () => { + expect(SubscriptionStatus.CANCELLED).toBe(3); + }); + + it('should have EXPIRED value', () => { + expect(SubscriptionStatus.EXPIRED).toBe(4); + }); +}); diff --git a/projects/saas-ui/src/app/shared/enum/tenant-status.enum.spec.ts b/projects/saas-ui/src/app/shared/enum/tenant-status.enum.spec.ts new file mode 100644 index 00000000..6230fdf8 --- /dev/null +++ b/projects/saas-ui/src/app/shared/enum/tenant-status.enum.spec.ts @@ -0,0 +1,12 @@ +import {TenantStatus} from './tenant-status.enum'; + +describe('TenantStatus', () => { + it('should have the correct enum values', () => { + expect(TenantStatus.ACTIVE).toEqual(0); + expect(TenantStatus.PENDING_PROVISION).toEqual(1); + expect(TenantStatus.PROVISIONING).toEqual(2); + expect(TenantStatus.PROVISION_FAILED).toEqual(3); + expect(TenantStatus.DEPROVISIONING).toEqual(4); + expect(TenantStatus.INACTIVE).toEqual(5); + }); +}); diff --git a/projects/saas-ui/src/app/shared/interfaces/features.interface.spec.ts b/projects/saas-ui/src/app/shared/interfaces/features.interface.spec.ts new file mode 100644 index 00000000..5365d7e8 --- /dev/null +++ b/projects/saas-ui/src/app/shared/interfaces/features.interface.spec.ts @@ -0,0 +1,39 @@ +import {Feature} from './features'; + +describe('Feature', () => { + let feature: Feature; + + beforeEach(() => { + feature = { + name: 'Test Feature', + description: 'This is a test feature', + key: 'testFeature', + type: 'boolean', + defaultValue: false, + }; + }); + + it('should have a name', () => { + expect(feature.name).toBeDefined(); + expect(typeof feature.name).toBe('string'); + }); + + it('should have a description', () => { + expect(feature.description).toBeDefined(); + expect(typeof feature.description).toBe('string'); + }); + + it('should have a key', () => { + expect(feature.key).toBeDefined(); + expect(typeof feature.key).toBe('string'); + }); + + it('should have a type', () => { + expect(feature.type).toBeDefined(); + expect(['boolean', 'number', 'string', 'object']).toContain(feature.type); + }); + + it('should have a defaultValue', () => { + expect(feature.defaultValue).toBeDefined(); + }); +}); diff --git a/projects/saas-ui/src/app/shared/models/address.model.spec.ts b/projects/saas-ui/src/app/shared/models/address.model.spec.ts new file mode 100644 index 00000000..7fec446d --- /dev/null +++ b/projects/saas-ui/src/app/shared/models/address.model.spec.ts @@ -0,0 +1,20 @@ +import {Address} from './address.model'; + +describe('Address Model', () => { + let address: Address; + + beforeEach(() => { + address = { + id: 'some-id', + address: '123 Main St', + city: 'Sample City', + state: 'CA', + zip: '12345', + country: 'USA', + }; + }); + + it('should create an Address instance', () => { + expect(address).toBeTruthy(); + }); +}); diff --git a/projects/saas-ui/src/app/shared/models/feature-values.model.spec.ts b/projects/saas-ui/src/app/shared/models/feature-values.model.spec.ts new file mode 100644 index 00000000..970a59aa --- /dev/null +++ b/projects/saas-ui/src/app/shared/models/feature-values.model.spec.ts @@ -0,0 +1,43 @@ +import {FeatureValues} from './feature-values.model'; + +describe('FeatureValues', () => { + let featureValues: FeatureValues; + + beforeEach(() => { + featureValues = new FeatureValues(); + }); + + it('should create an instance', () => { + expect(featureValues).toBeTruthy(); + }); + + it('should have featureKey property', () => { + expect(featureValues.featureKey).toBeUndefined(); + featureValues.featureKey = 'testFeatureKey'; + expect(featureValues.featureKey).toEqual('testFeatureKey'); + }); + + it('should have strategyKey property', () => { + expect(featureValues.strategyKey).toBeUndefined(); + featureValues.strategyKey = 'testStrategyKey'; + expect(featureValues.strategyKey).toEqual('testStrategyKey'); + }); + + it('should have strategyEntityId property', () => { + expect(featureValues.strategyEntityId).toBeUndefined(); + featureValues.strategyEntityId = 'testStrategyEntityId'; + expect(featureValues.strategyEntityId).toEqual('testStrategyEntityId'); + }); + + it('should have status property', () => { + expect(featureValues.status).toBeUndefined(); + featureValues.status = true; + expect(featureValues.status).toEqual(true); + }); + + it('should have value property', () => { + expect(featureValues.value).toBeUndefined(); + featureValues.value = 'testValue'; + expect(featureValues.value).toEqual('testValue'); + }); +}); diff --git a/projects/saas-ui/src/app/shared/models/feature.model.spec.ts b/projects/saas-ui/src/app/shared/models/feature.model.spec.ts new file mode 100644 index 00000000..6e5829f7 --- /dev/null +++ b/projects/saas-ui/src/app/shared/models/feature.model.spec.ts @@ -0,0 +1,43 @@ +import {Features} from './feature.model'; + +describe('Features', () => { + it('should set properties correctly through the constructor', () => { + const data = { + id: '1', + name: 'Test Feature', + description: 'A test feature description', + key: 'test-feature', + type: 'boolean' as const, + defaultValue: true, + value: {featureKey: 'test-feature', status: 'true', value: true} as any, // Replace `as any` with appropriate type if necessary + }; + + const feature = new Features(data); + + expect(feature.id).toEqual('1'); + expect(feature.name).toEqual('Test Feature'); + expect(feature.description).toEqual('A test feature description'); + expect(feature.key).toEqual('test-feature'); + expect(feature.type).toEqual('boolean'); + expect(feature.defaultValue).toEqual(true); + expect(feature.value).toEqual(data.value); + }); + + it('should handle partial data in the constructor', () => { + const data = { + name: 'Partial Feature', + type: 'string' as const, + defaultValue: 'default', + }; + + const feature = new Features(data); + + expect(feature.id).toBeUndefined(); + expect(feature.name).toEqual('Partial Feature'); + expect(feature.description).toBeUndefined(); + expect(feature.key).toBeUndefined(); + expect(feature.type).toEqual('string'); + expect(feature.defaultValue).toEqual('default'); + expect(feature.value).toBeUndefined(); + }); +}); diff --git a/projects/saas-ui/src/app/shared/models/lead.model.spec.ts b/projects/saas-ui/src/app/shared/models/lead.model.spec.ts new file mode 100644 index 00000000..4d849e73 --- /dev/null +++ b/projects/saas-ui/src/app/shared/models/lead.model.spec.ts @@ -0,0 +1,66 @@ +import {Lead} from './lead.model'; +import {Address} from './address.model'; + +describe('Lead', () => { + let lead: Lead; + + beforeEach(() => { + lead = new Lead(); + }); + + it('should create an instance', () => { + expect(lead).toBeTruthy(); + }); + + it('should have id property', () => { + expect(lead.id).toBeUndefined(); + }); + + it('should have companyName property', () => { + expect(lead.companyName).toBeUndefined(); + }); + + it('should have email property', () => { + expect(lead.email).toBeUndefined(); + }); + + it('should have isValidated property', () => { + expect(lead.isValidated).toBeUndefined(); + }); + + it('should have country property', () => { + expect(lead.country).toBeUndefined(); + }); +}); + +describe('Address', () => { + let address: Address; + + beforeEach(() => { + address = new Address(); + }); + + it('should create an instance', () => { + expect(address).toBeTruthy(); + }); + + it('should have id property', () => { + expect(address.id).toBeUndefined(); + }); + + it('should have address property', () => { + expect(address.address).toBeUndefined(); + }); + + it('should have city property', () => { + expect(address.city).toBeUndefined(); + }); + + it('should have state property', () => { + expect(address.state).toBeUndefined(); + }); + + it('should have zip property', () => { + expect(address.zip).toBeUndefined(); + }); +}); diff --git a/projects/saas-ui/src/app/shared/models/plan.model.spec.ts b/projects/saas-ui/src/app/shared/models/plan.model.spec.ts new file mode 100644 index 00000000..714b300b --- /dev/null +++ b/projects/saas-ui/src/app/shared/models/plan.model.spec.ts @@ -0,0 +1,34 @@ +import {PlanTier} from '../enum/plan-tier.enum'; +import {Plan} from './plan.model'; + +describe('Plan', () => { + let plan: Plan; + + beforeEach(() => { + plan = new Plan(); + }); + + it('should have an id property', () => { + expect(plan.id).toBeUndefined(); + }); + + it('should have a name property', () => { + expect(plan.name).toBeUndefined(); + }); + + it('should have a description property', () => { + expect(plan.description).toBeUndefined(); + }); + + it('should have a price property', () => { + expect(plan.price).toBeUndefined(); + }); + + it('should have a metadata property', () => { + expect(plan.metadata).toBeUndefined(); + }); + + it('should have a size property', () => { + expect(plan.size).toBeUndefined(); + }); +}); diff --git a/projects/saas-ui/src/app/shared/models/plans-features.model.spec.ts b/projects/saas-ui/src/app/shared/models/plans-features.model.spec.ts new file mode 100644 index 00000000..ffe8cc22 --- /dev/null +++ b/projects/saas-ui/src/app/shared/models/plans-features.model.spec.ts @@ -0,0 +1,77 @@ +import {Features} from './feature.model'; +import {PlanWithFeatures} from './plans-features.model'; + +describe('PlanWithFeatures', () => { + let planWithFeatures: PlanWithFeatures; + + beforeEach(() => { + planWithFeatures = new PlanWithFeatures(); + planWithFeatures.name = 'Enterprise Plan'; + planWithFeatures.tier = 'SILVER'; + planWithFeatures.size = 'Large'; + planWithFeatures.features = [ + new Features({ + id: '1', + name: 'Feature A', + description: 'Description for Feature A', + key: 'featureA', + type: 'boolean', + defaultValue: false, + }), + new Features({ + id: '2', + name: 'Feature B', + description: 'Description for Feature B', + key: 'featureB', + type: 'number', + defaultValue: 10, + }), + ]; + }); + + it('should create an instance of PlanWithFeatures', () => { + expect(planWithFeatures).toBeTruthy(); + expect(planWithFeatures).toBeInstanceOf(PlanWithFeatures); + }); + + it('should have name property', () => { + expect(planWithFeatures.name).toBe('Enterprise Plan'); + expect(typeof planWithFeatures.name).toBe('string'); + }); + + it('should have tier property', () => { + expect(planWithFeatures.tier).toBe('SILVER'); + expect(typeof planWithFeatures.tier).toBe('string'); + }); + + it('should have size property', () => { + expect(planWithFeatures.size).toBe('Large'); + expect(typeof planWithFeatures.size).toBe('string'); + }); + + it('should have features property', () => { + expect(planWithFeatures.features).toBeDefined(); + expect(Array.isArray(planWithFeatures.features)).toBe(true); + expect(planWithFeatures.features.length).toBeGreaterThan(0); + expect(planWithFeatures.features[0]).toBeInstanceOf(Features); + }); + + it('should handle empty features array', () => { + const emptyPlanWithFeatures = new PlanWithFeatures(); + emptyPlanWithFeatures.name = 'Basic Plan'; + emptyPlanWithFeatures.tier = 'BRONZE'; + emptyPlanWithFeatures.size = 'Small'; + emptyPlanWithFeatures.features = []; + + expect(emptyPlanWithFeatures.features).toEqual([]); + }); + + it('should handle undefined values', () => { + const undefinedPlanWithFeatures = new PlanWithFeatures(); + + expect(undefinedPlanWithFeatures.name).toBeUndefined(); + expect(undefinedPlanWithFeatures.tier).toBeUndefined(); + expect(undefinedPlanWithFeatures.size).toBeUndefined(); + expect(undefinedPlanWithFeatures.features).toBeUndefined(); + }); +}); diff --git a/projects/saas-ui/src/app/shared/models/tenant.model.spec.ts b/projects/saas-ui/src/app/shared/models/tenant.model.spec.ts new file mode 100644 index 00000000..363b0f26 --- /dev/null +++ b/projects/saas-ui/src/app/shared/models/tenant.model.spec.ts @@ -0,0 +1,61 @@ +import {Tenant} from './tenant.model'; + +describe('Tenant', () => { + let tenant: Tenant; + + beforeEach(() => { + tenant = new Tenant(); + }); + + it('should create an instance', () => { + expect(tenant).toBeTruthy(); + }); + + it('should have id property', () => { + expect(tenant.id).toBeUndefined(); + }); + + it('should have name property', () => { + expect(tenant.name).toBeUndefined(); + }); + + it('should have description property', () => { + expect(tenant.description).toBeUndefined(); + }); + + it('should have domains property', () => { + expect(tenant.domains).toBeUndefined(); + }); + + it('should have primaryContactName property', () => { + expect(tenant.primaryContactName).toBeUndefined(); + }); + + it('should have primaryContactEmail property', () => { + expect(tenant.primaryContactEmail).toBeUndefined(); + }); + + it('should have addressId property', () => { + expect(tenant.addressId).toBeUndefined(); + }); + + it('should have key property', () => { + expect(tenant.key).toBeUndefined(); + }); + + it('should have planId property', () => { + expect(tenant.planId).toBeUndefined(); + }); + + it('should handle optional properties correctly', () => { + expect(tenant.id).toBeUndefined(); + expect(tenant.name).toBeUndefined(); + expect(tenant.description).toBeUndefined(); + expect(tenant.domains).toBeUndefined(); + expect(tenant.primaryContactName).toBeUndefined(); + expect(tenant.primaryContactEmail).toBeUndefined(); + expect(tenant.addressId).toBeUndefined(); + expect(tenant.key).toBeUndefined(); + expect(tenant.planId).toBeUndefined(); + }); +}); diff --git a/projects/saas-ui/src/app/shared/models/tenantLead.model.spec.ts b/projects/saas-ui/src/app/shared/models/tenantLead.model.spec.ts new file mode 100644 index 00000000..dab8d55b --- /dev/null +++ b/projects/saas-ui/src/app/shared/models/tenantLead.model.spec.ts @@ -0,0 +1,99 @@ +import {TenantLead} from './tenantLead.model'; + +describe('TenantLead', () => { + let tenantLead: TenantLead; + + beforeEach(() => { + tenantLead = new TenantLead(); + }); + + it('should create an instance', () => { + expect(tenantLead).toBeTruthy(); + }); + + it('should have name property', () => { + expect(tenantLead.name).toBeUndefined(); + tenantLead.name = 'John Doe'; + expect(tenantLead.name).toEqual('John Doe'); + }); + + describe('contact', () => { + it('should have contact property', () => { + expect(tenantLead.contact).toBeUndefined(); + tenantLead.contact = {}; + expect(tenantLead.contact).toEqual({}); + }); + + it('should have firstName property', () => { + expect(tenantLead.contact?.firstName).toBeUndefined(); + tenantLead.contact = {firstName: 'John'}; + expect(tenantLead.contact?.firstName).toEqual('John'); + }); + + it('should have lastName property', () => { + expect(tenantLead.contact?.lastName).toBeUndefined(); + tenantLead.contact = {lastName: 'Doe'}; + expect(tenantLead.contact?.lastName).toEqual('Doe'); + }); + + it('should have email property', () => { + expect(tenantLead.contact?.email).toBeUndefined(); + tenantLead.contact = {email: 'john.doe@example.com'}; + expect(tenantLead.contact?.email).toEqual('john.doe@example.com'); + }); + + it('should have isPrimary property', () => { + expect(tenantLead.contact?.isPrimary).toBeUndefined(); + tenantLead.contact = {isPrimary: true}; + expect(tenantLead.contact?.isPrimary).toEqual(true); + }); + }); + + it('should have address property', () => { + expect(tenantLead.address).toBeUndefined(); + tenantLead.address = '123 Main St'; + expect(tenantLead.address).toEqual('123 Main St'); + }); + + it('should have city property', () => { + expect(tenantLead.city).toBeUndefined(); + tenantLead.city = 'New York'; + expect(tenantLead.city).toEqual('New York'); + }); + + it('should have state property', () => { + expect(tenantLead.state).toBeUndefined(); + tenantLead.state = 'NY'; + expect(tenantLead.state).toEqual('NY'); + }); + + it('should have zip property', () => { + expect(tenantLead.zip).toBeUndefined(); + tenantLead.zip = 12345; + expect(tenantLead.zip).toEqual(12345); + }); + + it('should have country property', () => { + expect(tenantLead.country).toBeUndefined(); + tenantLead.country = 'USA'; + expect(tenantLead.country).toEqual('USA'); + }); + + it('should have key property', () => { + expect(tenantLead.key).toBeUndefined(); + tenantLead.key = 'abc123'; + expect(tenantLead.key).toEqual('abc123'); + }); + + it('should have domains property', () => { + expect(tenantLead.domains).toBeUndefined(); + tenantLead.domains = ['example.com', 'test.com']; + expect(tenantLead.domains).toEqual(['example.com', 'test.com']); + }); + + it('should have planId property', () => { + expect(tenantLead.planId).toBeUndefined(); + tenantLead.planId = '123'; + expect(tenantLead.planId).toEqual('123'); + }); +}); diff --git a/projects/saas-ui/src/app/shared/services/billing-plan-service.spec.ts b/projects/saas-ui/src/app/shared/services/billing-plan-service.spec.ts new file mode 100644 index 00000000..384c1a92 --- /dev/null +++ b/projects/saas-ui/src/app/shared/services/billing-plan-service.spec.ts @@ -0,0 +1,185 @@ +import {TestBed} from '@angular/core/testing'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {BillingPlanService} from './billing-plan-service'; +import {ApiService, AnyAdapter, Count} from '@project-lib/core/api'; +import {APP_CONFIG} from '@project-lib/app-config'; +import {of} from 'rxjs'; +import { + GetPlanCommand, + GetPlanByIdCommand, + GetBillingCycles, + GetCurrencyDetails, + GetBillingDetails, + AddPlanCommand, + EditPlanCommand, + DeletePlanCommand, + GetTotalPlanCommand, + GetTotalBillingPlanCommand, +} from '../../main/commands'; +import {Plan} from '../models'; +import {GetPlanAdapter} from '../../on-boarding/adapters'; + +describe('BillingPlanService', () => { + let service: BillingPlanService; + let apiService: jasmine.SpyObj; + let anyAdapter: jasmine.SpyObj; + let getPlanAdapter: jasmine.SpyObj; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + BillingPlanService, + { + provide: ApiService, + useValue: jasmine.createSpyObj('ApiService', [ + 'get', + 'post', + 'patch', + 'put', + 'delete', + ]), + }, + { + provide: AnyAdapter, + useValue: jasmine.createSpyObj('AnyAdapter', [ + 'adaptToModel', + 'adaptFromModel', + ]), + }, + { + provide: GetPlanAdapter, + useValue: jasmine.createSpyObj('GetPlanAdapter', ['adapt']), + }, + { + provide: APP_CONFIG, + useValue: { + baseApiUrl: 'https://api.example.com', + tenantMgmtFacadeUrl: '/api/v1', + subscriptionServiceUrl: '/api/v1', + }, + }, + ], + }); + + service = TestBed.inject(BillingPlanService); + apiService = TestBed.inject(ApiService) as jasmine.SpyObj; + anyAdapter = TestBed.inject(AnyAdapter) as jasmine.SpyObj; + getPlanAdapter = TestBed.inject( + GetPlanAdapter, + ) as jasmine.SpyObj; + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should create and execute GetBillingDetails', () => { + const mockBillingDetails = {data: 'billing details'}; + + spyOn(GetBillingDetails.prototype, 'execute').and.returnValue( + of(mockBillingDetails), + ); + + service.getBillingDetails().subscribe(result => { + expect(result).toEqual(mockBillingDetails); + }); + + expect(GetBillingDetails.prototype.execute).toHaveBeenCalled(); + }); + + it('should create and execute GetCurrencyDetails', () => { + const mockCurrencyDetails = {data: 'currency details'}; + + spyOn(GetCurrencyDetails.prototype, 'execute').and.returnValue( + of(mockCurrencyDetails), + ); + + service.getCurrencyDetails().subscribe(result => { + expect(result).toEqual(mockCurrencyDetails); + }); + + expect(GetCurrencyDetails.prototype.execute).toHaveBeenCalled(); + }); + + it('should create and execute GetBillingCycles', () => { + const mockBillingCycles = {data: 'billing cycles'}; + + spyOn(GetBillingCycles.prototype, 'execute').and.returnValue( + of(mockBillingCycles), + ); + + service.getBillingCycles().subscribe(result => { + expect(result).toEqual(mockBillingCycles); + }); + + expect(GetBillingCycles.prototype.execute).toHaveBeenCalled(); + }); + + it('should create and execute GetTotalBillingPlanCommand', () => { + const mockCount: Count = {count: 10}; + + spyOn(GetTotalBillingPlanCommand.prototype, 'execute').and.returnValue( + of(mockCount), + ); + + service.getTotalBillingPlan().subscribe(result => { + expect(result).toEqual(mockCount); + }); + + expect(GetTotalBillingPlanCommand.prototype.execute).toHaveBeenCalled(); + }); + + it('should create and execute AddPlanCommand', () => { + const mockPlan: Plan = {id: '1', name: 'New Plan'} as Plan; + + spyOn(AddPlanCommand.prototype, 'execute').and.returnValue(of(mockPlan)); + + service.addPlan(mockPlan).subscribe(result => { + expect(result).toEqual(mockPlan); + }); + + expect(AddPlanCommand.prototype.execute).toHaveBeenCalled(); + }); + + it('should create and execute GetPlanByIdCommand', () => { + const mockPlan: Plan = {id: '1', name: 'Plan 1'} as Plan; + const planId = '1'; + + spyOn(GetPlanByIdCommand.prototype, 'execute').and.returnValue( + of(mockPlan), + ); + + service.getPlanById(planId).subscribe(result => { + expect(result).toEqual(mockPlan); + }); + + expect(GetPlanByIdCommand.prototype.execute).toHaveBeenCalled(); + }); + + it('should create and execute EditPlanCommand', () => { + const mockPlan: Plan = {id: '1', name: 'Updated Plan'} as Plan; + + spyOn(EditPlanCommand.prototype, 'execute').and.returnValue(of(mockPlan)); + + service.editPlan(mockPlan, mockPlan.id).subscribe(result => { + expect(result).toEqual(mockPlan); + }); + + expect(EditPlanCommand.prototype.execute).toHaveBeenCalled(); + }); + + it('should create and execute GetTotalPlanCommand', () => { + const mockCount: Count = {count: 10}; + + spyOn(GetTotalPlanCommand.prototype, 'execute').and.returnValue( + of(mockCount), + ); + + service.getTotalPlan().subscribe(result => { + expect(result).toEqual(mockCount); + }); + + expect(GetTotalPlanCommand.prototype.execute).toHaveBeenCalled(); + }); +}); diff --git a/projects/saas-ui/src/app/shared/services/feature-list-service.spec.ts b/projects/saas-ui/src/app/shared/services/feature-list-service.spec.ts new file mode 100644 index 00000000..4f976a0b --- /dev/null +++ b/projects/saas-ui/src/app/shared/services/feature-list-service.spec.ts @@ -0,0 +1,135 @@ +import {TestBed} from '@angular/core/testing'; +import {ApiService, AnyAdapter} from '@project-lib/core/api'; +import {APP_CONFIG} from '@project-lib/app-config'; +import {of, throwError} from 'rxjs'; +import {GetFeaturesCommand} from '../../main/commands/get-features.command'; +import {AddFeaturesCommand} from '../../main/commands/add-features.command'; +import {Features} from '../models/feature.model'; +import {FeatureValues} from '../models/feature-values.model'; +import {PlanWithFeatures} from '../models/plans-features.model'; +import {FeatureListService} from './feature-list-service'; +import {GetPlanAdapter} from '../../on-boarding/adapters'; +import { + EditFeaturesByPlanIdCommand, + GetFeatureByPlanIdCommand, +} from '../../main/commands'; + +class MockApiService { + get() { + return of([]); + } + post() { + return of([]); + } + patch() { + return of([]); + } + put() { + return of([]); + } + delete() { + return of([]); + } +} + +class MockAnyAdapter { + adaptToModel(data: any) { + return data; + } + adaptFromModel(data: any) { + return data; + } +} + +describe('FeatureListService', () => { + let service: FeatureListService; + let apiService: ApiService; + let anyAdapter: AnyAdapter; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + GetPlanAdapter, + FeatureListService, + {provide: ApiService, useClass: MockApiService}, + {provide: AnyAdapter, useClass: MockAnyAdapter}, + { + provide: APP_CONFIG, + useValue: { + baseApiUrl: 'https://api.example.com', + tenantMgmtFacadeUrl: '/api/v1', + }, + }, + ], + }); + + service = TestBed.inject(FeatureListService); + apiService = TestBed.inject(ApiService); + anyAdapter = TestBed.inject(AnyAdapter); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should create and execute GetFeaturesCommand', () => { + spyOn(GetFeaturesCommand.prototype, 'execute').and.returnValue(of([])); + service.getFeatures().subscribe(); + expect(GetFeaturesCommand.prototype.execute).toHaveBeenCalled(); + }); + + it('should create and execute AddFeaturesCommand', () => { + const featureValues: FeatureValues[] = [ + { + featureKey: 'feature1', + value: 'value1', + strategyKey: 'plan', + strategyEntityId: 'id1', + status: false, + }, + ]; + const planId = 'test-plan-id'; + const spy = spyOn(AddFeaturesCommand.prototype, 'execute').and.returnValue( + of([]), + ); + service.addFeatures(featureValues, planId).subscribe(); + expect(spy).toHaveBeenCalledWith(); + }); + + it('should create and execute EditFeaturesCommand', () => { + const featureValues: FeatureValues[] = [ + { + featureKey: 'feature1', + value: 'value1', + strategyKey: 'plan', + strategyEntityId: 'id1', + status: false, + }, + ]; + const planId = 'test-plan-id'; + const spy = spyOn( + EditFeaturesByPlanIdCommand.prototype, + 'execute', + ).and.returnValue(of([])); + service.editFeatures(featureValues, planId).subscribe(); + expect(spy).toHaveBeenCalledWith(); + }); + + it('should create and execute GetFeatureByIdCommand', () => { + const planId = 'test-plan-id'; + const mockPlanWithFeatures: PlanWithFeatures = { + name: 'Test Plan', + tier: 'Standard', + size: 'Medium', + features: [], + }; + const executeSpy = spyOn( + GetFeatureByPlanIdCommand.prototype, + 'execute', + ).and.returnValue(of(mockPlanWithFeatures)); + service.getFeatureById(planId).subscribe(result => { + expect(result).toEqual(mockPlanWithFeatures); + }); + expect(executeSpy).toHaveBeenCalled(); + }); +}); diff --git a/projects/saas-ui/src/app/shared/services/on-boarding-service.spec.ts b/projects/saas-ui/src/app/shared/services/on-boarding-service.spec.ts new file mode 100644 index 00000000..5c63d28f --- /dev/null +++ b/projects/saas-ui/src/app/shared/services/on-boarding-service.spec.ts @@ -0,0 +1,250 @@ +import {HttpHeaders, HttpParams} from '@angular/common/http'; +import {TestBed} from '@angular/core/testing'; +import {ApiService, BackendFilter} from '@project-lib/core/api'; +import {AnyAdapter} from '@project-lib/core/api'; +import {GetPlanAdapter} from '../../on-boarding/adapters'; +import {VerifyEmailCommand} from '../../on-boarding/commands'; +import {GetLeadByIdCommand} from '../../on-boarding/commands'; +import {AddTenantFromLeadCommand} from '../../on-boarding/commands'; +import {AddLeadCommand} from '../../on-boarding/commands'; +import {GetLeadListCommand} from '../../on-boarding/commands'; +import {GetTotalLeadCommand} from '../../main/commands/get-total-lead.command'; +import {Address, Lead, Tenant} from '../models'; +import {APP_CONFIG} from '@project-lib/app-config'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {TenantLead} from '../models/tenantLead.model'; +import {RegisterTenantCommand} from '../../main/commands/register-tenant.command'; +import {OnBoardingService} from './on-boarding-service'; +import {of} from 'rxjs'; + +describe('OnBoardingService', () => { + let service: OnBoardingService; + let apiService: ApiService; + let anyAdapter: AnyAdapter; + let getPlanAdapter: GetPlanAdapter; + let appConfig: IAnyObject; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + OnBoardingService, + {provide: ApiService, useValue: {}}, + {provide: AnyAdapter, useValue: {}}, + {provide: GetPlanAdapter, useValue: {}}, + {provide: APP_CONFIG, useValue: {}}, + ], + }); + service = TestBed.inject(OnBoardingService); + apiService = TestBed.inject(ApiService); + anyAdapter = TestBed.inject(AnyAdapter); + getPlanAdapter = TestBed.inject(GetPlanAdapter); + appConfig = TestBed.inject(APP_CONFIG); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should call validateEmail command and return a response', () => { + const code = 'validCode'; + const leadId = '123'; + const expectedResponse = {leadId, token: 'validToken'}; + + spyOn(VerifyEmailCommand.prototype, 'execute').and.returnValue( + of(expectedResponse), + ); + + service.validateEmail(code, leadId).subscribe(response => { + expect(response).toEqual(expectedResponse); + }); + }); + + it('should call GetLeadByIdCommand and return lead details', () => { + const leadId = '123'; + const expectedLead: Lead = { + id: '123', + firstName: 'John', + lastName: 'Deo', + companyName: 'SF', + email: 'john.doe@example.com', + isValidated: false, + address: {country: 'USA'}, + }; + + spyOn(GetLeadByIdCommand.prototype, 'execute').and.returnValue( + of(expectedLead), + ); + + service.getLeadDetails(leadId).subscribe(lead => { + expect(lead).toEqual(expectedLead); + }); + }); + + describe('addTenant', () => { + it('should call AddTenantFromLeadCommand execute method and return a Tenant', () => { + const tenant: Tenant = {id: '1', name: 'Test Tenant'}; + const leadId = 'testLeadId'; + const expectedTenant: Tenant = {id: '1', name: 'Test Tenant'}; + const commandSpy = spyOn( + AddTenantFromLeadCommand.prototype, + 'execute', + ).and.returnValue(of(expectedTenant)); + const result = service.addTenant(tenant, leadId); + result.subscribe(response => { + expect(commandSpy).toHaveBeenCalled(); + expect(response).toEqual(expectedTenant); + }); + }); + }); + + describe('registerTenant', () => { + it('should call RegisterTenantCommand execute method and return a TenantLead', () => { + const tenantLead: TenantLead = { + name: 'Test Tenant', + contact: { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + isPrimary: true, + }, + address: '123 Main St', + city: 'Metropolis', + state: 'NY', + zip: 12345, + country: 'USA', + domains: ['example.com'], + planId: 'plan123', + }; + + const expectedTenantLead: TenantLead = { + name: 'Test Tenant', + contact: { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + isPrimary: true, + }, + address: '123 Main St', + city: 'Metropolis', + state: 'NY', + zip: 12345, + country: 'USA', + domains: ['example.com'], + planId: 'plan123', + }; + const commandSpy = spyOn( + RegisterTenantCommand.prototype, + 'execute', + ).and.returnValue(of(expectedTenantLead)); + const result = service.registerTenant(tenantLead); + result.subscribe(response => { + expect(commandSpy).toHaveBeenCalled(); + expect(response).toEqual(expectedTenantLead); + }); + }); + }); + + describe('addLead', () => { + it('should call AddLeadCommand execute method and return a Lead', () => { + const address: Address = { + country: 'USA', + city: 'Testville', + state: 'TS', + zip: '12345', + }; + + const lead: Lead = { + firstName: 'John', + lastName: 'Doe', + companyName: 'Test Company', + email: 'john.doe@example.com', + isValidated: true, + address: address, + country: 'USA', + }; + + const expectedLead: Lead = { + id: 'lead123', + firstName: 'John', + lastName: 'Doe', + companyName: 'Test Company', + email: 'john.doe@example.com', + isValidated: true, + address: address, + country: 'USA', + }; + const commandSpy = spyOn( + AddLeadCommand.prototype, + 'execute', + ).and.returnValue(of(expectedLead)); + const result = service.addLead(lead); + result.subscribe(response => { + expect(commandSpy).toHaveBeenCalled(); + expect(response).toEqual(expectedLead); + }); + }); + }); + + describe('getLeadList', () => { + it('should call GetLeadListCommand execute method and return a list of leads', () => { + const filter: BackendFilter = { + where: {country: 'USA'}, + offset: 0, + limit: 10, + }; + + const backendFilter: BackendFilter = { + where: filter.where, + offset: filter.offset, + limit: filter.limit, + }; + + const expectedLeads: Lead[] = [ + { + id: 'lead1', + firstName: 'John', + lastName: 'Doe', + companyName: 'Test Company', + email: 'john.doe@example.com', + isValidated: true, + address: { + country: 'USA', + city: 'Testville', + state: 'TS', + zip: '12345', + }, + country: 'USA', + }, + { + id: 'lead2', + firstName: 'Jane', + lastName: 'Smith', + companyName: 'Another Company', + email: 'jane.smith@example.com', + isValidated: true, + address: { + country: 'USA', + city: 'Example City', + state: 'EX', + zip: '67890', + }, + country: 'USA', + }, + ]; + const commandSpy = spyOn( + GetLeadListCommand.prototype, + 'execute', + ).and.returnValue(of(expectedLeads)); + const result = service.getLeadList(filter); + result.subscribe(response => { + expect(commandSpy).toHaveBeenCalled(); + expect(commandSpy).toHaveBeenCalledWith(); + expect(response).toEqual(expectedLeads); + }); + const params = new HttpParams().set( + 'filter', + JSON.stringify(backendFilter), + ); + }); + }); +}); From 3f4a565605559de992ea359e3d77587607a076ba Mon Sep 17 00:00:00 2001 From: Deepika Mahindroo Date: Mon, 11 Nov 2024 12:21:49 +0530 Subject: [PATCH 2/3] test(arc-saas): adding missing test cases on enhanced test cases adding missing test cases on enhanced test cases GH-67 --- .../billing-plan.component.spec.ts | 97 ++++++-- .../button-renderer.component.spec.ts | 27 ++ .../eye-icon-renderer.component.spec.ts | 53 +++- .../onboarding-tenant-list.component.spec.ts | 99 ++++---- .../tenant-detail.component.spec.ts | 45 ++++ .../tenant-registration.component.spec.ts | 150 +++++++---- .../src/app/main/main.component.spec.ts | 10 +- .../add-tenant/add-tenant.component.spec.ts | 43 +++- .../guards/email-verify.guard.spec.ts | 49 ++++ .../tenant-list-facade-service.spec.ts | 233 ++++++++++++++++++ 10 files changed, 677 insertions(+), 129 deletions(-) create mode 100644 projects/saas-ui/src/app/shared/services/tenant-list-facade-service.spec.ts diff --git a/projects/saas-ui/src/app/main/components/billing-plan/billing-plan.component.spec.ts b/projects/saas-ui/src/app/main/components/billing-plan/billing-plan.component.spec.ts index 8a25005e..a1591422 100644 --- a/projects/saas-ui/src/app/main/components/billing-plan/billing-plan.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/billing-plan/billing-plan.component.spec.ts @@ -23,21 +23,30 @@ import {Plan} from '../../../shared/models'; describe('BillingPlanComponent', () => { let component: BillingPlanComponent; let fixture: ComponentFixture; - let billingplanService: BillingPlanService; + let mockBillingPlanService: jasmine.SpyObj; let location: Location; let route: ActivatedRoute; let router: Router; - class MockBillingPlanService { - getBillingDetails(filter: BackendFilter): Observable { - return of([]); - } - getTotalBillingPlan(): Observable { - return of({count: 0}); - } - } + const mockBillingData = [ + { + companyName: 'Test Company', + userName: 'John Doe', + planName: 'Premium', + startDate: '2024-01-01', + endDate: '2024-12-31', + status: SubscriptionStatus.ACTIVE, + }, + ]; beforeEach(async () => { + mockBillingPlanService = jasmine.createSpyObj('BillingPlanService', [ + 'getPlanOptions', + 'getTotalPlan', + 'getBillingDetails', + 'getTotalBillingPlan', + ]); + await TestBed.configureTestingModule({ declarations: [BillingPlanComponent], imports: [ThemeModule, RouterTestingModule, MainModule], @@ -47,7 +56,7 @@ describe('BillingPlanComponent', () => { {provide: Location, useValue: location}, {provide: ActivatedRoute, useValue: route}, {provide: Router, useValue: router}, - {provide: BillingPlanService, useClass: MockBillingPlanService}, + {provide: BillingPlanService, useValue: mockBillingPlanService}, ], }).compileComponents(); }); @@ -55,7 +64,6 @@ describe('BillingPlanComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(BillingPlanComponent); component = fixture.componentInstance; - billingplanService = TestBed.inject(BillingPlanService); location = TestBed.inject(Location); route = TestBed.inject(ActivatedRoute); router = TestBed.inject(Router); @@ -74,15 +82,70 @@ describe('BillingPlanComponent', () => { it('should call getTotal and return count', () => { const mockCount: Count = {count: 10}; - spyOn(billingplanService, 'getTotalBillingPlan').and.returnValue( - of(mockCount), - ); + mockBillingPlanService.getTotalBillingPlan.and.returnValue(of(mockCount)); component.getTotal().subscribe(count => { expect(count).toEqual(mockCount); }); }); + it('should define correct column definitions', () => { + expect(component.colDefs.length).toBe(6); + expect(component.colDefs).toContain( + jasmine.objectContaining({ + field: 'companyName', + width: 200, + minWidth: 20, + }), + ); + expect(component.colDefs).toContain( + jasmine.objectContaining({field: 'userName', width: 200, minWidth: 20}), + ); + expect(component.colDefs).toContain( + jasmine.objectContaining({field: 'planName', width: 200, minWidth: 20}), + ); + expect(component.colDefs).toContain( + jasmine.objectContaining({field: 'startDate', width: 200, minWidth: 20}), + ); + expect(component.colDefs).toContain( + jasmine.objectContaining({field: 'endDate', width: 200, minWidth: 20}), + ); + expect(component.colDefs).toContain( + jasmine.objectContaining({field: 'status', width: 200, minWidth: 20}), + ); + }); + + it('should get paginated billing plans', done => { + const page = 1; + const limit = 5; + + // Make sure the spy is properly configured before the test + mockBillingPlanService.getBillingDetails.and.returnValue( + of(mockBillingData), + ); + + component.getPaginatedBillPlans(page, limit).subscribe({ + next: data => { + expect(mockBillingPlanService.getBillingDetails).toHaveBeenCalledWith({ + offset: limit * (page - 1), + limit: limit, + }); + expect(data[0]).toEqual({ + companyName: mockBillingData[0].companyName, + userName: mockBillingData[0].userName, + planName: mockBillingData[0].planName, + startDate: mockBillingData[0].startDate, + endDate: mockBillingData[0].endDate, + status: SubscriptionStatus[mockBillingData[0].status], + }); + done(); + }, + error: error => { + done.fail(error); + }, + }); + }); + it('should call getPaginatedBillPlans and return transformed data', () => { const mockPlans = [ { @@ -103,9 +166,7 @@ describe('BillingPlanComponent', () => { }, ]; - spyOn(billingplanService, 'getBillingDetails').and.returnValue( - of(mockPlans), - ); + mockBillingPlanService.getBillingDetails.and.returnValue(of(mockPlans)); component.getPaginatedBillPlans(1, component.limit).subscribe(data => { expect(data).toEqual([ @@ -129,7 +190,7 @@ describe('BillingPlanComponent', () => { }); }); it('should handle errors in getPaginatedBillPlans', () => { - spyOn(billingplanService, 'getBillingDetails').and.returnValue( + mockBillingPlanService.getBillingDetails.and.returnValue( throwError('Error'), ); diff --git a/projects/saas-ui/src/app/main/components/button-renderer/button-renderer.component.spec.ts b/projects/saas-ui/src/app/main/components/button-renderer/button-renderer.component.spec.ts index 020ba6c5..e210450b 100644 --- a/projects/saas-ui/src/app/main/components/button-renderer/button-renderer.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/button-renderer/button-renderer.component.spec.ts @@ -146,6 +146,19 @@ describe('ButtonRendererComponent', () => { expect(mockRouter.navigate).toHaveBeenCalledWith(['/main/edit-plan/1']); }); + it('should navigate to edit plan with correct id', () => { + const params: ICellRendererParams = { + node: { + data: { + id: 2, + }, + }, + } as any; + component.agInit(params); + component.editPlan(null); + expect(mockRouter.navigate).toHaveBeenCalledWith(['/main/edit-plan/2']); + }); + it('should return true on refresh method', () => { const params = {} as any; const result = component.refresh(params); @@ -157,4 +170,18 @@ describe('ButtonRendererComponent', () => { component.onGridReady(params); expect(component.gridApi).toEqual(params.api); }); + + it('should validate form fields', () => { + component.addPlanForm.controls['name'].setValue('Test Plan'); + component.addPlanForm.controls['description'].setValue('Test Description'); + component.addPlanForm.controls['price'].setValue(100); + component.addPlanForm.controls['currencyId'].setValue('USD'); + component.addPlanForm.controls['billingCycleId'].setValue(1); + component.addPlanForm.controls['tier'].setValue(1); + + expect(component.addPlanForm.valid).toBeTrue(); + + component.addPlanForm.controls['name'].setValue(''); + expect(component.addPlanForm.valid).toBeFalse(); + }); }); diff --git a/projects/saas-ui/src/app/main/components/eye-icon-renderer/eye-icon-renderer.component.spec.ts b/projects/saas-ui/src/app/main/components/eye-icon-renderer/eye-icon-renderer.component.spec.ts index f6504805..d4601374 100644 --- a/projects/saas-ui/src/app/main/components/eye-icon-renderer/eye-icon-renderer.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/eye-icon-renderer/eye-icon-renderer.component.spec.ts @@ -1,16 +1,23 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; -import { EyeIconRendererComponent } from './eye-icon-renderer.component'; +import {EyeIconRendererComponent} from './eye-icon-renderer.component'; +import {NbThemeModule} from '@nebular/theme'; +import {ThemeModule} from '@project-lib/theme/theme.module'; +import {Router} from '@angular/router'; +import {By} from '@angular/platform-browser'; +import {ICellRendererParams} from 'ag-grid-community'; describe('EyeIconRendererComponent', () => { let component: EyeIconRendererComponent; let fixture: ComponentFixture; + let router: jasmine.SpyObj; beforeEach(async () => { + router = jasmine.createSpyObj(['navigate']); await TestBed.configureTestingModule({ - declarations: [ EyeIconRendererComponent ] - }) - .compileComponents(); + imports: [NbThemeModule.forRoot(), ThemeModule], + declarations: [EyeIconRendererComponent], + }).compileComponents(); fixture = TestBed.createComponent(EyeIconRendererComponent); component = fixture.componentInstance; @@ -20,4 +27,40 @@ describe('EyeIconRendererComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + describe('agInit', () => { + it('should initialize params with the provided values', () => { + const mockParams = {node: {data: {id: '123'}}}; + component.agInit(mockParams); + expect(component['params']).toEqual(mockParams); + }); + }); + + describe('refresh', () => { + it('should return false when refresh is called', () => { + const result = component.refresh({}); + expect(result).toBeFalse(); + }); + }); + + it('should render the eye icon as visible when the parameter is true', () => { + const mockParams = {node: {data: {id: '123', visible: true}}}; + component.agInit(mockParams); + fixture.detectChanges(); + const iconElement = fixture.debugElement.query(By.css('.eye-icon')); + expect(iconElement).toBeTruthy(); // Check if the icon is rendered + // expect(iconElement.nativeElement.classList).toContain('visible'); // Assuming there's a class for visibility + }); + + it('should handle invalid parameters gracefully', () => { + const mockParams = null; // Simulating invalid parameters + component.agInit(mockParams); + expect(component['params']).toBeNull(); + }); + + it('should navigate to the correct route when onClick is called', () => { + // Arrange: Set up mock parameters with a specific row data ID + const mockParams = {node: {data: {id: '123'}}}; + component.agInit; + }); }); diff --git a/projects/saas-ui/src/app/main/components/onboarding-tenant-list/onboarding-tenant-list.component.spec.ts b/projects/saas-ui/src/app/main/components/onboarding-tenant-list/onboarding-tenant-list.component.spec.ts index 5f9a3416..d7ee8acd 100644 --- a/projects/saas-ui/src/app/main/components/onboarding-tenant-list/onboarding-tenant-list.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/onboarding-tenant-list/onboarding-tenant-list.component.spec.ts @@ -5,7 +5,6 @@ import {ActivatedRoute, Router} from '@angular/router'; import {NbCardModule, NbStatusService, NbToastrService} from '@nebular/theme'; import {of} from 'rxjs'; import {TenantFacadeService} from '../../../shared/services/tenant-list-facade.service'; -import {OnboardingTenantListComponent} from './onboarding-tenant-list.component'; import {APP_CONFIG} from '@project-lib/app-config'; import {ThemeModule} from '@project-lib/theme/theme.module'; import {AgGridModule} from 'ag-grid-angular'; @@ -13,6 +12,9 @@ import {TenantStatus} from '../../../shared/enum/tenant-status.enum'; import {GridApi} from 'ag-grid-community'; import {RouterTestingModule} from '@angular/router/testing'; import {TenantRegistrationComponent} from '../tenant-registration/tenant-registration.component'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {ApiService} from '@project-lib/core/api'; +import {TenantDetails} from '../../../shared/models/tenantDetails.model'; const mockAppConfig = {baseApiUrl: 'https://api.example.com/'}; describe('OnboardingTenantListComponent', () => { @@ -28,6 +30,7 @@ describe('OnboardingTenantListComponent', () => { tenantFacadeSpy = jasmine.createSpyObj('TenantFacadeService', [ 'getTenantList', 'getTotalTenant', + 'getTenantDetails', ]); routerSpy = jasmine.createSpyObj('Router', ['navigate']); // Define what the spy should return when the methods are called @@ -82,7 +85,10 @@ describe('OnboardingTenantListComponent', () => { tenantFacadeService = TestBed.inject( TenantFacadeService, ) as jasmine.SpyObj; - gridApiMock = jasmine.createSpyObj('GridApi', ['updateGridOptions']); + gridApiMock = jasmine.createSpyObj('GridApi', [ + 'updateGridOptions', + 'setDatasource', + ]); router = TestBed.inject(Router); component = fixture.componentInstance; tenantFacadeService = TestBed.inject( @@ -94,38 +100,12 @@ describe('OnboardingTenantListComponent', () => { expect(component).toBeTruthy(); }); - it('should initialize grid options correctly', () => { - expect(component.gridOptions.pagination).toBe(true); + it('should initialize gridOptions correctly', () => { + expect(component.gridOptions).toBeDefined(); + expect(component.gridOptions.pagination).toBeTrue(); expect(component.gridOptions.rowModelType).toBe('infinite'); expect(component.gridOptions.paginationPageSize).toBe(component.limit); - expect(component.gridOptions.paginationPageSizeSelector).toEqual([ - component.limit, - 10, - 20, - 50, - 100, - ]); - }); - - it('should transform and paginate data correctly', () => { - component.getPaginatedTenants(1, component.limit).subscribe(data => { - expect(data).toEqual([ - { - name: 'Company A', - key: 'company-a', - domains: 'domain1.com', - address: ' 12345, USA', - status: 'ACTIVE', - }, - { - name: 'Company B', - key: 'company-b', - domains: 'domain2.com', - address: ' 67890, Canada', - status: 'INACTIVE', - }, - ]); - }); + expect(component.gridOptions.cacheBlockSize).toBe(component.limit); }); it('should return the total count from the tenant facade', () => { @@ -133,13 +113,41 @@ describe('OnboardingTenantListComponent', () => { expect(count).toEqual({count: 2}); }); }); + it('should handle errors in retrieving tenant list gracefully', done => { + tenantFacadeSpy.getTenantDetails.and.returnValue(of([])); // Return an empty observable array to simulate error handling + + component.getPaginatedTenantDetails(1, component.limit).subscribe(data => { + expect(data).toEqual([]); // Expect data to be an empty array if an error is simulated + done(); + }); + }); + + it('should set the grid API and configure the datasource', () => { + const params = {api: gridApiMock}; + component.onGridReady(params); + expect(component.gridApi).toBe(gridApiMock); + expect(gridApiMock.setDatasource).toHaveBeenCalled(); + }); + + it('should navigate to create-tenant page', () => { + spyOn(router, 'navigate'); + component.registerTenantPage(); + expect(router.navigate).toHaveBeenCalledWith(['main/create-tenant']); + }); + + it('should handle errors in getPaginatedTenantDetails gracefully', done => { + tenantFacadeSpy.getTenantDetails.and.returnValue(of(null)); // Simulate error + component.getPaginatedTenantDetails(1, component.limit).subscribe(data => { + expect(data).toEqual([]); + done(); + }); + }); - describe('onGridReady', () => { - it('should set the grid API and configure the datasource', () => { - const params = {api: gridApiMock}; - component.onGridReady(params); - expect(component.gridApi).toBe(gridApiMock); - expect(gridApiMock.updateGridOptions).toHaveBeenCalled(); + it('should retrieve total tenant count', done => { + tenantFacadeSpy.getTotalTenant.and.returnValue(of({count: 10})); + component.getTotal().subscribe(total => { + expect(total.count).toBe(10); + done(); }); }); @@ -158,9 +166,9 @@ describe('OnboardingTenantListComponent', () => { }, ]; - tenantFacadeService.getTenantList.and.returnValue(of(mockTenants)); + tenantFacadeService.getTenantDetails.and.returnValue(of()); - component.getPaginatedTenants(1, 5).subscribe(data => { + component.getPaginatedTenantDetails(1, 5).subscribe(data => { expect(data.length).toBe(1); expect(data[0].name).toBe('Tenant1'); expect(data[0].address).toBe(' 12345, Country'); @@ -168,17 +176,6 @@ describe('OnboardingTenantListComponent', () => { }); }); - describe('createCompanyLink', () => { - it('should create a company link correctly', () => { - const params = {data: {key: 'tenant1'}, value: 'Company1'}; - const link = component.createCompanyLink(params); - console.log(link); - expect(link).toBe( - 'Company1', - ); - }); - }); - describe('registerTenantPage', () => { it('should navigate to create-tenant page', () => { spyOn(router, 'navigate'); diff --git a/projects/saas-ui/src/app/main/components/tenant-detail/tenant-detail.component.spec.ts b/projects/saas-ui/src/app/main/components/tenant-detail/tenant-detail.component.spec.ts index 3824bc81..0cd41522 100644 --- a/projects/saas-ui/src/app/main/components/tenant-detail/tenant-detail.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/tenant-detail/tenant-detail.component.spec.ts @@ -6,6 +6,7 @@ import {TenantFacadeService} from '../../../shared/services'; import {TenantDetailComponent} from './tenant-detail.component'; import {ThemeModule} from '@project-lib/theme/theme.module'; import {NbThemeModule} from '@nebular/theme'; +import {TenantDetails} from '../../../shared/models/tenantDetails.model'; describe('TenantDetailComponent', () => { let component: TenantDetailComponent; @@ -59,6 +60,50 @@ describe('TenantDetailComponent', () => { ); }); + it('should load tenant data successfully', () => { + const tenantData: TenantDetails = { + id: '1', + firstName: 'John', + lastName: 'Doe', + name: 'John Doe', + email: 'john.doe@example.com', + address: { + country: 'USA', + city: '', + state: '', + zip: '', + }, + subscription: { + plan: { + name: 'Premium', + price: 99.99, + description: '', + tier: '1', + }, + startDate: new Date('2023-01-01'), + endDate: new Date('2023-12-31'), + }, + status: 0, + key: 'er45', + }; + tenantFacadeService.getTenantDetails.and.returnValue(of([tenantData])); + fixture.detectChanges(); + expect(tenantFacadeService.getTenantDetails).toHaveBeenCalledWith({ + where: {id: '1'}, + }); + expect(component.tenantDetailForm.value).toEqual({ + tenantName: 'John Doe', + name: 'John Doe', + email: 'john.doe@example.com', + country: 'USA', + planName: 'Premium', + startDate: '01/01/2023', + endDate: '31/12/2023', + price: 99.99, + tier: '', + }); + }); + it('should navigate back to tenant list on backToPreviousPage', () => { component.backToPriviousPage(); expect(routerSpy.navigate).toHaveBeenCalledWith([ diff --git a/projects/saas-ui/src/app/main/components/tenant-registration/tenant-registration.component.spec.ts b/projects/saas-ui/src/app/main/components/tenant-registration/tenant-registration.component.spec.ts index 6c656c78..8bec1d45 100644 --- a/projects/saas-ui/src/app/main/components/tenant-registration/tenant-registration.component.spec.ts +++ b/projects/saas-ui/src/app/main/components/tenant-registration/tenant-registration.component.spec.ts @@ -2,7 +2,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {ReactiveFormsModule, FormBuilder, FormsModule} from '@angular/forms'; import {RouterTestingModule} from '@angular/router/testing'; import {ActivatedRoute, Router} from '@angular/router'; -import {of} from 'rxjs'; +import {of, throwError} from 'rxjs'; import { NbCardModule, NbDynamicOverlay, @@ -22,6 +22,7 @@ import {TenantRegistrationComponent} from './tenant-registration.component'; import {BillingPlanService, OnBoardingService} from '../../../shared/services'; import {ThemeModule} from '@project-lib/theme/theme.module'; import {DOCUMENT} from '@angular/common'; +import {TenantLead} from '../../../shared/models/tenantLead.model'; describe('TenantRegistrationComponent', () => { let component: TenantRegistrationComponent; @@ -113,7 +114,6 @@ describe('TenantRegistrationComponent', () => { it('should initialize form and fetch radio options on init', () => { spyOn(component, 'getRadioOptions').and.callThrough(); component.ngOnInit(); - expect(component.tenantRegForm).toBeDefined(); expect(component.leadId).toBe('123'); expect(component.getRadioOptions).toHaveBeenCalled(); @@ -123,45 +123,115 @@ describe('TenantRegistrationComponent', () => { component.ngOnInit(); const emailControl = component.tenantRegForm.get('email'); emailControl.setValue('test@example.com'); - expect(component.tenantRegForm.get('domains').value).toBe('example.com'); }); - it('should validate form with proper values', () => { + describe('Form Validation', () => { + it('should initialize form with empty fields and required validators', () => { + expect(component.tenantRegForm.valid).toBeFalse(); + const controls = component.tenantRegForm.controls; + expect(controls['firstName'].hasError('required')).toBeTrue(); + expect(controls['lastName'].hasError('required')).toBeTrue(); + expect(controls['name'].hasError('required')).toBeTrue(); + expect(controls['email'].hasError('required')).toBeTrue(); + expect(controls['country'].hasError('required')).toBeTrue(); + expect(controls['paymentMethod'].hasError('required')).toBeTrue(); + }); + + it('should validate email format correctly', () => { + const email = component.tenantRegForm.controls['email']; + email.setValue('invalid-email'); + expect(email.hasError('email')).toBeTrue(); + email.setValue('valid@example.com'); + expect(email.hasError('email')).toBeFalse(); + }); + + it('should validate email domain match', () => { + component.tenantRegForm.get('email').setValue('user@example.com'); + component.tenantRegForm.get('domains').setValue('example.org'); + expect(component.tenantRegForm.hasError('domainMismatch')).toBeTrue(); + component.tenantRegForm.get('domains').setValue('example.com'); + expect(component.tenantRegForm.hasError('domainMismatch')).toBeFalse(); + }); + }); + + it('should update domain field based on email input', () => { + const emailControl = component.tenantRegForm.get('email'); + emailControl.setValue('user@domain.com'); + fixture.detectChanges(); + expect(component.tenantRegForm.get('domains').value).toBe('domain.com'); + }); + + it('should show email format error on invalid email input', () => { + const emailControl = component.tenantRegForm.get('email'); + emailControl.setValue('invalid-email'); + expect(emailControl.hasError('email')).toBeTrue(); + }); + + it('should show domain mismatch error when email domain does not match', () => { + component.tenantRegForm.get('email').setValue('user@wrongdomain.com'); + component.tenantRegForm.get('domains').setValue('anotherdomain.com'); + expect(component.tenantRegForm.hasError('domainMismatch')).toBeTrue(); + }); + + it('should call registerTenant with correct parameters on valid form submission', () => { component.tenantRegForm.setValue({ - firstName: 'John', + firstName: 'Jane', lastName: 'Doe', name: 'Company', - email: 'test@example.com', - address: '123 Street', + email: 'jane@example.com', + address: '456 Street', country: 'USA', - zip: '12345', - key: 'tenantKey1', + zip: '67890', + key: 'tenantKey2', domains: 'example.com', - planId: '1', + planId: '2', + paymentMethod: 'credit-card', + comment: 'test-comment', }); - - expect(component.tenantRegForm.valid).toBeTrue(); + component.onSubmit(); + expect(onBoardingService.registerTenant).toHaveBeenCalledWith( + jasmine.objectContaining({ + name: 'Company', + contact: jasmine.objectContaining({ + firstName: 'Jane', + lastName: 'Doe', + email: 'jane@example.com', + isPrimary: true, // Ensure this is a boolean, not a string + }), + address: '456 Street', + zip: '67890', + country: 'USA', + key: 'tenantKey2', + domains: ['example.com'], + planId: '2', + paymentMethod: 'credit-card', + comment: 'test-comment', + }), + ); }); - it('should mark form as invalid if email domain does not match', () => { + it('should navigate to tenant list on successful registration', () => { + // Fill the form with valid data component.tenantRegForm.setValue({ - firstName: 'John', - lastName: 'Doe', name: 'Company', - email: 'test@wrongdomain.com', - address: '123 Street', + firstName: 'Jane', + lastName: 'Doe', + email: 'jane@example.com', + address: '456 Street', + zip: '67890', country: 'USA', - zip: '12345', - key: 'tenantKey1', + key: 'tenantKey2', domains: 'example.com', - planId: '1', + planId: '2', + paymentMethod: 'credit-card', + comment: 'test-comment', }); - - expect(component.tenantRegForm.valid).toBeFalse(); - expect(component.tenantRegForm.errors).toEqual({domainMismatch: true}); + component.onSubmit(); + expect(router.navigate).toHaveBeenCalledWith(['main/onboard-tenant-list']); }); - it('should call registerTenant on valid form submission', () => { + + it('should validate form with proper values', () => { component.tenantRegForm.setValue({ firstName: 'John', lastName: 'Doe', @@ -169,38 +239,14 @@ describe('TenantRegistrationComponent', () => { email: 'test@example.com', address: '123 Street', country: 'USA', - zip: 12345, + zip: '12345', key: 'tenantKey1', domains: 'example.com', planId: '1', + paymentMethod: 'cash', // Add a valid payment method value + comment: 'Test comment', // Optional, but add if it's part of the form }); - component.onSubmit(); - - expect(onBoardingService.registerTenant).toHaveBeenCalledWith({ - name: 'Company', - contact: { - firstName: 'John', - lastName: 'Doe', - email: 'test@example.com', - isPrimary: true, - }, - address: '123 Street', - zip: 12345, - country: 'USA', - key: 'tenantKey1', - domains: ['example.com'], - planId: '1', - }); - expect(toastrService.show).toHaveBeenCalledWith( - 'Tenant Added , successfully', - ); - expect(router.navigate).toHaveBeenCalledWith(['main/onboard-tenant-list']); - }); - - it('should navigate back to the previous page', () => { - component.backToPriviousPage(); - - expect(router.navigate).toHaveBeenCalledWith(['main/onboard-tenant-list']); + expect(component.tenantRegForm.valid).toBeTrue(); }); }); diff --git a/projects/saas-ui/src/app/main/main.component.spec.ts b/projects/saas-ui/src/app/main/main.component.spec.ts index 9f0d86da..b82b5afe 100644 --- a/projects/saas-ui/src/app/main/main.component.spec.ts +++ b/projects/saas-ui/src/app/main/main.component.spec.ts @@ -26,6 +26,7 @@ import {ThemeModule} from '@project-lib/theme/theme.module'; import {APP_CONFIG} from '@project-lib/app-config'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {RouterTestingModule} from '@angular/router/testing'; +import {LeadListComponent} from './components/lead-list/lead-list.component'; describe('MainComponent', () => { let component: MainComponent; @@ -49,6 +50,13 @@ describe('MainComponent', () => { }; beforeEach(async () => { + router = jasmine.createSpyObj('Router', ['navigate']); + sidebarServiceMock = jasmine.createSpyObj('NbSidebarService', ['toggle']); + authService = jasmine.createSpyObj('AuthService', [ + 'currentUser ', + 'logout', + ]); + menuService = jasmine.createSpyObj('NbMenuService', ['onItemClick']); const nbRestoreScrollTopHelperMock = { shouldRestore: jasmine .createSpy('shouldRestore') @@ -81,8 +89,6 @@ describe('MainComponent', () => { fixture = TestBed.createComponent(MainComponent); component = fixture.componentInstance; - authService = TestBed.inject(AuthService); - menuService = TestBed.inject(NbMenuService); sidebarServiceMock = jasmine.createSpyObj('NbSidebarService', ['toggle']); router = TestBed.inject(Router); routerMock = jasmine.createSpyObj('Router', ['navigate']); diff --git a/projects/saas-ui/src/app/on-boarding/components/add-tenant/add-tenant.component.spec.ts b/projects/saas-ui/src/app/on-boarding/components/add-tenant/add-tenant.component.spec.ts index 359bddb1..477b0992 100644 --- a/projects/saas-ui/src/app/on-boarding/components/add-tenant/add-tenant.component.spec.ts +++ b/projects/saas-ui/src/app/on-boarding/components/add-tenant/add-tenant.component.spec.ts @@ -108,7 +108,10 @@ describe('AddTenantComponent', () => { expect(form.get('domains')).toBeTruthy(); expect(form.get('planId')).toBeTruthy(); }); - + it('should not submit the form when invalid', () => { + component.onSubmit(); + expect(mockOnBoardingService.addTenant).not.toHaveBeenCalled(); + }); it('should fetch lead data if leadId is present', () => { const leadData: Lead = { email: 'test@example.com', @@ -153,4 +156,42 @@ describe('AddTenantComponent', () => { expect(component.subscriptionPlans).toEqual(plans); }); + it('should submit the form and navigate to the next page when form is valid', () => { + // Arrange + component.addTenantForm.controls['key'].setValue('tenant123'); + component.addTenantForm.controls['domains'].setValue(['example.com']); + component.addTenantForm.controls['planId'].setValue(1); + component.addTenantForm.controls['paymentDetails'].setValue({ + cardNumber: '1234567890123456', + expiryMonth: 12, + expiryYear: 2025, + cvv: '123', + }); + + mockOnBoardingService.addTenant.and.returnValue(of({})); // Mock successful submission + mockRouter.navigate.and.returnValue(Promise.resolve(true)); + + // Act + component.onSubmit(); + + // Assert + expect(mockOnBoardingService.addTenant).toHaveBeenCalled(); + expect(mockRouter.navigate).toHaveBeenCalledWith([ + '/tenant/registration/complete', + ]); + }); + + it('should not submit the form when it is invalid', () => { + // Arrange + component.addTenantForm.controls['key'].setValue(''); + component.addTenantForm.controls['domains'].setValue(''); + component.addTenantForm.controls['planId'].setValue(null); + + // Act + component.onSubmit(); + + // Assert + expect(mockOnBoardingService.addTenant).not.toHaveBeenCalled(); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); }); diff --git a/projects/saas-ui/src/app/on-boarding/guards/email-verify.guard.spec.ts b/projects/saas-ui/src/app/on-boarding/guards/email-verify.guard.spec.ts index 1742e8a2..c72d0bcc 100644 --- a/projects/saas-ui/src/app/on-boarding/guards/email-verify.guard.spec.ts +++ b/projects/saas-ui/src/app/on-boarding/guards/email-verify.guard.spec.ts @@ -35,7 +35,56 @@ describe('EmailVerifyGuard', () => { ) as jasmine.SpyObj; }); + const createRoute = ( + queryParams: any, + params: any, + ): ActivatedRouteSnapshot => { + return { + queryParamMap: { + keys: Object.keys(queryParams), + get: (key: string) => queryParams[key], + }, + params: params, + } as ActivatedRouteSnapshot; + }; + + // Mock RouterStateSnapshot + const routerState: RouterStateSnapshot = {} as RouterStateSnapshot; + it('should be created', () => { expect(guard).toBeTruthy(); }); + + it('should activate when code is provided and token is returned', done => { + const route = createRoute({code: 'test-code'}, {leadId: '123'}); + const response = {token: 'valid-token', leadId: '123'}; // Include leadId in the response + onboardingService.validateEmail.and.returnValue(of(response)); + + guard.canActivate(route, routerState).subscribe(result => { + expect(result).toBeTrue(); + expect(store.saveAccessToken).toHaveBeenCalledWith('valid-token'); + done(); + }); + }); + + it('should not activate when code is provided but no token is returned', done => { + const route = createRoute({code: 'test-code'}, {leadId: '123'}); + const response = {token: '', leadId: '123'}; // Provide leadId but empty token + onboardingService.validateEmail.and.returnValue(of(response)); + + guard.canActivate(route, routerState).subscribe(result => { + expect(result).toBeFalse(); + expect(store.saveAccessToken).not.toHaveBeenCalled(); + done(); + }); + }); + + it('should not activate when code is not provided', done => { + const route = createRoute({}, {leadId: '123'}); + + guard.canActivate(route, routerState).subscribe(result => { + expect(result).toBeFalse(); + done(); + }); + }); }); diff --git a/projects/saas-ui/src/app/shared/services/tenant-list-facade-service.spec.ts b/projects/saas-ui/src/app/shared/services/tenant-list-facade-service.spec.ts new file mode 100644 index 00000000..9bee562a --- /dev/null +++ b/projects/saas-ui/src/app/shared/services/tenant-list-facade-service.spec.ts @@ -0,0 +1,233 @@ +import {TestBed} from '@angular/core/testing'; +import {of, throwError} from 'rxjs'; +import {Tenant} from '../models'; +import {APP_CONFIG} from '@project-lib/app-config'; + +import {GetTotalTenantCommand} from '../../main/commands/get-total-tenant.command'; +import {GetTenantDetailsCommand} from '../../main/commands/get-tenant-details.command'; +import {GetTenantByIdCommand} from '../../main/commands/get-tenant-by-id.command'; +import {EditTenantCommand} from '../../main/commands/edit-tenant.command'; +import {DeleteTenantCommand} from '../../main/commands/delete-tenant.command'; +import {TenantDetails} from '../models/tenantDetails.model'; +import {ApiService, AnyAdapter} from '@project-lib/core/api'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {TenantFacadeService} from './tenant-list-facade.service'; + +describe('TenantFacadeService', () => { + let service: TenantFacadeService; + let apiService: jasmine.SpyObj; + let anyAdapter: jasmine.SpyObj; + let appConfig: IAnyObject; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', [ + 'get', + 'post', + 'put', + 'delete', + ]); + anyAdapter = jasmine.createSpyObj('AnyAdapter', ['adapt']); + appConfig = { + baseApiUrl: 'https://api.example.com', + tenantmgmtServiceUrl: '/tenantmgmt', + }; + + TestBed.configureTestingModule({ + providers: [ + TenantFacadeService, + {provide: ApiService, useValue: apiService}, + {provide: AnyAdapter, useValue: anyAdapter}, + {provide: APP_CONFIG, useValue: appConfig}, + ], + }); + service = TestBed.inject(TenantFacadeService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should handle API configuration issues', done => { + const commandSpy = jasmine.createSpyObj('GetTenantLeadListCommand', [ + 'execute', + ]); + const errorResponse = new Error('API configuration error'); + appConfig.baseApiUrl = ''; // Simulate missing configuration + commandSpy.execute.and.returnValue(throwError(() => errorResponse)); + + spyOn(service, 'getTenantList').and.callFake(() => commandSpy.execute()); + + service + .getTenantList({where: {}, offset: 0, limit: 10, order: []}) + .subscribe({ + next: () => done.fail('Expected an error'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); + describe('getTenantList', () => { + it('should return tenant list data from command', done => { + const mockResponse = [{id: 1, name: 'Tenant 1'}]; + const commandSpy = jasmine.createSpyObj('GetTenantLeadListCommand', [ + 'execute', + ]); + commandSpy.execute.and.returnValue(of(mockResponse)); + + spyOn(service, 'getTenantList').and.callFake(() => commandSpy.execute()); + + service + .getTenantList({where: {}, offset: 0, limit: 10, order: []}) + .subscribe(result => { + expect(result).toEqual(mockResponse); + done(); + }); + }); + + it('should handle error when getTenantList fails', done => { + const commandSpy = jasmine.createSpyObj('GetTenantLeadListCommand', [ + 'execute', + ]); + const errorResponse = new Error('API error'); + commandSpy.execute.and.returnValue(throwError(() => errorResponse)); + + spyOn(service, 'getTenantList').and.callFake(() => commandSpy.execute()); + + service + .getTenantList({where: {}, offset: 0, limit: 10, order: []}) + .subscribe({ + next: () => done.fail('Expected an error'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); + }); + + describe('getTotalTenant', () => { + it('should return total tenant count', done => { + const mockResponse = {count: 5}; + const commandSpy = jasmine.createSpyObj('GetTotalTenantCommand', [ + 'execute', + ]); + commandSpy.execute.and.returnValue(of(mockResponse)); + + spyOn(service, 'getTotalTenant').and.callFake(() => commandSpy.execute()); + + service.getTotalTenant().subscribe(result => { + expect(result).toEqual(mockResponse); + done(); + }); + }); + }); + + describe('getTenantById', () => { + it('should return tenant data by ID', done => { + const tenantId = 'tenant123'; + const mockResponse: Tenant = {id: tenantId, name: 'Tenant 123'}; + const commandSpy = jasmine.createSpyObj('GetTenantByIdCommand', [ + 'execute', + ]); + commandSpy.execute.and.returnValue(of(mockResponse)); + + spyOn(service, 'getTenantById').and.callFake(() => commandSpy.execute()); + + service.getTenantById(tenantId).subscribe(result => { + expect(result).toEqual(mockResponse); + done(); + }); + }); + }); + + it('should return an empty tenant list', done => { + const mockResponse: Tenant[] = []; + const commandSpy = jasmine.createSpyObj('GetTenantLeadListCommand', [ + 'execute', + ]); + commandSpy.execute.and.returnValue(of(mockResponse)); + + spyOn(service, 'getTenantList').and.callFake(() => commandSpy.execute()); + + service + .getTenantList({where: {}, offset: 0, limit: 10, order: []}) + .subscribe(result => { + expect(result).toEqual(mockResponse); + done(); + }); + }); + + it('should return an error when tenant ID does not exist', done => { + const tenantId = 'nonExistentId'; + const errorResponse = new Error('Tenant not found'); + const commandSpy = jasmine.createSpyObj('GetTenantByIdCommand', [ + 'execute', + ]); + commandSpy.execute.and.returnValue(throwError(() => errorResponse)); + + spyOn(service, 'getTenantById').and.callFake(() => commandSpy.execute()); + + service.getTenantById(tenantId).subscribe({ + next: () => done.fail('Expected an error'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); + + it('should return an error when editing a tenant with invalid data', done => { + const tenant: Tenant = {id: 'tenant123', name: ''}; // Invalid name + const errorResponse = new Error('Invalid tenant data'); + const commandSpy = jasmine.createSpyObj('EditTenantCommand', ['execute']); + commandSpy.execute.and.returnValue(throwError(() => errorResponse)); + + spyOn(service, 'editTenant').and.callFake(() => commandSpy.execute()); + + service.editTenant(tenant).subscribe({ + next: () => done.fail('Expected an error'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); + + describe('editTenant', () => { + it('should return updated tenant data', done => { + const tenant: Tenant = {id: 'tenant123', name: 'Updated Tenant'}; + const mockResponse: Tenant = tenant; + const commandSpy = jasmine.createSpyObj('EditTenantCommand', ['execute']); + commandSpy.execute.and.returnValue(of(mockResponse)); + + spyOn(service, 'editTenant').and.callFake(() => commandSpy.execute()); + + service.editTenant(tenant).subscribe(result => { + expect(result).toEqual(mockResponse); + done(); + }); + }); + }); + + describe('deleteTenant', () => { + it('should handle error when deleteTenant fails', done => { + const tenant: Tenant = {id: 'tenant123', name: 'Tenant to Delete'}; + const errorResponse = new Error('API error'); + const commandSpy = jasmine.createSpyObj('DeleteTenantCommand', [ + 'execute', + ]); + commandSpy.execute.and.returnValue(throwError(() => errorResponse)); + + spyOn(service, 'deleteTenant').and.callFake(() => commandSpy.execute()); + + service.deleteTenant(tenant).subscribe({ + next: () => done.fail('Expected an error'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); + }); +}); From 78a3db14e550203f08f9b9fa9eca455cf7d09d21 Mon Sep 17 00:00:00 2001 From: Deepika Mahindroo Date: Mon, 11 Nov 2024 12:35:45 +0530 Subject: [PATCH 3/3] feat(test cases): test cases on commands test cases on commands GH-67 --- .../add-features-for-plan.command.spec.ts | 34 +++++++++++ .../main/commands/add-tenant.command.spec.ts | 28 +++++++++ .../main/commands/delete-plan.command.spec.ts | 48 +++++++++++++++ .../commands/delete-tenant.command.spec.ts | 47 +++++++++++++++ .../main/commands/edit-tenant.command.spec.ts | 42 +++++++++++++ .../main/commands/get-plan.command.spec.ts | 60 +++++++++++++++++++ .../commands/get-tenant-by-id.command.spec.ts | 50 ++++++++++++++++ .../get-tenant-details.command.spec.ts | 45 ++++++++++++++ .../commands/get-tenant-lead.command.spec.ts | 48 +++++++++++++++ .../commands/get-total-lead.command.spec.ts | 45 ++++++++++++++ .../commands/get-total-tenant.command.spec.ts | 44 ++++++++++++++ 11 files changed, 491 insertions(+) create mode 100644 projects/saas-ui/src/app/main/commands/add-features-for-plan.command.spec.ts create mode 100644 projects/saas-ui/src/app/main/commands/add-tenant.command.spec.ts create mode 100644 projects/saas-ui/src/app/main/commands/delete-plan.command.spec.ts create mode 100644 projects/saas-ui/src/app/main/commands/delete-tenant.command.spec.ts create mode 100644 projects/saas-ui/src/app/main/commands/edit-tenant.command.spec.ts create mode 100644 projects/saas-ui/src/app/main/commands/get-plan.command.spec.ts create mode 100644 projects/saas-ui/src/app/main/commands/get-tenant-by-id.command.spec.ts create mode 100644 projects/saas-ui/src/app/main/commands/get-tenant-details.command.spec.ts create mode 100644 projects/saas-ui/src/app/main/commands/get-tenant-lead.command.spec.ts create mode 100644 projects/saas-ui/src/app/main/commands/get-total-lead.command.spec.ts create mode 100644 projects/saas-ui/src/app/main/commands/get-total-tenant.command.spec.ts diff --git a/projects/saas-ui/src/app/main/commands/add-features-for-plan.command.spec.ts b/projects/saas-ui/src/app/main/commands/add-features-for-plan.command.spec.ts new file mode 100644 index 00000000..8c6e7fff --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/add-features-for-plan.command.spec.ts @@ -0,0 +1,34 @@ +import {ApiService, IAdapter} from '@project-lib/core/api'; + +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {Feature} from '../../shared/interfaces/features'; +import {of, throwError} from 'rxjs'; +import {AddFeaturesForPlanCommand} from './add-features-for-plan.command'; + +describe('AddFeaturesForPlanCommand', () => { + let addFeaturesForPlanCommand: AddFeaturesForPlanCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: IAnyObject; + const planId = 'plan123'; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['post']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = { + baseApiUrl: 'https://api.example.com', + subscriptionServiceUrl: '/subscription', + }; + + addFeaturesForPlanCommand = new AddFeaturesForPlanCommand( + apiService, + adapter, + planId, + appConfig, + ); + }); + + it('should create an instance of AddFeaturesForPlanCommand', () => { + expect(addFeaturesForPlanCommand).toBeTruthy(); + }); +}); diff --git a/projects/saas-ui/src/app/main/commands/add-tenant.command.spec.ts b/projects/saas-ui/src/app/main/commands/add-tenant.command.spec.ts new file mode 100644 index 00000000..a2930c48 --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/add-tenant.command.spec.ts @@ -0,0 +1,28 @@ +import {ApiService, IAdapter, PostAPICommand} from '@project-lib/core/api'; +import {Tenant} from '../../shared/models'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {AddTenantCommand} from './add-tenant.command'; + +describe('AddTenantCommand', () => { + let addTenantCommand: AddTenantCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: jasmine.SpyObj; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['post']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = jasmine.createSpyObj('IAnyObject', [ + 'baseApiUrl', + 'tenantmgmtServiceUrl', + ]); + appConfig.baseApiUrl = 'https://api.example.com'; + appConfig.tenantmgmtServiceUrl = '/tenant-management'; + + addTenantCommand = new AddTenantCommand(apiService, adapter, appConfig); + }); + + it('should create an instance of AddTenantCommand', () => { + expect(addTenantCommand).toBeTruthy(); + }); +}); diff --git a/projects/saas-ui/src/app/main/commands/delete-plan.command.spec.ts b/projects/saas-ui/src/app/main/commands/delete-plan.command.spec.ts new file mode 100644 index 00000000..81841b16 --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/delete-plan.command.spec.ts @@ -0,0 +1,48 @@ +import {ApiService, DelAPICommand, IAdapter} from '@project-lib/core/api'; + +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {of, throwError} from 'rxjs'; +import {DeletePlanCommand} from './delete-plan.command'; + +describe('DeletePlanCommand', () => { + let deletePlanCommand: DeletePlanCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: jasmine.SpyObj; + const planId = '123'; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['delete']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = jasmine.createSpyObj('IAnyObject', [ + 'baseApiUrl', + 'subscriptionServiceUrl', + ]); + appConfig.baseApiUrl = 'https://api.example.com'; + appConfig.subscriptionServiceUrl = '/subscription'; + + deletePlanCommand = new DeletePlanCommand( + apiService, + adapter, + planId, + appConfig, + ); + }); + + it('should create an instance of DeletePlanCommand', () => { + expect(deletePlanCommand).toBeTruthy(); + }); + + it('should handle errors from the ApiService', done => { + const errorResponse = new Error('API error'); + apiService.delete.and.returnValue(throwError(() => errorResponse)); + + deletePlanCommand.execute().subscribe({ + next: () => done.fail('Expected an error, but got success'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); +}); diff --git a/projects/saas-ui/src/app/main/commands/delete-tenant.command.spec.ts b/projects/saas-ui/src/app/main/commands/delete-tenant.command.spec.ts new file mode 100644 index 00000000..534107c3 --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/delete-tenant.command.spec.ts @@ -0,0 +1,47 @@ +import {ApiService, IAdapter} from '@project-lib/core/api'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {of, throwError} from 'rxjs'; +import {DeleteTenantCommand} from './delete-tenant.command'; + +describe('DeleteTenantCommand', () => { + let deleteTenantCommand: DeleteTenantCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: jasmine.SpyObj; + const tenantId = 'tenant123'; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['delete']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = jasmine.createSpyObj('IAnyObject', [ + 'baseApiUrl', + 'tenantmgmtServiceUrl', + ]); + appConfig.baseApiUrl = 'https://api.example.com'; + appConfig.tenantmgmtServiceUrl = '/tenantmgmt'; + + deleteTenantCommand = new DeleteTenantCommand( + apiService, + adapter, + tenantId, + appConfig, + ); + }); + + it('should create an instance of DeleteTenantCommand', () => { + expect(deleteTenantCommand).toBeTruthy(); + }); + + it('should handle errors from the ApiService', done => { + const errorResponse = new Error('API error'); + apiService.delete.and.returnValue(throwError(() => errorResponse)); + + deleteTenantCommand.execute().subscribe({ + next: () => done.fail('Expected an error, but got success'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); +}); diff --git a/projects/saas-ui/src/app/main/commands/edit-tenant.command.spec.ts b/projects/saas-ui/src/app/main/commands/edit-tenant.command.spec.ts new file mode 100644 index 00000000..61f0062d --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/edit-tenant.command.spec.ts @@ -0,0 +1,42 @@ +import {ApiService, IAdapter} from '@project-lib/core/api'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {Tenant} from '../../shared/models/tenant.model'; +import {of, throwError} from 'rxjs'; +import {EditTenantCommand} from './edit-tenant.command'; + +describe('EditTenantCommand', () => { + let editTenantCommand: EditTenantCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: IAnyObject; + const tenantId = 'tenant123'; + const tenantData: Tenant = { + id: tenantId, + name: 'Tenant Test Name', + description: 'A sample description for the tenant', + // Include other necessary tenant fields here + }; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['patch']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = jasmine.createSpyObj('IAnyObject', [ + 'baseApiUrl', + 'tenantmgmtServiceUrl', + ]); + appConfig.baseApiUrl = 'https://api.example.com'; + appConfig.tenantmgmtServiceUrl = '/tenantmgmt'; + + editTenantCommand = new EditTenantCommand( + apiService, + adapter, + tenantId, + appConfig, + ); + editTenantCommand.data = tenantData; + }); + + it('should create an instance of EditTenantCommand', () => { + expect(editTenantCommand).toBeTruthy(); + }); +}); diff --git a/projects/saas-ui/src/app/main/commands/get-plan.command.spec.ts b/projects/saas-ui/src/app/main/commands/get-plan.command.spec.ts new file mode 100644 index 00000000..7defb3c3 --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/get-plan.command.spec.ts @@ -0,0 +1,60 @@ +import {IApiService, IAdapter} from '@project-lib/core/api'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {Plan} from '../../shared/models'; +import {of, throwError} from 'rxjs'; +import {GetPlanCommand} from './get-plan.command'; + +describe('GetPlanCommand', () => { + let getPlanCommand: GetPlanCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: IAnyObject; + const mockPlans = [ + { + id: 'plan1', + name: 'Basic Plan', + description: 'Basic plan description', + price: 10, + currency: 'USD', + cycle: 'monthly', + tier: 'basic', + }, + { + id: 'plan2', + name: 'Premium Plan', + description: 'Premium plan description', + price: 50, + currency: 'USD', + cycle: 'monthly', + tier: 'premium', + }, + ]; + + beforeEach(() => { + apiService = jasmine.createSpyObj('IApiService', ['get']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = { + baseApiUrl: 'https://api.example.com', + subscriptionServiceUrl: '/subscription', + }; + + getPlanCommand = new GetPlanCommand(apiService, adapter, appConfig); + }); + + it('should create an instance of GetPlanCommand', () => { + expect(getPlanCommand).toBeTruthy(); + }); + + it('should handle errors from the ApiService', done => { + const errorResponse = new Error('API error'); + apiService.get.and.returnValue(throwError(() => errorResponse)); + + getPlanCommand.execute().subscribe({ + next: () => done.fail('Expected an error, but got success'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); +}); diff --git a/projects/saas-ui/src/app/main/commands/get-tenant-by-id.command.spec.ts b/projects/saas-ui/src/app/main/commands/get-tenant-by-id.command.spec.ts new file mode 100644 index 00000000..8ddfbe4b --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/get-tenant-by-id.command.spec.ts @@ -0,0 +1,50 @@ +import {ApiService, IAdapter} from '@project-lib/core/api'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {of, throwError} from 'rxjs'; +import {GetTenantByIdCommand} from './get-tenant-by-id.command'; + +describe('GetTenantByIdCommand', () => { + let getTenantByIdCommand: GetTenantByIdCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: IAnyObject; + const tenantId = 'tenant123'; + const mockTenantData = { + id: tenantId, + name: 'Tenant Name', + address: '123 Main St', + }; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['get']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = { + baseApiUrl: 'https://api.example.com', + tenantmgmtServiceUrl: '/tenantmgmt', + }; + + getTenantByIdCommand = new GetTenantByIdCommand( + apiService, + adapter, + tenantId, + appConfig, + ); + }); + + it('should create an instance of GetTenantByIdCommand', () => { + expect(getTenantByIdCommand).toBeTruthy(); + }); + + it('should handle errors from the ApiService', done => { + const errorResponse = new Error('API error'); + apiService.get.and.returnValue(throwError(() => errorResponse)); + + getTenantByIdCommand.execute().subscribe({ + next: () => done.fail('Expected an error, but got success'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); +}); diff --git a/projects/saas-ui/src/app/main/commands/get-tenant-details.command.spec.ts b/projects/saas-ui/src/app/main/commands/get-tenant-details.command.spec.ts new file mode 100644 index 00000000..dd7a1121 --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/get-tenant-details.command.spec.ts @@ -0,0 +1,45 @@ +import {ApiService, IAdapter} from '@project-lib/core/api'; + +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {TenantDetails} from '../../shared/models/tenantDetails.model'; +import {of, throwError} from 'rxjs'; +import {GetTenantDetailsCommand} from './get-tenant-details.command'; + +describe('GetTenantDetailsCommand', () => { + let getTenantDetailsCommand: GetTenantDetailsCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: IAnyObject; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['get']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = { + baseApiUrl: 'https://api.example.com', + tenantMgmtFacadeUrl: '/tenantmgmt-facade', + }; + + getTenantDetailsCommand = new GetTenantDetailsCommand( + apiService, + adapter, + appConfig, + ); + }); + + it('should create an instance of GetTenantDetailsCommand', () => { + expect(getTenantDetailsCommand).toBeTruthy(); + }); + + it('should handle errors from the ApiService', done => { + const errorResponse = new Error('API error'); + apiService.get.and.returnValue(throwError(() => errorResponse)); + + getTenantDetailsCommand.execute().subscribe({ + next: () => done.fail('Expected an error, but got success'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); +}); diff --git a/projects/saas-ui/src/app/main/commands/get-tenant-lead.command.spec.ts b/projects/saas-ui/src/app/main/commands/get-tenant-lead.command.spec.ts new file mode 100644 index 00000000..16a612ed --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/get-tenant-lead.command.spec.ts @@ -0,0 +1,48 @@ +import {ApiService, IAdapter, GetListAPICommand} from '@project-lib/core/api'; + +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {of, throwError} from 'rxjs'; +import {GetTenantLeadListCommand} from './get-tenant-lead.command'; + +describe('GetTenantLeadListCommand', () => { + let getTenantLeadListCommand: GetTenantLeadListCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: IAnyObject; + const mockLeads = [ + {tenantId: 'tenant1', leadName: 'Lead 1'}, + {tenantId: 'tenant2', leadName: 'Lead 2'}, + ]; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['get']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = { + baseApiUrl: 'https://api.example.com', + tenantmgmtServiceUrl: '/tenantmgmt', + }; + + getTenantLeadListCommand = new GetTenantLeadListCommand( + apiService, + adapter, + appConfig, + ); + }); + + it('should create an instance of GetTenantLeadListCommand', () => { + expect(getTenantLeadListCommand).toBeTruthy(); + }); + + it('should handle errors from the ApiService', done => { + const errorResponse = new Error('API error'); + apiService.get.and.returnValue(throwError(() => errorResponse)); + + getTenantLeadListCommand.execute().subscribe({ + next: () => done.fail('Expected an error, but got success'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); +}); diff --git a/projects/saas-ui/src/app/main/commands/get-total-lead.command.spec.ts b/projects/saas-ui/src/app/main/commands/get-total-lead.command.spec.ts new file mode 100644 index 00000000..57e2ff80 --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/get-total-lead.command.spec.ts @@ -0,0 +1,45 @@ +import {ApiService, IAdapter, GetAPICommand} from '@project-lib/core/api'; + +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {of, throwError} from 'rxjs'; +import {GetTotalLeadCommand} from './get-total-lead.command'; + +describe('GetTotalLeadCommand', () => { + let getTotalLeadCommand: GetTotalLeadCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: IAnyObject; + const mockCountResponse = {count: 5}; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['get']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = { + baseApiUrl: 'https://api.example.com', + tenantmgmtServiceUrl: '/tenantmgmt', + }; + + getTotalLeadCommand = new GetTotalLeadCommand( + apiService, + adapter, + appConfig, + ); + }); + + it('should create an instance of GetTotalLeadCommand', () => { + expect(getTotalLeadCommand).toBeTruthy(); + }); + + it('should handle errors from the ApiService', done => { + const errorResponse = new Error('API error'); + apiService.get.and.returnValue(throwError(() => errorResponse)); + + getTotalLeadCommand.execute().subscribe({ + next: () => done.fail('Expected an error, but got success'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); +}); diff --git a/projects/saas-ui/src/app/main/commands/get-total-tenant.command.spec.ts b/projects/saas-ui/src/app/main/commands/get-total-tenant.command.spec.ts new file mode 100644 index 00000000..084cc0e9 --- /dev/null +++ b/projects/saas-ui/src/app/main/commands/get-total-tenant.command.spec.ts @@ -0,0 +1,44 @@ +import {ApiService, IAdapter, GetAPICommand} from '@project-lib/core/api'; +import {IAnyObject} from '@project-lib/core/i-any-object'; +import {of, throwError} from 'rxjs'; +import {GetTotalTenantCommand} from './get-total-tenant.command'; + +describe('GetTotalTenantCommand', () => { + let getTotalTenantCommand: GetTotalTenantCommand; + let apiService: jasmine.SpyObj; + let adapter: jasmine.SpyObj>; + let appConfig: IAnyObject; + const mockCountResponse = {count: 10}; + + beforeEach(() => { + apiService = jasmine.createSpyObj('ApiService', ['get']); + adapter = jasmine.createSpyObj('IAdapter', ['adapt']); + appConfig = { + baseApiUrl: 'https://api.example.com', + tenantmgmtServiceUrl: '/tenantmgmt', + }; + + getTotalTenantCommand = new GetTotalTenantCommand( + apiService, + adapter, + appConfig, + ); + }); + + it('should create an instance of GetTotalTenantCommand', () => { + expect(getTotalTenantCommand).toBeTruthy(); + }); + + it('should handle errors from the ApiService', done => { + const errorResponse = new Error('API error'); + apiService.get.and.returnValue(throwError(() => errorResponse)); + + getTotalTenantCommand.execute().subscribe({ + next: () => done.fail('Expected an error, but got success'), + error: error => { + expect(error).toBe(errorResponse); + done(); + }, + }); + }); +});