Skip to content

Commit

Permalink
Optional hierarchy constraints are ignored if specified but fail to r…
Browse files Browse the repository at this point in the history
…esolve (#92)

1.1.4
  • Loading branch information
kennsippell authored Mar 15, 2024
1 parent fc9c928 commit d5ee2c0
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 10 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cht-user-management",
"version": "1.1.3",
"version": "1.1.4",
"main": "dist/index.js",
"dependencies": {
"@fastify/autoload": "^5.8.0",
Expand Down
5 changes: 4 additions & 1 deletion src/lib/remote-place-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export default class RemotePlaceResolver {
) : Promise<void> => {
const topDownHierarchy = Config.getHierarchyWithReplacement(place.type, 'desc');
for (const hierarchyLevel of topDownHierarchy) {
// #91 - for editing: forget previous resolution
delete place.resolvedHierarchy[hierarchyLevel.level];

if (!place.hierarchyProperties[hierarchyLevel.property_name]) {
continue;
}
Expand Down Expand Up @@ -178,7 +181,7 @@ function pickFromMapOptimistic(map: RemotePlaceMap, placeName: string, fuzzFunct
const fuzzyName = fuzzFunction(placeName);
const fuzzyResult = map[fuzzyName.toLowerCase()];
const [optimisticResult] = [result, fuzzyResult].filter(r => r && r.type !== 'invalid');
return optimisticResult || result || fuzzyResult;
return optimisticResult || result || fuzzyResult || RemotePlaceResolver.NoResult;
}

function findLocalPlaces(
Expand Down
6 changes: 5 additions & 1 deletion src/lib/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ export class Validation {
if (hierarchyLevel.level !== 0 || data) {
const isExpected = hierarchyLevel.required;
const resolution = place.resolvedHierarchy[hierarchyLevel.level];
const isValid = !isExpected || resolution?.type === 'remote' || resolution?.type === 'local';
const isValid = resolution?.type !== 'invalid' && (
!isExpected ||
resolution?.type === 'remote' ||
resolution?.type === 'local'
);
if (!isValid) {
const levelUp = hierarchy[index + 1]?.property_name;
result.push({
Expand Down
15 changes: 15 additions & 0 deletions test/lib/validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect } from 'chai';

import { Validation } from '../../src/lib/validation';
import { mockSimpleContactType, mockPlace } from '../mocks';
import RemotePlaceResolver from '../../src/lib/remote-place-resolver';

type Scenario = {
type: string;
Expand Down Expand Up @@ -97,6 +98,20 @@ describe('lib/validation.ts', () => {
expect(Validation.getValidationErrors(place)).to.be.empty;
});

it('#91 - parent is invalid when required:false but resolution is NoResult', () => {
const contactType = mockSimpleContactType('string', undefined);
contactType.hierarchy[0].required = false;

const place = mockPlace(contactType, 'prop');
place.resolvedHierarchy[1] = RemotePlaceResolver.NoResult;

console.log('Validation.getValidationErrors(place)', Validation.getValidationErrors(place));
expect(Validation.getValidationErrors(place)).to.deep.eq([{
property_name: 'hierarchy_PARENT',
description: `Cannot find 'parent' matching 'parent'`,
}]);
});

it('parent is invalid when missing but expected', () => {
const contactType = mockSimpleContactType('string', undefined);
const place = mockPlace(contactType, 'prop');
Expand Down
56 changes: 51 additions & 5 deletions test/services/place-factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import fs from 'fs';
import { expect } from 'chai';
import sinon from 'sinon';

import PlaceFactory from '../../src/services/place-factory';
import SessionCache from '../../src/services/session-cache';
import { RemotePlace } from '../../src/lib/cht-api';
import { expectInvalidProperties, mockChtSession, mockParentPlace, mockProperty, mockValidContactType } from '../mocks';
import Place from '../../src/services/place';
import { Config } from '../../src/config';
import Place from '../../src/services/place';
import PlaceFactory from '../../src/services/place-factory';
import { RemotePlace } from '../../src/lib/cht-api';
import RemotePlaceCache from '../../src/lib/remote-place-cache';
import RemotePlaceResolver from '../../src/lib/remote-place-resolver';
import SessionCache from '../../src/services/session-cache';

describe('services/place-factory.ts', () => {
beforeEach(() => {
Expand Down Expand Up @@ -236,6 +237,51 @@ describe('services/place-factory.ts', () => {
expect(place.resolvedHierarchy).to.deep.eq([undefined, remotePlace, grandParent]);
});

it('#91 - no result for optional level in hierarchy causes validation error', async () => {
const { remotePlace, sessionCache, contactType, fakeFormData, chtApi } = mockScenario();

const grandParent: RemotePlace = {
id: 'id-grandparent',
name: 'grand-parent',
type: 'remote',
lineage: [],
};
remotePlace.lineage = [grandParent.id];
fakeFormData.hierarchy_GRANDPARENT = 'no match';

chtApi.getPlacesWithType
.resolves([grandParent])
.onSecondCall().resolves([remotePlace]);

const place: Place = await PlaceFactory.createOne(fakeFormData, contactType, sessionCache, chtApi);
expect(place.resolvedHierarchy[2]).to.eq(RemotePlaceResolver.NoResult);
expectInvalidProperties(place.validationErrors, ['hierarchy_PARENT', 'hierarchy_GRANDPARENT'], 'Cannot find');
});

it('hierarchy resolution can be resolved by editing to blank', async () => {
const { remotePlace, sessionCache, contactType, fakeFormData, chtApi } = mockScenario();

const grandParent: RemotePlace = {
id: 'id-grandparent',
name: 'grand-parent',
type: 'remote',
lineage: [],
};
remotePlace.lineage = [grandParent.id];
fakeFormData.hierarchy_GRANDPARENT = 'no match';

chtApi.getPlacesWithType
.resolves([grandParent])
.onSecondCall().resolves([remotePlace]);

const place: Place = await PlaceFactory.createOne(fakeFormData, contactType, sessionCache, chtApi);
expectInvalidProperties(place.validationErrors, ['hierarchy_PARENT', 'hierarchy_GRANDPARENT'], 'Cannot find');

fakeFormData.hierarchy_GRANDPARENT = '';
const edited = await PlaceFactory.editOne(place.id, fakeFormData, sessionCache, chtApi);
expect(edited.validationErrors).to.be.empty;
});

it('ambiguous parent disambiguated by greatgrandparent', async () => {
const { remotePlace, sessionCache, contactType, fakeFormData, chtApi } = mockScenario();

Expand Down Expand Up @@ -314,7 +360,7 @@ describe('services/place-factory.ts', () => {
const place: Place = await PlaceFactory.createOne(fakeFormData, contactType, sessionCache, chtApi);
expectInvalidProperties(place.validationErrors, ['hierarchy_replacement'], 'Cannot find');
expect(place.resolvedHierarchy[1]?.id).to.eq('parent-id');
expect(place.resolvedHierarchy[0]).to.be.undefined;
expect(place.resolvedHierarchy[0]).to.eq(RemotePlaceResolver.NoResult);
});

it('place not under users facility is invalid', async () => {
Expand Down

0 comments on commit d5ee2c0

Please sign in to comment.