import { Injectable } from '@angular/core'; import { GCMockModule } from '@core/mocks/gc-module.mock'; import { AutomationAPI } from '@core/typings/api/automation.typing'; import { ProgramApplicantType } from '@core/typings/program.typing'; import { ViewTranslationsBlank } from '@core/typings/translation.typing'; import { FormDefinitionForUi } from '@features/configure-forms/form.typing'; import { AutoTableRepository, PaginationOptions } from '@yourcause/common'; import { AfterEach, BeforeEach, Spec, TestCase } from '@yourcause/test-decorators'; import { DescribeAngularService } from '@yourcause/test-decorators/angular'; import { expect, spy } from 'chai'; import { ProgramAutomationService } from './program-automation.service'; import { ProgramAutomationDetailFromApi, ProgramAutomationForApplicantApi, ProgramAutomationForm, ProgramAutomationRowForUi, ProgramAutomationRowFromApi, ProgramAutomationRuleset, ProgramAutomationTableAction } from './program-automation.typing'; @Injectable({ providedIn: 'root' }) @DescribeAngularService(ProgramAutomationService, { imports: [ GCMockModule ] }) export class ProgramAutomationServiceSpec implements Spec< ProgramAutomationServiceSpec, ProgramAutomationService > { routingFormId = 12; draftComplete: ProgramAutomationRowFromApi = { id: 1, name: 'Automation 1', formId: this.routingFormId, grantProgramRoutingRulesCount: 2, complete: true, draft: true, isArchived: false, landingLinkGuid: '124124adg' }; draftIncomplete: ProgramAutomationRowFromApi = { id: 2, name: 'Automation 2', formId: this.routingFormId, grantProgramRoutingRulesCount: 0, complete: false, draft: true, isArchived: false, landingLinkGuid: '124234d124adg' }; published: ProgramAutomationRowFromApi = { id: 3, name: 'Automation 3', formId: this.routingFormId, grantProgramRoutingRulesCount: 3, complete: true, draft: false, isArchived: false, landingLinkGuid: '124124dadf3adg' }; archived: ProgramAutomationRowFromApi = { id: 4, name: 'Automation 4', formId: this.routingFormId, grantProgramRoutingRulesCount: 3, complete: true, draft: false, isArchived: true, landingLinkGuid: '124124da33df3adg' }; programAutomationsFromApi: ProgramAutomationRowFromApi[] = [ this.draftComplete, this.draftIncomplete, this.published, this.archived ]; ruleset1: ProgramAutomationRuleset = { grantProgramRoutingAutomationRuleSetId: 54, rules: [], sequence: 1, routeToGrantProgramId: 64, applyRulesWithOr: false, createdDate: '', updatedDate: '', name: 'Route A', description: '', formName: '', formId: this.routingFormId }; ruleset2: ProgramAutomationRuleset = { grantProgramRoutingAutomationRuleSetId: 77, rules: [{ automationRuleSetExpressionId: 53, columnName: 'test_object.test_text', comparisonType: AutomationAPI.AutomationRuleComparisonTypes.Equals, comparisonValue: 'text_value', referenceFieldId: null }], sequence: 2, routeToGrantProgramId: 88, applyRulesWithOr: true, createdDate: '', updatedDate: '', name: 'Route B', description: '', formName: '', formId: this.routingFormId }; rulesets: ProgramAutomationRuleset[] = [ this.ruleset1, this.ruleset2 ]; detail: ProgramAutomationDetailFromApi = { id: 15, name: 'Routing Rule', description: '', formId: this.routingFormId, isArchived: false, draft: false, complete: true, landingLinkGuid: '23523ladjf', fallbackGrantProgramId: null, ruleSetFailureMessage: 'message', grantProgramRoutingAutomationRuleSets: this.rulesets, programApplicantType: ProgramApplicantType.ORGS }; result = { grantProgramName: '', grantProgramId: 1, grantProgramGuid: 'asdf', message: '', isFallbackProgram: false, noProgramToRouteTo: true }; routingInfo: ProgramAutomationForApplicantApi = { name: 'Ruleset A', description: '', ruleSetFailureMessage: '', fallbackGrantProgramGuid: '', isArchived: false, formId: this.routingFormId, formRevisionId: 235, clientLogoUrl: '', clientId: 1, clientName: '', formDefinition: [] }; @BeforeEach() mock (service: ProgramAutomationService) { service['translationService']['set']('viewTranslations', ViewTranslationsBlank); spy.on(service['programAutomationResources'], 'getProgramAutomationRecords', async () => { return { records: this.programAutomationsFromApi, recordCount: this.programAutomationsFromApi.length }; }); spy.on(service['programAutomationResources'], 'getProgramAutomationDetail', async () => { return this.detail; }); } @AfterEach() restoreSpy (service: ProgramAutomationService) { spy.restore(); service.resetProgramAutomationRules(); } @TestCase('should be able to get program automation detail - with id') async getProgramAutomationDetail (service: ProgramAutomationService) { await service.getProgramAutomationDetail(1); expect(service['programAutomationResources']['getProgramAutomationDetail']).to.have.been.called.once; } @TestCase('should be able to get program automation detail - without id') async getProgramAutomationDetailNoId (service: ProgramAutomationService) { spy.on(service, 'getBlankProgramAutomationDetail'); const detail = await service.getProgramAutomationDetail(undefined); expect(service['programAutomationResources']['getProgramAutomationRecords']).to.not.been.called; const hasBlankRecord = !detail.id && detail.grantProgramRoutingAutomationRuleSets.length === 0; expect(hasBlankRecord).to.be.true; } @TestCase('should be able to get automation rules') async getAutomationRules (service: ProgramAutomationService) { await service.getAutomationRules({} as PaginationOptions); expect(service['programAutomationResources']['getProgramAutomationRecords']).to.have.been.called.once; } @TestCase('should be able to get all automation rules') async getAllAutomationRules (service: ProgramAutomationService) { await service.getAllAutomationRules(); expect(service['programAutomationResources']['getProgramAutomationRecords']).to.have.been.called.once; } @TestCase('should be able to set all program automation rules') async setAllProgramAutomationRules (service: ProgramAutomationService) { service['set']('programAutomationRules', undefined); spy.on(service, 'setProgramAutomationRules'); await service.setAllProgramAutomationRules(); expect(service['programAutomationResources']['getProgramAutomationRecords']).to.have.been.called.once; expect(service['setProgramAutomationRules']).to.have.been.called.once; } @TestCase('should be able to reset program automation rules') async resetProgramAutomationRules (service: ProgramAutomationService) { service['set']('programAutomationRules', undefined); // Make sure rules are set await service.setAllProgramAutomationRules(); // Then reset service.resetProgramAutomationRules(); const resetRules = !service.programAutomationRules; const resetOptions = !service.allProgramAutomationOptions && !service.activeProgramAutomationOptions; expect(resetRules).to.be.true; expect(resetOptions).to.be.true; } @TestCase('should be able to reset program automation table repo') resetProgramAutomationTableRepo (service: ProgramAutomationService) { let calledReset = false; const repo = { reset: () => { calledReset = true; } } as AutoTableRepository; spy.on(service['autoTableRepositoryFactory'], 'getRepository', () => { return repo; }); service.resetProgramAutomationTableRepo(); expect(calledReset).to.be.true; } @TestCase('should be able to save program automation - success') async saveProgramAutomation (service: ProgramAutomationService) { spy.on(service['programAutomationResources'], 'saveProgramAutomation', async () => { return { grantProgramRoutingDetailsId: 1235, grantProgramRoutingLinkGuid: '' }; }); await service.saveProgramAutomation(this.detail); expect(service['programAutomationResources']['saveProgramAutomation']).to.have.been.called.once; } @TestCase('should be able to save program automation - error') async saveProgramAutomationError (service: ProgramAutomationService) { spy.on(service['programAutomationResources'], 'saveProgramAutomation', async () => { /* eslint-disable no-throw-literal */ throw { error: { message: 'There was an error' } }; }); const result = await service.saveProgramAutomation(this.detail); expect(!result).to.be.true; } @TestCase('should be able to publish automation from modal') async publishAutomationFromModal (service: ProgramAutomationService) { spy.on(service['confirmAndTakeActionService'], 'takeAction', async () => { return { passed: true, endpointResponse: null }; }); await service.publishAutomationFromModal(3); expect(service['confirmAndTakeActionService']['takeAction']).to.have.been.called.once; } @TestCase('should be able to sequence rules') async sequenceRules (service: ProgramAutomationService) { spy.on(service['confirmAndTakeActionService'], 'takeAction', async () => { return { passed: true, endpointResponse: null }; }); await service.handleSequenceRulesets({ ruleSets: [], grantProgramRoutingDetailsId: 53 }); expect(service['confirmAndTakeActionService']['takeAction']).to.have.been.called.once; } @TestCase('should be able to update automation record - publish') async updateAutomationRecordPublish (service: ProgramAutomationService) { await service.setAllProgramAutomationRules(); const draftComplete = service.programAutomationRules.find((rule) => { return rule.id === this.draftComplete.id; }); service.updateAutomationRecord(draftComplete, ProgramAutomationTableAction.PUBLISH); const updatedRecord = service.programAutomationRules.find((rule) => { return rule.id === this.draftComplete.id; }); expect(updatedRecord.draft).to.be.false; } @TestCase('should be able to update automation record - archive') async updateAutomationRecordArchive (service: ProgramAutomationService) { await service.setAllProgramAutomationRules(); const publishedRecord = service.programAutomationRules.find((rule) => { return rule.id === this.published.id; }); service.updateAutomationRecord(publishedRecord, ProgramAutomationTableAction.ARCHIVE); const updatedRecord = service.programAutomationRules.find((rule) => { return rule.id === this.published.id; }); expect(updatedRecord.isArchived).to.be.true; } @TestCase('should be able to update automation record - archive') async updateAutomationRecordDelete (service: ProgramAutomationService) { await service.setAllProgramAutomationRules(); const recordToDelete = service.programAutomationRules.find((rule) => { return rule.id === this.published.id; }); service.updateAutomationRecord(recordToDelete, ProgramAutomationTableAction.DELETE); const hasRecord = service.programAutomationRules.some((rule) => { return rule.id === this.published.id; }); expect(hasRecord).to.be.false; } @TestCase('should be able to handle table action - publish') async handleTableActionPublish (service: ProgramAutomationService) { await service.setAllProgramAutomationRules(); spy.on(service, 'handlePublish', async () => { return true; }); await service.handleTableAction(service.programAutomationRules[0], ProgramAutomationTableAction.PUBLISH); expect(service['handlePublish']).to.have.been.called.once; } @TestCase('should be able to handle table action - archive') async handleTableActionArchive (service: ProgramAutomationService) { await service.setAllProgramAutomationRules(); spy.on(service, 'handleArchive', async () => { return true; }); await service.handleTableAction(service.programAutomationRules[0], ProgramAutomationTableAction.ARCHIVE); expect(service['handleArchive']).to.have.been.called.once; } @TestCase('should be able to handle table action - delete') async handleTableActionDelete (service: ProgramAutomationService) { await service.setAllProgramAutomationRules(); spy.on(service, 'handleDelete', async () => { return true; }); await service.handleTableAction(service.programAutomationRules[0], ProgramAutomationTableAction.DELETE); expect(service['handleDelete']).to.have.been.called.once; } @TestCase('should be able to handle publish') async handlePublish (service: ProgramAutomationService) { await service.setAllProgramAutomationRules(); spy.on(service['confirmAndTakeActionService'], 'confirmAndTakeAction', async () => { return { passed: true, endpointResponse: null }; }); await service.handlePublish(service.programAutomationRules[0]); expect(service['confirmAndTakeActionService']['confirmAndTakeAction']).to.have.been.called.once; } @TestCase('should be able to handle archive') async handleArchive (service: ProgramAutomationService) { await service.setAllProgramAutomationRules(); spy.on(service['confirmAndTakeActionService'], 'confirmAndTakeAction', async () => { return { passed: true, endpointResponse: null }; }); await service.handleArchive(service.programAutomationRules[0]); expect(service['confirmAndTakeActionService']['confirmAndTakeAction']).to.have.been.called.once; } @TestCase('should be able to handle delete') async handleDelete (service: ProgramAutomationService) { await service.setAllProgramAutomationRules(); spy.on(service['confirmAndTakeActionService'], 'confirmAndTakeAction', async () => { return { passed: true, endpointResponse: null }; }); await service.handleDelete(service.programAutomationRules[0]); expect(service['confirmAndTakeActionService']['confirmAndTakeAction']).to.have.been.called.once; } @TestCase('should be able to handle save ruleset - pass') async handleSaveRuleset (service: ProgramAutomationService) { spy.on(service['programAutomationResources'], 'saveRuleset', async () => { return this.ruleset1; }); await service.handleSaveRuleset( 1, 2, 3, 4, 'Name', false, [] ); expect(service['programAutomationResources']['saveRuleset']).to.have.been.called.once; } @TestCase('should be able to handle save ruleset - error') async handleSaveRulesetFail (service: ProgramAutomationService) { spy.on(service['programAutomationResources'], 'saveRuleset', async () => { /* eslint-disable no-throw-literal */ throw { error: { message: 'There was an error' } }; }); const result = await service.handleSaveRuleset( 1, 2, 3, 4, 'Name', false, [] ); expect(!result).to.be.true; } @TestCase('should be able to delete ruleset') async deleteRuleset (service: ProgramAutomationService) { spy.on(service['confirmAndTakeActionService'], 'takeAction', async () => { return { passed: true, endpointResponse: null }; }); await service.handleDeleteRuleset(1, 3); expect(service['confirmAndTakeActionService']['takeAction']).to.have.been.called.once; } @TestCase('should be able to set new route clicked') setNewRouteClicked (service: ProgramAutomationService) { service.setNewRouteClicked(true); expect(service.newRouteClicked).to.be.true; service.setNewRouteClicked(false); expect(service.newRouteClicked).to.be.false; } @TestCase('should be able to set evaluation order clicked') setEvaluationOrderClicked (service: ProgramAutomationService) { service.setEvaluationOrderClicked(true); expect(service.evaluationOrderClicked).to.be.true; service.setEvaluationOrderClicked(false); expect(service.evaluationOrderClicked).to.be.false; } @TestCase('should be able to get allow application rules - true') allowApplicationRules (service: ProgramAutomationService) { const formDef = [{ components: [{ type: 'amountRequested' }, { type: 'designation' }] } as FormDefinitionForUi]; const allow = service.allowApplicationRules(formDef); expect(allow).to.be.true; } @TestCase('should be able to get allow application rules - false') allowApplicationRulesFalse (service: ProgramAutomationService) { const formDef = [{ components: [{ type: 'referenceFields-otherField' }, { type: 'designation' }] } as FormDefinitionForUi]; const allow = service.allowApplicationRules(formDef); expect(allow).to.be.false; } @TestCase('should be able to get routing info for applicant') async getRoutingInfoForApplicant (service: ProgramAutomationService) { spy.on(service['programAutomationResources'], 'getRoutingInfoForApplicant', async () => { return this.routingInfo; }); spy.on(service['formLogicService'], 'adaptFormDefinitionForTabs'); await service.getRoutingInfoForApplicant('guid'); expect(service['formLogicService']['adaptFormDefinitionForTabs']).to.have.been.called.once; expect(service['programAutomationResources']['getRoutingInfoForApplicant']).to.have.been.called.once; } @TestCase('should be able to submit program routing') async submitProgramRouting (service: ProgramAutomationService) { spy.on(service['programAutomationResources'], 'submitProgramRouting', async () => { return this.result; }); await service.submitProgramRouting(1, '', {} as ProgramAutomationForm); expect(service['programAutomationResources']['submitProgramRouting']).to.have.been.called.once; } @TestCase('should be able to submit program routing - error') async submitProgramRoutingFail (service: ProgramAutomationService) { spy.on(service['programAutomationResources'], 'submitProgramRouting', async () => { /* eslint-disable no-throw-literal */ throw { error: { message: 'There was an error' } }; }); spy.on(service['logger'], 'error', () => {}); spy.on(service['notifier'], 'success', () => {}); spy.on(service['notifier'], 'error', () => {}); const result = await service.submitProgramRouting(1, '', {} as ProgramAutomationForm); expect(!result).to.be.true; expect(service['notifier']['success']).to.not.have.been.called.once; expect(service['logger']['error']).to.have.been.called.once; expect(service['notifier']['error']).to.have.been.called.once; } @TestCase('should be able to show no programs available') async showNoProgramsAvailable (service: ProgramAutomationService) { spy.on(service['modalFactory'], 'open', async () => { return true; }); await service.showNoProgramsAvailable(this.result); expect(service['modalFactory']['open']).to.have.been.called.once; } }