Skip to content

Commit c757aaf

Browse files
test: intacct dashboard (#981)
* test: export functionality and polling * updated tests * Revert "updated tests" This reverts commit 4f66cac. * test: highlight the error and possible fixes * fixture * added missing mocks * test: test the initialization logic of intacct dashboard * refactor: readability --------- Co-authored-by: anishfyle <[email protected]>
1 parent 9aa2b08 commit c757aaf

File tree

3 files changed

+150
-20
lines changed

3 files changed

+150
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,148 @@
1-
import { ComponentFixture, TestBed } from '@angular/core/testing';
2-
1+
import { ComponentFixture, discardPeriodicTasks, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
32
import { IntacctDashboardComponent } from './intacct-dashboard.component';
43
import { HttpClientTestingModule } from '@angular/common/http/testing';
4+
import { AccountingExportService } from 'src/app/core/services/common/accounting-export.service';
5+
import { DashboardService } from 'src/app/core/services/common/dashboard.service';
6+
import { ExportLogService } from 'src/app/core/services/si/export-log/export-log.service';
7+
import { WorkspaceService } from 'src/app/core/services/common/workspace.service';
8+
import { UserService } from 'src/app/core/services/misc/user.service';
9+
import { SiExportSettingService } from 'src/app/core/services/si/si-configuration/si-export-setting.service';
10+
import { MinimalUser } from 'src/app/core/models/db/user.model';
11+
import { of } from 'rxjs';
12+
import { AccountingExportSummary, AccountingExportSummaryModel } from 'src/app/core/models/db/accounting-export-summary.model';
13+
import { mockAccountingExportSummary, mockCompletedTasksWithFailures, mockConfiguration, mockErrors, mockExportableAccountingExportIds, mockExportSettingGet, mockExportSettings, mockTasksInProgress } from '../../intacct.fixture';
14+
import { SharedModule } from 'src/app/shared/shared.module';
15+
import { Error } from 'src/app/core/models/db/error.model';
16+
import { AccountingErrorType, AppName, CCCImportState, IntacctCategoryDestination, ReimbursableImportState, TaskLogState } from 'src/app/core/models/enum/enum.model';
17+
18+
describe('IntacctDashboardComponent', () => {
519

6-
xdescribe('DashboardComponent', () => {
720
let component: IntacctDashboardComponent;
821
let fixture: ComponentFixture<IntacctDashboardComponent>;
22+
let dashboardServiceSpy: jasmine.SpyObj<DashboardService>;
23+
let accountingExportServiceSpy: jasmine.SpyObj<AccountingExportService>;
24+
let userServiceSpy: jasmine.SpyObj<UserService>;
25+
let workspaceServiceSpy: jasmine.SpyObj<WorkspaceService>;
26+
let intacctExportSettingServiceSpy: jasmine.SpyObj<SiExportSettingService>;
27+
let exportLogServiceSpy: jasmine.SpyObj<ExportLogService>;
928

1029
beforeEach(async () => {
11-
const localStorageDump = {
12-
13-
org_id: '2'
14-
};
15-
localStorage.setItem('user', JSON.stringify(localStorageDump));
30+
const dashboardServiceSpyObj = jasmine.createSpyObj('DashboardService', ['getExportErrors', 'triggerAccountingExport', 'getAllTasks', 'getExportableAccountingExportIds']);
31+
const accountingExportServiceSpyObj = jasmine.createSpyObj('AccountingExportService', ['getAccountingExportSummary', 'importExpensesFromFyle']);
32+
const userServiceSpyObj = jasmine.createSpyObj('UserService', ['getUserProfile']);
33+
const workspaceServiceSpyObj = jasmine.createSpyObj('WorkspaceService', ['getConfiguration', 'getWorkspaceId', 'setOnboardingState']);
34+
const intacctExportSettingServiceSpyObj = jasmine.createSpyObj('SiExportSettingService', ['getExportSettings']);
35+
const exportLogServiceSpyObj = jasmine.createSpyObj('ExportLogService', ['getExportLogs']);
36+
1637
await TestBed.configureTestingModule({
17-
imports: [HttpClientTestingModule],
18-
declarations: [ IntacctDashboardComponent ]
19-
})
20-
.compileComponents();
38+
imports: [HttpClientTestingModule, SharedModule],
39+
declarations: [IntacctDashboardComponent],
40+
providers: [
41+
{ provide: DashboardService, useValue: dashboardServiceSpyObj },
42+
{ provide: AccountingExportService, useValue: accountingExportServiceSpyObj },
43+
{ provide: UserService, useValue: userServiceSpyObj },
44+
{ provide: WorkspaceService, useValue: workspaceServiceSpyObj },
45+
{ provide: SiExportSettingService, useValue: intacctExportSettingServiceSpyObj },
46+
{ provide: ExportLogService, useValue: exportLogServiceSpyObj }
47+
]
48+
}).compileComponents();
49+
50+
dashboardServiceSpy = TestBed.inject(DashboardService) as jasmine.SpyObj<DashboardService>;
51+
accountingExportServiceSpy = TestBed.inject(AccountingExportService) as jasmine.SpyObj<AccountingExportService>;
52+
userServiceSpy = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
53+
workspaceServiceSpy = TestBed.inject(WorkspaceService) as jasmine.SpyObj<WorkspaceService>;
54+
intacctExportSettingServiceSpy = TestBed.inject(SiExportSettingService) as jasmine.SpyObj<SiExportSettingService>;
55+
exportLogServiceSpy = TestBed.inject(ExportLogService) as jasmine.SpyObj<ExportLogService>;
56+
57+
userServiceSpy.getUserProfile.and.returnValue({ full_name: 'John Doe' } as MinimalUser);
58+
dashboardServiceSpy.getExportErrors.and.returnValue(of([]));
59+
accountingExportServiceSpy.getAccountingExportSummary.and.returnValue(of(mockAccountingExportSummary as unknown as AccountingExportSummary));
60+
dashboardServiceSpy.getExportableAccountingExportIds.and.returnValue(of(mockExportableAccountingExportIds));
61+
dashboardServiceSpy.getAllTasks.and.returnValue(of(mockCompletedTasksWithFailures));
62+
workspaceServiceSpy.getConfiguration.and.returnValue(of(mockConfiguration));
63+
intacctExportSettingServiceSpy.getExportSettings.and.returnValue(of(mockExportSettingGet));
64+
dashboardServiceSpy.triggerAccountingExport.and.returnValue(of({}));
65+
accountingExportServiceSpy.importExpensesFromFyle.and.returnValue(of({}));
66+
2167

2268
fixture = TestBed.createComponent(IntacctDashboardComponent);
2369
component = fixture.componentInstance;
24-
fixture.detectChanges();
2570
});
2671

2772
it('should create', () => {
2873
expect(component).toBeTruthy();
2974
});
30-
});
75+
76+
77+
it('should initialize correctly', fakeAsync(() => {
78+
component.getExportErrors$ = of(mockErrors as Error[]);
79+
spyOn(AccountingExportSummaryModel, 'parseAPIResponseToAccountingSummary');
80+
81+
tick();
82+
fixture.detectChanges();
83+
84+
expect(component.isLoading).toBeFalse();
85+
expect(component.errors).toEqual({
86+
[AccountingErrorType.EMPLOYEE_MAPPING]: [mockErrors[0]],
87+
[AccountingErrorType.CATEGORY_MAPPING]: [mockErrors[1]],
88+
[AccountingErrorType.ACCOUNTING_ERROR]: [mockErrors[2]]
89+
});
90+
91+
expect(component.reimbursableImportState).toEqual(ReimbursableImportState.PROCESSING);
92+
expect(component.cccImportState).toEqual(CCCImportState.PAID);
93+
94+
expect(AccountingExportSummaryModel.parseAPIResponseToAccountingSummary).toHaveBeenCalledOnceWith(mockAccountingExportSummary);
95+
expect(component.destinationFieldMap).toEqual({
96+
"EMPLOYEE": "EMPLOYEE",
97+
"CATEGORY": IntacctCategoryDestination.EXPENSE_TYPE
98+
});
99+
expect(component.exportableAccountingExportIds).toEqual([1, 2, 3]);
100+
expect(component.failedExpenseGroupCount).toBe(1);
101+
expect(component.isImportInProgress).toBeFalse();
102+
}));
103+
104+
it('should initialize correctly when an export is in progress', fakeAsync(() => {
105+
dashboardServiceSpy.getAllTasks.and.returnValue(of(mockTasksInProgress));
106+
spyOn<any>(component, 'pollExportStatus');
107+
108+
tick();
109+
fixture.detectChanges();
110+
111+
expect(component.isImportInProgress).toBeFalse();
112+
expect(component.isExportInProgress).toBeTrue();
113+
114+
// eslint-disable-next-line dot-notation
115+
expect(component['pollExportStatus']).toHaveBeenCalledOnceWith([1, 2, 3]);
116+
}));
117+
118+
it('should handle export correctly', fakeAsync(() => {
119+
dashboardServiceSpy.getAllTasks.and.returnValue(of(mockTasksInProgress));
120+
component.exportableAccountingExportIds = [1, 2];
121+
122+
component.export();
123+
tick(3000);
124+
125+
expect(component.isExportInProgress).toBeTrue();
126+
expect(component.processedCount).toBe(1);
127+
expect(component.exportProgressPercentage).toBe(50);
128+
129+
// Simulate export completion
130+
dashboardServiceSpy.getAllTasks.and.returnValue(of(mockCompletedTasksWithFailures));
131+
dashboardServiceSpy.getExportErrors.and.returnValue(of([]));
132+
133+
tick(3000);
134+
135+
expect(component.isExportInProgress).toBeFalse();
136+
expect(component.failedExpenseGroupCount).toBe(1);
137+
138+
// The failed expense group should be marked as exportable again
139+
expect(component.exportableAccountingExportIds).toEqual([2]);
140+
expect(component.exportProgressPercentage).toBe(0);
141+
expect(component.processedCount).toBe(0);
142+
143+
fixture.detectChanges();
144+
flush();
145+
146+
discardPeriodicTasks();
147+
}));
148+
});

src/app/integrations/intacct/intacct-main/intacct-dashboard/intacct-dashboard.component.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,21 @@ export class IntacctDashboardComponent implements OnInit {
129129
interval(3000).pipe(
130130
switchMap(() => from(this.dashboardService.getAllTasks([], exportableAccountingExportIds, this.accountingExportType, AppName.INTACCT))),
131131
takeWhile((response: IntacctTaskResponse) =>
132-
response.results.filter(task =>
133-
(task.status === TaskLogState.IN_PROGRESS || task.status === TaskLogState.ENQUEUED)
134-
).length > 0, true)
132+
response.results.filter(task =>
133+
(task.status === TaskLogState.IN_PROGRESS || task.status === TaskLogState.ENQUEUED)
134+
).length > 0, true
135+
)
135136
).subscribe((res: IntacctTaskResponse) => {
136-
this.processedCount = res.results.filter((task: { status: string; type: TaskLogType; expense_group: number; }) => (task.status !== 'IN_PROGRESS' && task.status !== 'ENQUEUED') && (task.type !== TaskLogType.FETCHING_EXPENSES && task.type !== TaskLogType.CREATING_AP_PAYMENT && task.type !== TaskLogType.CREATING_REIMBURSEMENT) && exportableAccountingExportIds.includes(task.expense_group)).length;
137+
this.processedCount = res.results.filter(
138+
(task: { status: string; type: TaskLogType; expense_group: number; }) =>
139+
(task.status !== 'IN_PROGRESS' && task.status !== 'ENQUEUED') &&
140+
(
141+
task.type !== TaskLogType.FETCHING_EXPENSES &&
142+
task.type !== TaskLogType.CREATING_AP_PAYMENT &&
143+
task.type !== TaskLogType.CREATING_REIMBURSEMENT
144+
) &&
145+
exportableAccountingExportIds.includes(task.expense_group)
146+
).length;
137147
this.exportProgressPercentage = Math.round((this.processedCount / exportableAccountingExportIds.length) * 100);
138148

139149
if (res.results.filter(task => (task.status === TaskLogState.IN_PROGRESS || task.status === TaskLogState.ENQUEUED)).length === 0) {
@@ -187,7 +197,9 @@ export class IntacctDashboardComponent implements OnInit {
187197

188198
this.isLoading = false;
189199

190-
const queuedTasks: IntacctTaskLog[] = responses[2].results.filter((task: IntacctTaskLog) => task.status === TaskLogState.ENQUEUED || task.status === TaskLogState.IN_PROGRESS);
200+
const queuedTasks: IntacctTaskLog[] = responses[2].results.filter(
201+
(task: IntacctTaskLog) => task.status === TaskLogState.ENQUEUED || task.status === TaskLogState.IN_PROGRESS
202+
);
191203
this.failedExpenseGroupCount = responses[2].results.filter((task: IntacctTaskLog) => task.status === TaskLogState.FAILED || task.status === TaskLogState.FATAL).length;
192204

193205
this.exportableAccountingExportIds = responses[4].exportable_expense_group_ids;

src/app/integrations/intacct/intacct-onboarding/intacct-onboarding.component.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
22

33
import { IntacctOnboardingComponent } from './intacct-onboarding.component';
44

5-
xdescribe('OnboardingComponent', () => {
5+
describe('IntacctOnboardingComponent', () => {
66
let component: IntacctOnboardingComponent;
77
let fixture: ComponentFixture<IntacctOnboardingComponent>;
88

0 commit comments

Comments
 (0)