From c3a6b9c67f1da4b3e148514db1b15e77250cc603 Mon Sep 17 00:00:00 2001 From: Morgan Ludtke <42942267+ludtkemorgan@users.noreply.github.com> Date: Fri, 26 Jan 2024 09:53:51 -0600 Subject: [PATCH] fix: geocoding verification fix (#3832) * fix: geocoding verification fix * fix: e2e test fix --- .../services/geocoding.service.spec.ts | 212 ++++++++++++++---- .../services/geocoding.service.ts | 106 +++++---- .../applications/applications.e2e-spec.ts | 8 +- 3 files changed, 239 insertions(+), 87 deletions(-) diff --git a/backend/core/src/applications/services/geocoding.service.spec.ts b/backend/core/src/applications/services/geocoding.service.spec.ts index 5a21a65d8d..b89ee9b6f3 100644 --- a/backend/core/src/applications/services/geocoding.service.spec.ts +++ b/backend/core/src/applications/services/geocoding.service.spec.ts @@ -8,6 +8,7 @@ import { Listing } from "../../listings/entities/listing.entity" import { InputType } from "../../shared/types/input-type" import { MapLayer } from "../../map-layers/entities/map-layer.entity" import { FeatureCollection } from "@turf/helpers" +import { ApplicationMultiselectQuestion } from "../entities/application-multiselect-question.entity" describe("GeocodingService", () => { let service: GeocodingService @@ -175,7 +176,7 @@ describe("GeocodingService", () => { multiselectQuestion: { options: [ { - text: "Geocoding option by radius", + text: "Geocoding option by Radius", collectAddress: true, radiusSize: 5, validationMethod: ValidationMethod.radius, @@ -186,54 +187,46 @@ describe("GeocodingService", () => { ], } const preferenceAddress = { ...address, latitude: 38.89485, longitude: -77.04251 } - const application = { - id: "applicationId", - preferences: [ + const preferences = [ + { + key: "Geocoding preference", + options: [ + { + key: "Geocoding option by Radius", + checked: true, + extraData: [ + { + type: InputType.address, + value: preferenceAddress, + }, + ], + }, + ], + }, + ] + it("should save the validated value as extraData", () => { + const response = service.validateRadiusPreferences( + (preferences as unknown) as ApplicationMultiselectQuestion[], + listing as Listing + ) + expect(response).toEqual([ { key: "Geocoding preference", options: [ { - key: "Geocoding option by radius", + key: "Geocoding option by Radius", checked: true, extraData: [ { type: InputType.address, value: preferenceAddress, }, + { key: "geocodingVerified", type: InputType.text, value: "true" }, ], }, ], }, - ], - } - it("should save the validated value as extraData", async () => { - await service.validateRadiusPreferences( - (application as unknown) as Application, - listing as Listing - ) - expect(applicationRepoUpdate).toBeCalledWith( - { id: "applicationId" }, - { - preferences: expect.arrayContaining([ - expect.objectContaining({ - key: "Geocoding preference", - options: [ - { - checked: true, - extraData: [ - { - type: "address", - value: preferenceAddress, - }, - { key: "geocodingVerified", type: "text", value: "true" }, - ], - key: "Geocoding option by radius", - }, - ], - }), - ]), - } - ) + ]) }) }) describe("validateGeoLayerPreferences", () => { @@ -255,9 +248,28 @@ describe("GeocodingService", () => { ], } const preferenceAddress = { ...address, latitude: 38.89485, longitude: -77.04251 } - const application = { - id: "applicationId", - preferences: [ + + const preference = { + key: "Geocoding preference", + options: [ + { + key: "Geocoding option by map", + checked: true, + extraData: [ + { + type: InputType.address, + value: preferenceAddress, + }, + ], + }, + ], + } + it("should save the validated value as extraData for map layer", async () => { + const response = await service.validateGeoLayerPreferences( + ([preference] as unknown) as ApplicationMultiselectQuestion[], + listing as Listing + ) + expect(response).toEqual([ { key: "Geocoding preference", options: [ @@ -269,23 +281,114 @@ describe("GeocodingService", () => { type: InputType.address, value: preferenceAddress, }, + { key: "geocodingVerified", type: InputType.text, value: "true" }, ], }, ], }, + ]) + }) + }) + describe("validateGeocodingPreferences", () => { + const listing = { + buildingAddress: address, + listingMultiselectQuestions: [ + { + multiselectQuestion: { + options: [ + { + text: "Geocoding option by radius", + collectAddress: true, + radiusSize: 5, + validationMethod: ValidationMethod.radius, + }, + ], + }, + }, + { + multiselectQuestion: { + options: [ + { + text: "Geocoding option by map", + collectAddress: true, + mapLayerId: "mapLayerId", + validationMethod: ValidationMethod.map, + }, + ], + }, + }, + { + multiselectQuestion: { + options: [ + { + text: "non-geocoding option", + }, + ], + }, + }, ], } - it("should save the validated value as extraData for map layer", async () => { - await service.validateGeoLayerPreferences( + + const preferenceAddress = { ...address, latitude: 38.89485, longitude: -77.04251 } + const preferences = [ + { + key: "Geocoding preference by map", + options: [ + { + key: "Geocoding option by map", + checked: true, + extraData: [ + { + type: InputType.address, + value: preferenceAddress, + }, + ], + }, + ], + }, + { + key: "Geocoding preference by radius", + options: [ + { + key: "Geocoding option by radius", + checked: true, + extraData: [ + { + type: InputType.address, + value: preferenceAddress, + }, + ], + }, + ], + }, + { + key: "non-geocoding preference", + options: [ + { + key: "non-geocoding option", + checked: true, + }, + ], + }, + ] + + const application = { + id: "applicationId", + preferences: preferences, + } + + it("should save all updated preferences", async () => { + await service.validateGeocodingPreferences( (application as unknown) as Application, - listing as Listing + (listing as unknown) as Listing ) + expect(applicationRepoUpdate).toBeCalledWith( { id: "applicationId" }, { preferences: expect.arrayContaining([ expect.objectContaining({ - key: "Geocoding preference", + key: "Geocoding preference by map", options: [ { checked: true, @@ -300,6 +403,31 @@ describe("GeocodingService", () => { }, ], }), + expect.objectContaining({ + key: "Geocoding preference by radius", + options: [ + { + checked: true, + extraData: [ + { + type: "address", + value: preferenceAddress, + }, + { key: "geocodingVerified", type: "text", value: "true" }, + ], + key: "Geocoding option by radius", + }, + ], + }), + expect.objectContaining({ + key: "non-geocoding preference", + options: [ + { + checked: true, + key: "non-geocoding option", + }, + ], + }), ]), } ) diff --git a/backend/core/src/applications/services/geocoding.service.ts b/backend/core/src/applications/services/geocoding.service.ts index 651827fa35..841dafc709 100644 --- a/backend/core/src/applications/services/geocoding.service.ts +++ b/backend/core/src/applications/services/geocoding.service.ts @@ -22,8 +22,11 @@ export class GeocodingService { ) {} public async validateGeocodingPreferences(application: Application, listing: Listing) { - await this.validateRadiusPreferences(application, listing) - await this.validateGeoLayerPreferences(application, listing) + let preferences = application.preferences + preferences = this.validateRadiusPreferences(preferences, listing) + preferences = await this.validateGeoLayerPreferences(preferences, listing) + + await this.applicationRepository.update({ id: application.id }, { preferences: preferences }) } verifyRadius( @@ -89,7 +92,18 @@ export class GeocodingService { return GeocodingValues.unknown } - public async validateRadiusPreferences(application: Application, listing: Listing) { + /** + * Checks if there are any preferences that have a validation method of radius, validates those preferences addresses, + * and then adds the appropriate validation check field to those preferences + * + * @param preferences + * @param listing + * @returns the preferences with the geocoding verified field added to preferences that have validation method of radius + */ + public validateRadiusPreferences( + preferences: ApplicationMultiselectQuestion[], + listing: Listing + ): ApplicationMultiselectQuestion[] { // Get all radius preferences from the listing const radiusPreferenceOptions: MultiselectOption[] = listing.listingMultiselectQuestions.reduce( (options, multiselectQuestion) => { @@ -102,45 +116,55 @@ export class GeocodingService { ) // If there are any radius preferences do the calculation and save the new preferences if (radiusPreferenceOptions.length) { - const preferences: ApplicationMultiselectQuestion[] = application.preferences.map( - (preference) => { - const newPreferenceOptions: ApplicationMultiselectQuestionOption[] = preference.options.map( - (option) => { - const addressData = option.extraData.find((data) => data.type === InputType.address) - if (option.checked && addressData) { - const foundOption = radiusPreferenceOptions.find( - (preferenceOption) => preferenceOption.text === option.key + const newPreferences: ApplicationMultiselectQuestion[] = preferences.map((preference) => { + const newPreferenceOptions: ApplicationMultiselectQuestionOption[] = preference.options.map( + (option) => { + const addressData = option.extraData?.find((data) => data.type === InputType.address) + if (option.checked && addressData) { + const foundOption = radiusPreferenceOptions.find( + (preferenceOption) => preferenceOption.text === option.key + ) + if (foundOption) { + const geocodingVerified = this.verifyRadius( + addressData.value as Address, + foundOption.radiusSize, + listing.buildingAddress ) - if (foundOption) { - const geocodingVerified = this.verifyRadius( - addressData.value as Address, - foundOption.radiusSize, - listing.buildingAddress - ) - return { - ...option, - extraData: [ - ...option.extraData, - { - key: "geocodingVerified", - type: InputType.text, - value: geocodingVerified, - }, - ], - } + return { + ...option, + extraData: [ + ...option.extraData, + { + key: "geocodingVerified", + type: InputType.text, + value: geocodingVerified, + }, + ], } } - return option } - ) - return { ...preference, options: newPreferenceOptions } - } - ) - await this.applicationRepository.update({ id: application.id }, { preferences: preferences }) + return option + } + ) + return { ...preference, options: newPreferenceOptions } + }) + return newPreferences } + return preferences } - public async validateGeoLayerPreferences(application: Application, listing: Listing) { + /** + * Checks if there are any preferences that have a validation method of 'map', validates those preferences addresses, + * and then adds the appropriate validation check field to those preferences + * + * @param preferences + * @param listing + * @returns all preferences on the application + */ + public async validateGeoLayerPreferences( + preferences: ApplicationMultiselectQuestion[], + listing: Listing + ): Promise { // Get all map layer preferences from the listing const mapPreferenceOptions: MultiselectOption[] = listing.listingMultiselectQuestions?.reduce( (options, multiselectQuestion) => { @@ -158,7 +182,7 @@ export class GeocodingService { ): ApplicationMultiselectQuestionOption[] => { const preferenceOptions = [] preference.options.forEach((option) => { - const addressData = option.extraData.find((data) => data.type === InputType.address) + const addressData = option.extraData?.find((data) => data.type === InputType.address) if (option.checked && addressData) { const foundOption = mapPreferenceOptions.find( (preferenceOption) => preferenceOption.text === option.key @@ -188,16 +212,16 @@ export class GeocodingService { return preferenceOptions } if (mapPreferenceOptions?.length) { - const preferences = [] + const newPreferences = [] const mapLayers = await this.mapLayerRepository.findBy({ id: In(mapPreferenceOptions.map((option) => option.mapLayerId)), }) - application.preferences.forEach((preference) => { + preferences.forEach((preference) => { const newPreferenceOptions = preferencesOptions(preference, mapLayers) - preferences.push({ ...preference, options: newPreferenceOptions }) + newPreferences.push({ ...preference, options: newPreferenceOptions }) }) - - await this.applicationRepository.update({ id: application.id }, { preferences: preferences }) + return newPreferences } + return preferences } } diff --git a/backend/core/test/applications/applications.e2e-spec.ts b/backend/core/test/applications/applications.e2e-spec.ts index 6047b81870..32883556d1 100644 --- a/backend/core/test/applications/applications.e2e-spec.ts +++ b/backend/core/test/applications/applications.e2e-spec.ts @@ -362,7 +362,7 @@ describe("Applications", () => { expect(Array.isArray(res.body.items)).toBe(true) expect(res.body.items.length).toBe(1) expect(res.body.items[0].id === createRes.body.id) - expect(res.body.items[0]).toMatchObject(createRes.body) + expect(res.body.items[0]).toMatchObject({ ...createRes.body, updatedAt: expect.anything() }) }) it(`should not allow an admin to search for users application using a search query param of less than 3 characters`, async () => { @@ -402,7 +402,7 @@ describe("Applications", () => { expect(Array.isArray(res.body.items)).toBe(true) expect(res.body.items.length).toBe(1) expect(res.body.items[0].id === createRes.body.id) - expect(res.body.items[0]).toMatchObject(createRes.body) + expect(res.body.items[0]).toMatchObject({ ...createRes.body, updatedAt: expect.anything() }) }) it(`should allow an admin to search for users application using email as textquery`, async () => { @@ -425,7 +425,7 @@ describe("Applications", () => { expect(Array.isArray(res.body.items)).toBe(true) expect(res.body.items.length).toBe(1) expect(res.body.items[0].id === createRes.body.id) - expect(res.body.items[0]).toMatchObject(createRes.body) + expect(res.body.items[0]).toMatchObject({ ...createRes.body, updatedAt: expect.anything() }) }) // because we changed this to be done async to the request this is causing some problems with the tests @@ -568,7 +568,7 @@ describe("Applications", () => { const { createRes, firstName } = responses[index] expect(item.id === createRes.body.id) - expect(item).toMatchObject(createRes.body) + expect(item).toMatchObject({ ...createRes.body, updatedAt: expect.anything() }) expect(item.applicant).toMatchObject(createRes.body.applicant) expect(item.applicant.firstName === firstName) }