diff --git a/angular/projects/common/src/app/legislation/legislation-list-detail-public/legislation-list-detail.component.spec.ts b/angular/projects/common/src/app/legislation/legislation-list-detail-public/legislation-list-detail.component.spec.ts index aa84732a5..2fa142739 100644 --- a/angular/projects/common/src/app/legislation/legislation-list-detail-public/legislation-list-detail.component.spec.ts +++ b/angular/projects/common/src/app/legislation/legislation-list-detail-public/legislation-list-detail.component.spec.ts @@ -1,9 +1,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { LegislationListDetailComponent } from './legislation-list-detail.component'; import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRouteStub } from '../../spec/spec-utils'; import { GlobalModule } from 'nrpti-angular-components'; +import { Legislation } from '../../models/master/common-models/legislation'; describe('LegislationListDetailComponent', () => { let component: LegislationListDetailComponent; @@ -14,7 +15,7 @@ describe('LegislationListDetailComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [GlobalModule], + imports: [GlobalModule, HttpClientTestingModule], declarations: [LegislationListDetailComponent], providers: [ { provide: ActivatedRoute, useValue: activedRouteStub }, @@ -32,4 +33,16 @@ describe('LegislationListDetailComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should display an actCode when the backend cannot return the legislation code map', async () => { + const testActCode = 'ACT_103'; + const testLegislation = new Legislation({ act: testActCode }); + + // populates the component with legislation data, which will then be parsed by buildLegislationString() and added as a grid item + component.data = [testLegislation]; + fixture.detectChanges(); + const gridEl = fixture.nativeElement.querySelector('.grid-item-value'); + + expect(gridEl.textContent).toContain(testActCode); + }); }); diff --git a/angular/projects/public-nrpti/src/app/services/acts.service.spec.ts b/angular/projects/public-nrpti/src/app/services/acts.service.spec.ts new file mode 100644 index 000000000..005b7c5a7 --- /dev/null +++ b/angular/projects/public-nrpti/src/app/services/acts.service.spec.ts @@ -0,0 +1,18 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ActService } from './acts.service'; +import { ConfigService } from 'nrpti-angular-components'; + +describe('ApiService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ActService, ConfigService], + imports: [HttpClientTestingModule] + }); + }); + + it('should be created', () => { + const service = TestBed.get(ActService); + expect(service).toBeTruthy(); + }); +}); diff --git a/angular/projects/public-nrpti/src/app/services/acts.service.ts b/angular/projects/public-nrpti/src/app/services/acts.service.ts index 1461dc889..b7958c263 100644 --- a/angular/projects/public-nrpti/src/app/services/acts.service.ts +++ b/angular/projects/public-nrpti/src/app/services/acts.service.ts @@ -2,7 +2,6 @@ * @description This service provides methods to fetch and store issuing agencies. * @class ApplicationAgencyService */ - import { Injectable } from '@angular/core'; import { ConfigService } from 'nrpti-angular-components'; import { Observable } from 'rxjs'; @@ -10,7 +9,7 @@ import { HttpClient } from '@angular/common/http'; /** * @class - * @description Service for managing issuing agencies. + * @description Service for serving up-to-date map of actCode to actName and regulations. */ @Injectable({ providedIn: 'root' @@ -27,7 +26,7 @@ export class ActService { constructor(private configService: ConfigService, public http: HttpClient) {} /** - * Initialize the service by setting the API endpoint and refreshing agencies. + * Initialize the service by setting the API endpoint and refreshing acts. * @async */ async init() { @@ -35,8 +34,8 @@ export class ActService { await this.refreshAct().toPromise(); } /** - * Refresh the list of agencies from the API. - * @returns {Observable} An observable that completes when agencies are refreshed. + * Refresh the map of actCodes from the API. + * @returns {Observable} An observable that completes when acts are refreshed. */ refreshAct(): Observable { return new Observable(observer => { @@ -58,8 +57,8 @@ export class ActService { }); } /** - * Get the list of agencies. - * @returns {Object} A dictionary of agency codes and names. + * Get the list of acts and regualtions mapped to an actCode. + * @returns {Object} A dictionary of act codes, names, and regulations. */ getAllActsAndRegulations() { return this.actsRegulationsData; diff --git a/api/src/controllers/acts-regulations-controller.js b/api/src/controllers/acts-regulations-controller.js index d1e2a76a1..37d10d5bd 100644 --- a/api/src/controllers/acts-regulations-controller.js +++ b/api/src/controllers/acts-regulations-controller.js @@ -70,7 +70,7 @@ exports.updateActTitles = async function(args, res, next){ let actTitle = ''; for (const [actCode, {actAPI}] of Object.entries(LEGISLATION_CODES)){ const response = await axios.get(actAPI); - actTitle = parseTitleFromXML(response.data); + actTitle = this.parseTitleFromXML(response.data); actMap[actCode] = actTitle; } updateTitlesInDB(actMap); @@ -91,6 +91,7 @@ exports.getActTitleFromDB = async function(actCode){ return (actTitleFromDB); } catch (error) { console.error("getActTitleFromDB: Failed to fetch data from DB:", error); + return actCode; } } @@ -119,7 +120,7 @@ exports.getAllActsAndRegulationsFromDB = async function(){ * @param {string} responseXML xml as a string from the BCLaws API * @return {string} the title of the act */ -function parseTitleFromXML(responseXML){ +exports.parseTitleFromXML = function(responseXML){ let actTitle = ''; let startIndex = 0; try{ @@ -127,11 +128,14 @@ function parseTitleFromXML(responseXML){ const titleEnd = ''; const titleStartIndex = responseXML.indexOf(titleStart, startIndex); const titleEndIndex = responseXML.indexOf(titleEnd, titleStart); - actTitle = responseXML.substring(titleStartIndex + titleStart.length, titleEndIndex); + + if(titleStartIndex >= 0 && titleEndIndex >= 0){ //indexOf() returns -1 if no matches are found + actTitle = responseXML.substring(titleStartIndex + titleStart.length, titleEndIndex); + } else throw new Error('act:title not found in XML'); + } catch (error) { console.error("parseTitleFromXML: Failed to parse XML:", error); return null; } return actTitle; } - diff --git a/api/src/tests/controllers/acts-regulations-controller.test.js b/api/src/tests/controllers/acts-regulations-controller.test.js new file mode 100644 index 000000000..597f249b9 --- /dev/null +++ b/api/src/tests/controllers/acts-regulations-controller.test.js @@ -0,0 +1,17 @@ +const actsRegulationsController = require('../../controllers/acts-regulations-controller'); + +describe('parseTitleFromXML', () => { + const testTitle = 'Act Name'; + const testXMLWithActName = '' + testTitle + ''; + const testXMLWithoutActName = ''; + + it('returns null if act:title field is not found', () => { + const result = actsRegulationsController.parseTitleFromXML(testXMLWithoutActName); + expect(result).toEqual(null); + }); + + it('returns Act title if act:title field is present', () => { + const result = actsRegulationsController.parseTitleFromXML(testXMLWithActName); + expect(result).toEqual(testTitle); + }); +});