From 403a3d9e060dae905e0401cfcff4569e5433c838 Mon Sep 17 00:00:00 2001 From: Viswas Date: Thu, 26 Sep 2024 17:30:45 +0530 Subject: [PATCH 1/2] test: unit test intacct mapping component --- .../intacct-mapping.component.spec.ts | 68 ++++- .../integrations/intacct/intacct.fixture.ts | 287 +++++++++++++++++- 2 files changed, 346 insertions(+), 9 deletions(-) diff --git a/src/app/integrations/intacct/intacct-main/intacct-mapping/intacct-mapping.component.spec.ts b/src/app/integrations/intacct/intacct-main/intacct-mapping/intacct-mapping.component.spec.ts index dfd3466ea..59e95c770 100644 --- a/src/app/integrations/intacct/intacct-main/intacct-mapping/intacct-mapping.component.spec.ts +++ b/src/app/integrations/intacct/intacct-main/intacct-mapping/intacct-mapping.component.spec.ts @@ -1,23 +1,77 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { provideRouter, Router, RouterModule } from '@angular/router'; +import { of } from 'rxjs'; import { IntacctMappingComponent } from './intacct-mapping.component'; +import { SiMappingsService } from 'src/app/core/services/si/si-core/si-mappings.service'; +import { brandingConfig, brandingFeatureConfig } from 'src/app/branding/branding-config'; +import { mockMappingSettingsResponse, mockMappingSettingsWithCustomFieldResponse } from '../../intacct.fixture'; +import { MappingSettingResponse } from 'src/app/core/models/intacct/db/mapping-setting.model'; +import { SharedModule } from 'src/app/shared/shared.module'; -xdescribe('IntacctMappingComponent', () => { +describe('IntacctMappingComponent', () => { let component: IntacctMappingComponent; + let router: Router; let fixture: ComponentFixture; + let mappingServiceSpy: jasmine.SpyObj; beforeEach(async () => { + mappingServiceSpy = jasmine.createSpyObj('SiMappingsService', ['getMappingSettings']); + await TestBed.configureTestingModule({ - declarations: [ IntacctMappingComponent ] - }) - .compileComponents(); + imports: [SharedModule, RouterModule.forRoot([])], + declarations: [ IntacctMappingComponent ], + providers: [ + provideRouter([]), + { provide: SiMappingsService, useValue: mappingServiceSpy } + ] + }).compileComponents(); + + mappingServiceSpy.getMappingSettings.and.returnValue(of(mockMappingSettingsResponse as MappingSettingResponse)); + brandingConfig.brandId = 'fyle'; + brandingFeatureConfig.featureFlags.mapEmployees = true; + + router = TestBed.inject(Router); + spyOn(router, 'navigateByUrl'); fixture = TestBed.createComponent(IntacctMappingComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); -}); + + it('should initialize correctly', () => { + fixture.detectChanges(); + + expect(component.isLoading).toBeFalse(); + expect(component.mappingPages.length).toBe(3); + expect(component.activeModule).toEqual(component.mappingPages[0]); + expect(router.navigateByUrl).toHaveBeenCalledWith('/integrations/intacct/main/mapping/employee'); + }); + + it('should handle custom mapping fields', () => { + mappingServiceSpy.getMappingSettings.and.returnValue(of(mockMappingSettingsWithCustomFieldResponse as MappingSettingResponse)); + fixture.detectChanges(); + + expect(component.mappingPages.length).toBe(4); + expect(component.mappingPages[3].label).toBe('Sample Custom Field'); + expect(component.mappingPages[3].routerLink).toBe('/integrations/intacct/main/mapping/sample_custom_field'); + }); + + it('should not include employee mapping when the feature is disabled', () => { + brandingFeatureConfig.featureFlags.mapEmployees = false; + fixture.detectChanges(); + + expect(component.mappingPages.length).toBe(2); + expect(component.mappingPages[0].label).not.toBe('Employee'); + }); + + it('should handle different branding configurations', () => { + mappingServiceSpy.getMappingSettings.and.returnValue(of(mockMappingSettingsWithCustomFieldResponse as MappingSettingResponse)); + brandingConfig.brandId = 'co'; + fixture.detectChanges(); + + expect(component.mappingPages[3].label).toBe('Sample custom field'); + }); +}); \ No newline at end of file diff --git a/src/app/integrations/intacct/intacct.fixture.ts b/src/app/integrations/intacct/intacct.fixture.ts index 07a0d8d15..fb976e6bc 100644 --- a/src/app/integrations/intacct/intacct.fixture.ts +++ b/src/app/integrations/intacct/intacct.fixture.ts @@ -1,7 +1,11 @@ import { minimalUser } from "src/app/core/interceptor/jwt.fixture"; +import { AccountingExportSummary } from "src/app/core/models/db/accounting-export-summary.model"; +import { Error } from "src/app/core/models/db/error.model"; import { MinimalUser } from "src/app/core/models/db/user.model"; -import { IntacctOnboardingState } from "src/app/core/models/enum/enum.model"; +import { AccountingErrorType, CCCExpenseState, ExpenseState, ExportDateType, FyleField, IntacctCorporateCreditCardExpensesObject, IntacctOnboardingState, IntacctReimbursableExpensesObject, SplitExpenseGrouping, TaskLogState, TaskLogType } from "src/app/core/models/enum/enum.model"; import { IntacctWorkspace } from "src/app/core/models/intacct/db/workspaces.model"; +import { ExportSettingGet } from "src/app/core/models/intacct/intacct-configuration/export-settings.model"; + export const workspaceResponse: IntacctWorkspace[] = [{ "id": 1, @@ -41,4 +45,283 @@ export const testOnboardingState: {[k in IntacctOnboardingState]: string} = { [IntacctOnboardingState.IMPORT_SETTINGS]: '/integrations/intacct/onboarding/import_settings', [IntacctOnboardingState.ADVANCED_CONFIGURATION]: '/integrations/intacct/onboarding/advanced_settings', [IntacctOnboardingState.COMPLETE]: '/integrations/intacct/main/dashboard' -}; \ No newline at end of file +}; + +export const mockTasksInProgress = { + results: [ + { status: TaskLogState.COMPLETE, type: TaskLogType.CREATING_BILLS, expense_group: 1 }, + { status: TaskLogState.IN_PROGRESS, type: TaskLogType.CREATING_BILLS, expense_group: 2 } + ] +}; + + +export const mockCompletedTasksWithFailures = { + results: [ + { status: TaskLogState.COMPLETE, type: TaskLogType.CREATING_BILLS, expense_group: 1 }, + { status: TaskLogState.FAILED, type: TaskLogType.CREATING_BILLS, expense_group: 2 } + ] +}; + +export const mockExportSettingGet: ExportSettingGet = { + "configurations": { + "reimbursable_expenses_object": IntacctReimbursableExpensesObject.BILL, + "corporate_credit_card_expenses_object": IntacctCorporateCreditCardExpensesObject.EXPENSE_REPORT, + "auto_map_employees": "NAME", + "employee_field_mapping": "EMPLOYEE", + "use_merchant_in_journal_line": false + }, + "expense_group_settings": { + "reimbursable_expense_group_fields": [ + "claim_number", + "employee_email", + "fund_source", + "report_id" + ], + "reimbursable_export_date_type": ExportDateType.CURRENT_DATE, + "expense_state": ExpenseState.PAYMENT_PROCESSING, + "corporate_credit_card_expense_group_fields": [ + "fund_source", + "employee_email", + "expense_id", + "expense_number", + "spent_at" + ], + "ccc_export_date_type": ExportDateType.SPENT_AT, + "ccc_expense_state": CCCExpenseState.PAID, + "split_expense_grouping": SplitExpenseGrouping.MULTIPLE_LINE_ITEM + }, + "general_mappings": { + "default_gl_account": { + "id": null, + "name": null + }, + "default_credit_card": { + "id": null, + "name": null + }, + "default_charge_card": { + "id": null, + "name": null + }, + "default_reimbursable_expense_payment_type": { + "id": null, + "name": null + }, + "default_ccc_expense_payment_type": { + "id": "8", + "name": "Chase Bank" + }, + "default_ccc_vendor": { + "id": null, + "name": null + } + }, + "workspace_id": 309 +}; + + +export const mockExportDetails: AccountingExportSummary = { + "id": 160, + "last_exported_at": "2024-07-11T08:07:09.848044Z", + "next_export_at": "", + "export_mode": "MANUAL", + "created_at": "2023-09-29T11:55:35.676822Z", + "updated_at": "2024-07-11T08:07:22.395524Z", + "workspace": 309, + total_accounting_export_count: 0, + successful_accounting_export_count: 0, + failed_accounting_export_count: 0 +}; + +export const mockTasks = { + "count": 4, + "next": null, + "previous": null, + "results": [ + { + "id": 9287, + "type": "CREATING_CHARGE_CARD_TRANSACTIONS", + "task_id": null, + "supdoc_id": null, + "status": "COMPLETE", + "detail": { + "key": "225351", + "status": "success", + "url_id": "qXWaCK6f5_qV4aHm6u3PiIeh8ogPEuqB6MjqAbzQVGQ", + "function": "record_cctransaction", + "controlid": "58251c29-1e0f-4928-86d6-d6d1bbdb4edb" + }, + "sage_intacct_errors": null, + "created_at": "2024-07-11T08:07:09.864962Z", + "updated_at": "2024-07-11T08:07:21.798373Z", + "workspace": 309, + "expense_group": 7542, + "bill": null, + "expense_report": null, + "charge_card_transaction": 90843, + "journal_entry": null, + "ap_payment": null, + "sage_intacct_reimbursement": null + }, + { + "id": 9069, + "type": "CREATING_CHARGE_CARD_TRANSACTIONS", + "task_id": null, + "supdoc_id": null, + "status": "COMPLETE", + "detail": { + "key": "223630", + "status": "success", + "url_id": "qXWaCK6f5_qV4aHm6u3PiLvFSqblKp1NRLeOkn9AajA", + "function": "record_cctransaction", + "controlid": "bd160ada-5051-4d2c-9edb-3a78a27c4799" + }, + "sage_intacct_errors": null, + "created_at": "2024-05-22T13:30:24.795959Z", + "updated_at": "2024-05-23T08:04:27.992025Z", + "workspace": 309, + "expense_group": 7310, + "bill": null, + "expense_report": null, + "charge_card_transaction": 90705, + "journal_entry": null, + "ap_payment": null, + "sage_intacct_reimbursement": null + }, + { + "id": 9070, + "type": "CREATING_CHARGE_CARD_TRANSACTIONS", + "task_id": null, + "supdoc_id": "7341", + "status": "COMPLETE", + "detail": { + "key": "223610", + "status": "success", + "url_id": "qXWaCK6f5_qV4aHm6u3PiJU3ZRrpBmI3o1E1U_b8XPU", + "function": "record_cctransaction", + "controlid": "12e78bb4-9da9-4906-bb25-5ba38937ac98" + }, + "sage_intacct_errors": null, + "created_at": "2024-05-22T13:30:24.822472Z", + "updated_at": "2024-05-22T14:04:56.668057Z", + "workspace": 309, + "expense_group": 7341, + "bill": null, + "expense_report": null, + "charge_card_transaction": 90704, + "journal_entry": null, + "ap_payment": null, + "sage_intacct_reimbursement": null + }, + { + "id": 8153, + "type": "CREATING_EXPENSE_REPORTS", + "task_id": null, + "supdoc_id": null, + "status": "COMPLETE", + "detail": { + "key": "209973", + "url_id": "G-ux6pWcr8AhwLamSloWEE2iCi9ieG24GsfBYIQxAbA" + }, + "sage_intacct_errors": null, + "created_at": "2023-09-29T12:12:51.475099Z", + "updated_at": "2024-02-12T05:21:06.660047Z", + "workspace": 309, + "expense_group": 5999, + "bill": null, + "expense_report": 135126, + "charge_card_transaction": null, + "journal_entry": null, + "ap_payment": null, + "sage_intacct_reimbursement": null + } + ] +}; + +export const mockExportableAccountingExportIds = { + exportable_expense_group_ids: [1, 2, 3] +}; + +export const mockConfiguration = { + "id": 329, + "workspace": "Workspace object (309)", + "employee_field_mapping": "EMPLOYEE", + "reimbursable_expenses_object": "BILL", + "corporate_credit_card_expenses_object": "EXPENSE_REPORT", + "import_projects": false, + "import_categories": true, + "sync_fyle_to_sage_intacct_payments": false, + "sync_sage_intacct_to_fyle_payments": true, + "auto_map_employees": "NAME", + "import_tax_codes": false, + "memo_structure": [ + "employee_email", + "category", + "spent_on", + "report_number", + "purpose", + "expense_link" + ], + "auto_create_destination_entity": true, + "is_journal_credit_billable": true, + "is_simplify_report_closure_enabled": true, + "change_accounting_period": true, + "import_vendors_as_merchants": true, + "use_merchant_in_journal_line": false, + "auto_create_merchants_as_vendors": false, + "import_code_fields": [ + "_EXPENSE_TYPE", + "_ACCOUNT" + ], + "created_at": "2023-09-29T11:59:03.359631Z", + "updated_at": "2024-09-19T08:58:21.110832Z" +}; + +export const mockAccountingExportSummary = { + "id": 46, + "last_exported_at": "2024-08-28T17:11:21.098195Z", + "next_export_at": "2024-02-24T17:11:14.033111Z", + "export_mode": "MANUAL", + "total_expense_groups_count": 29, + "successful_expense_groups_count": 0, + "failed_expense_groups_count": 29, + "created_at": "2023-07-17T20:56:55.442251Z", + "updated_at": "2024-09-11T10:08:20.636603Z", + "workspace": 240 +}; + + +export const mockErrors = [ + { id: 1, type: AccountingErrorType.EMPLOYEE_MAPPING, error_title: 'Employee mapping error' }, + { id: 2, type: AccountingErrorType.CATEGORY_MAPPING, error_title: 'Category mapping error' }, + { id: 3, type: AccountingErrorType.ACCOUNTING_ERROR, error_title: 'Accounting error' } +] as Error[]; + +export const mockExportSettings = { + configurations: { + reimbursable_expenses_object: IntacctReimbursableExpensesObject.EXPENSE_REPORT, + corporate_credit_card_expenses_object: IntacctCorporateCreditCardExpensesObject.BILL + }, + expense_group_settings: { + expense_state: ExpenseState.PAYMENT_PROCESSING, + ccc_expense_state: ExpenseState.PAID + } +} as unknown as ExportSettingGet; + + +export const mockMappingSettingsResponse = { + results: [ + { source_field: FyleField.EMPLOYEE, destination_field: 'EMPLOYEE' }, + { source_field: FyleField.CATEGORY, destination_field: 'EXPENSE_TYPE' }, + { source_field: 'PROJECT', destination_field: 'LOCATION' } + ] +}; + +export const mockMappingSettingsWithCustomFieldResponse = { + results: [ + { source_field: FyleField.EMPLOYEE, destination_field: 'EMPLOYEE' }, + { source_field: FyleField.CATEGORY, destination_field: 'EXPENSE_TYPE' }, + { source_field: 'PROJECT', destination_field: 'LOCATION' }, + { source_field: 'SAMPLE_CUSTOM_FIELD', destination_field: 'PROJECT' } + ] +}; From e4fdd45df5c9996658fe789d3c6cbb282522b787 Mon Sep 17 00:00:00 2001 From: Viswas Date: Thu, 26 Sep 2024 20:54:39 +0530 Subject: [PATCH 2/2] test: unit test intacct mapping base component --- .../intacct-base-mapping.component.spec.ts | 127 ++++++++++++++++-- .../integrations/intacct/intacct.fixture.ts | 26 ++++ 2 files changed, 145 insertions(+), 8 deletions(-) diff --git a/src/app/integrations/intacct/intacct-main/intacct-mapping/intacct-base-mapping/intacct-base-mapping.component.spec.ts b/src/app/integrations/intacct/intacct-main/intacct-mapping/intacct-base-mapping/intacct-base-mapping.component.spec.ts index 32521a814..273d8bc5e 100644 --- a/src/app/integrations/intacct/intacct-main/intacct-mapping/intacct-base-mapping/intacct-base-mapping.component.spec.ts +++ b/src/app/integrations/intacct/intacct-main/intacct-mapping/intacct-base-mapping/intacct-base-mapping.component.spec.ts @@ -1,23 +1,134 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - +/* eslint-disable dot-notation */ +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { of, throwError } from 'rxjs'; import { IntacctBaseMappingComponent } from './intacct-base-mapping.component'; +import { MappingService } from 'src/app/core/services/common/mapping.service'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; +import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service'; +import { FyleField, IntacctCategoryDestination, IntacctCorporateCreditCardExpensesObject, IntacctReimbursableExpensesObject, ToastSeverity } from 'src/app/core/models/enum/enum.model'; +import { mockConfigurationResponse, mockMappingSettingsResponse, mockDestinationAttributesResponse } from '../../../intacct.fixture'; +import { MappingSettingResponse } from 'src/app/core/models/intacct/db/mapping-setting.model'; +import { DestinationAttribute, PaginatedDestinationAttribute } from 'src/app/core/models/db/destination-attribute.model'; +import { SharedModule } from 'src/app/shared/shared.module'; + -xdescribe('IntacctBaseMappingComponent', () => { +describe('IntacctBaseMappingComponent', () => { let component: IntacctBaseMappingComponent; let fixture: ComponentFixture; + let routeSpy: jasmine.SpyObj; + let mappingServiceSpy: jasmine.SpyObj; + let workspaceServiceSpy: jasmine.SpyObj; + let toastServiceSpy: jasmine.SpyObj; beforeEach(async () => { + routeSpy = jasmine.createSpyObj('ActivatedRoute', [], { + params: of({ source_field: 'employee' }), + snapshot: { params: { source_field: 'employee' } } + }); + mappingServiceSpy = jasmine.createSpyObj('MappingService', ['getMappingSettings', 'getPaginatedDestinationAttributes', 'triggerAutoMapEmployees']); + workspaceServiceSpy = jasmine.createSpyObj('WorkspaceService', ['getConfiguration']); + toastServiceSpy = jasmine.createSpyObj('IntegrationsToastService', ['displayToastMessage']); + await TestBed.configureTestingModule({ - declarations: [ IntacctBaseMappingComponent ] - }) - .compileComponents(); + declarations: [IntacctBaseMappingComponent], + providers: [ + { provide: ActivatedRoute, useValue: routeSpy }, + { provide: MappingService, useValue: mappingServiceSpy }, + { provide: WorkspaceService, useValue: workspaceServiceSpy }, + { provide: IntegrationsToastService, useValue: toastServiceSpy } + ] + }).compileComponents(); fixture = TestBed.createComponent(IntacctBaseMappingComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); -}); + + it('should initialize correctly', fakeAsync(() => { + workspaceServiceSpy.getConfiguration.and.returnValue(of(mockConfigurationResponse)); + mappingServiceSpy.getMappingSettings.and.returnValue(of(mockMappingSettingsResponse as MappingSettingResponse)); + mappingServiceSpy.getPaginatedDestinationAttributes.and.returnValue(of(mockDestinationAttributesResponse as PaginatedDestinationAttribute)); + + fixture.detectChanges(); + tick(); + + expect(component.isLoading).toBeFalse(); + expect(component.sourceField).toBe(FyleField.EMPLOYEE); + expect(component.destinationField).toBe('EMPLOYEE'); + expect(component.destinationOptions).toEqual(mockDestinationAttributesResponse.results as DestinationAttribute[]); + expect(component.showAutoMapEmployee).toBeTrue(); + })); + + it('should handle category mapping', fakeAsync(() => { + routeSpy.params = of({ source_field: 'category' }); + routeSpy.snapshot.params.source_field = 'category'; + + workspaceServiceSpy.getConfiguration.and.returnValue(of(mockConfigurationResponse)); + mappingServiceSpy.getMappingSettings.and.returnValue(of(mockMappingSettingsResponse as MappingSettingResponse)); + mappingServiceSpy.getPaginatedDestinationAttributes.and.returnValue(of(mockDestinationAttributesResponse as PaginatedDestinationAttribute)); + + fixture.detectChanges(); + tick(); + + expect(component.sourceField).toBe('CATEGORY'); + expect(component.destinationField).toBe(IntacctCategoryDestination.ACCOUNT); + })); + + it('should trigger auto map employees', () => { + mappingServiceSpy.triggerAutoMapEmployees.and.returnValue(of(null)); + component.triggerAutoMapEmployees(); + + expect(component.isLoading).toBeFalse(); + expect(toastServiceSpy.displayToastMessage).toHaveBeenCalledWith(ToastSeverity.INFO, 'Auto mapping of employees may take few minutes'); + }); + + it('should handle error when triggering auto map employees', () => { + mappingServiceSpy.triggerAutoMapEmployees.and.returnValue(throwError(() => new Error('Error'))); + component.triggerAutoMapEmployees(); + + expect(component.isLoading).toBeFalse(); + expect(toastServiceSpy.displayToastMessage).toHaveBeenCalledWith(ToastSeverity.ERROR, 'Something went wrong, please try again'); + }); + + describe('getDestinationField', () => { + it('should return employee field mapping when source field is EMPLOYEE', () => { + component.sourceField = FyleField.EMPLOYEE; + const intacctConfiguration = { employee_field_mapping: 'EMPLOYEE' } as any; + const mappingSettings = [] as any; + + const result = component['getDestinationField'](intacctConfiguration, mappingSettings); + expect(result).toBe('EMPLOYEE'); + }); + + it('should return EXPENSE_TYPE when source field is CATEGORY and reimbursable_expenses_object is EXPENSE_REPORT', () => { + component.sourceField = FyleField.CATEGORY; + const intacctConfiguration = { reimbursable_expenses_object: IntacctReimbursableExpensesObject.EXPENSE_REPORT } as any; + const mappingSettings = [] as any; + + const result = component['getDestinationField'](intacctConfiguration, mappingSettings); + expect(result).toBe(IntacctCategoryDestination.EXPENSE_TYPE); + }); + + it('should return EXPENSE_TYPE when source field is CATEGORY and corporate_credit_card_expenses_object is EXPENSE_REPORT', () => { + component.sourceField = FyleField.CATEGORY; + const intacctConfiguration = { corporate_credit_card_expenses_object: IntacctCorporateCreditCardExpensesObject.EXPENSE_REPORT } as any; + const mappingSettings = [] as any; + + const result = component['getDestinationField'](intacctConfiguration, mappingSettings); + expect(result).toBe(IntacctCategoryDestination.EXPENSE_TYPE); + }); + + it('should return ACCOUNT when source field is CATEGORY and neither expenses_object is EXPENSE_REPORT', () => { + component.sourceField = FyleField.CATEGORY; + const intacctConfiguration = { reimbursable_expenses_object: 'OTHER', corporate_credit_card_expenses_object: 'OTHER' } as any; + const mappingSettings = [] as any; + + const result = component['getDestinationField'](intacctConfiguration, mappingSettings); + expect(result).toBe(IntacctCategoryDestination.ACCOUNT); + }); + }); +}); \ No newline at end of file diff --git a/src/app/integrations/intacct/intacct.fixture.ts b/src/app/integrations/intacct/intacct.fixture.ts index fb976e6bc..165c738e9 100644 --- a/src/app/integrations/intacct/intacct.fixture.ts +++ b/src/app/integrations/intacct/intacct.fixture.ts @@ -325,3 +325,29 @@ export const mockMappingSettingsWithCustomFieldResponse = { { source_field: 'SAMPLE_CUSTOM_FIELD', destination_field: 'PROJECT' } ] }; + +export const mockConfigurationResponse = { + employee_field_mapping: 'EMPLOYEE', + reimbursable_expenses_object: null, + corporate_credit_card_expenses_object: 'JOURNAL_ENTRY', + auto_map_employees: 'EMPLOYEE_CODE' +}; + +export const mockDestinationAttributesResponse = { + results: [ + { + id: 216107, + attribute_type: 'EMPLOYEE', + display_name: 'employee', + value: 'Brian Foster', + destination_id: 'Brian Foster' + }, + { + id: 216116, + attribute_type: 'EMPLOYEE', + display_name: 'employee', + value: 'Chris Curtis', + destination_id: 'Chris Curtis' + } + ] +};