Skip to content

Commit

Permalink
fix(#8674): assign parent place to new contacts (#8682)
Browse files Browse the repository at this point in the history
Contacts created via the places api don't get a parent place assigned which causes an error: The contact must be a child of the place, when trying to create a user for the new contact. This PR fixes that.

#8674
  • Loading branch information
freddieptf authored Dec 13, 2023
1 parent 43a1683 commit 6dec634
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ describe('extract-person-contacts migration', () => {
name: 'The Clinic',
contact: {
name: 'Clinic Contact',
phone: '555 1000'
phone: '555 1000',
type: 'person'
},
parent: {
_id: 'hc-id',
Expand Down Expand Up @@ -145,7 +146,8 @@ describe('extract-person-contacts migration', () => {
name: 'The Health Center',
contact: {
name: 'HC Contact',
phone: '555 2000'
phone: '555 2000',
type: 'person'
},
parent: {
_id: 'dh-id',
Expand Down Expand Up @@ -194,7 +196,8 @@ describe('extract-person-contacts migration', () => {
parent: {},
contact: {
name: 'DH Contact',
phone: '555 3000'
phone: '555 3000',
type: 'person'
}
};
const districtHospitalContact = {
Expand Down
1 change: 1 addition & 0 deletions shared-libs/contacts/src/people.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,4 @@ module.exports.isAPerson = isAPerson;

module.exports._getPerson = getPerson;
module.exports._validatePerson = validatePerson;
module.exports._getDefaultPersonType = getDefaultPersonType;
50 changes: 32 additions & 18 deletions shared-libs/contacts/src/places.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const getPlace = id => {
})
.catch(err => {
if (err.status === 404) {
err.message = 'Failed to find place.';
err.message = 'Failed to find place.';
}
throw err;
});
Expand Down Expand Up @@ -75,6 +75,12 @@ const validatePlace = place => {
if (!_.isString(place.contact) && !_.isObject(place.contact)) {
return err(`Property "contact" on place ${placeId} must be an object or string.`);
}
if (_.isObject(place.contact)) {
const errStr = people._validatePerson(place.contact);
if (errStr) {
return err(errStr);
}
}
}
if (place.parent && !_.isEmpty(place.parent)) {
// validate parents
Expand All @@ -83,23 +89,31 @@ const validatePlace = place => {
return Promise.resolve();
};

const createPlace = place => {
const self = module.exports;
return self._validatePlace(place)
.then(() => {
const date = place.reported_date ? utils.parseDate(place.reported_date) : new Date();
place.reported_date = date.valueOf();
if (place.parent) {
place.parent = lineage.minifyLineage(place.parent);
}
if (place.contact) {
// also validates contact if creating
return people.getOrCreatePerson(place.contact).then(person => {
place.contact = lineage.minifyLineage(person);
});
}
})
.then(() => db.medic.post(place));
const createPlace = async (place) => {
if (place.contact && !place.contact.type) {
place.contact.type = people._getDefaultPersonType();
}
await module.exports._validatePlace(place);

const contact = place.contact;
delete place.contact;

const date = place.reported_date ? utils.parseDate(place.reported_date) : new Date();
place.reported_date = date.valueOf();
if (place.parent) {
place.parent = lineage.minifyLineage(place.parent);
}

const response = await db.medic.post(place);
if (!contact) {
return response;
}
const placeUUID = response.id;

contact.place = placeUUID;
const person = await people.getOrCreatePerson(contact);
const result = await updatePlace(placeUUID, { contact: person._id });
return { ...result, contact: { id: person._id } };
};

/*
Expand Down
103 changes: 84 additions & 19 deletions shared-libs/contacts/test/unit/places.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const contactTypes = [
group_key: 'contact.type.health_center.plural',
create_key: 'contact.type.health_center.new',
edit_key: 'contact.type.place.edit',
parents: [ 'district_hospital' ],
parents: ['district_hospital'],
icon: 'medic-health-center',
create_form: 'form:contact:health_center:create',
edit_form: 'form:contact:health_center:edit'
Expand All @@ -37,7 +37,7 @@ const contactTypes = [
group_key: 'contact.type.clinic.plural',
create_key: 'contact.type.clinic.new',
edit_key: 'contact.type.place.edit',
parents: [ 'health_center' ],
parents: ['health_center'],
icon: 'medic-clinic',
create_form: 'form:contact:clinic:create',
edit_form: 'form:contact:clinic:edit',
Expand All @@ -50,7 +50,7 @@ const contactTypes = [
create_key: 'contact.type.person.new',
edit_key: 'contact.type.person.edit',
primary_contact_key: 'clinic.field.contact',
parents: [ 'district_hospital', 'health_center', 'clinic' ],
parents: ['district_hospital', 'health_center', 'clinic'],
icon: 'medic-person',
create_form: 'form:contact:person:create',
edit_form: 'form:contact:person:edit',
Expand Down Expand Up @@ -263,14 +263,14 @@ describe('places controller', () => {
};
db.medic.post.callsFake(doc => {
if (doc.name === 'CHP Branch One') {
return Promise.resolve({id: 'abc'});
return Promise.resolve({ id: 'abc' });
}
if (doc.name === 'CHP Area One') {
// the parent should be created/resolved, parent id should be set.
chai.expect(doc.parent._id).to.equal('abc');
chai.expect(doc.parent.name).to.equal(undefined); // minified
chai.expect(doc.parent.type).to.equal(undefined); // minified
return Promise.resolve({id: 'def'});
return Promise.resolve({ id: 'def' });
}
if (doc.name === 'CHP Family') {
// both parents should be created/resolved
Expand All @@ -280,7 +280,7 @@ describe('places controller', () => {
chai.expect(doc.parent.parent._id).to.equal('abc');
chai.expect(doc.parent.parent.name).to.equal(undefined); // minified
chai.expect(doc.parent.parent.type).to.equal(undefined); // minified
return Promise.resolve({id: 'ghi'});
return Promise.resolve({ id: 'ghi' });
}
});
fetchHydratedDoc.callsFake(id => {
Expand Down Expand Up @@ -322,7 +322,7 @@ describe('places controller', () => {
}
});
return controller._createPlaces(place).then(actual => {
chai.expect(actual).to.deep.equal({id: 'ghi'});
chai.expect(actual).to.deep.equal({ id: 'ghi' });
});
});

Expand All @@ -336,27 +336,92 @@ describe('places controller', () => {
type: 'person'
}
};

sinon.stub(people, 'getOrCreatePerson').resolves({
_id: 'qwe',
_rev: '1',
name: 'Jim',
type: 'person'
});
fetchHydratedDoc.resolves({
_id: 'ad06d137',
name: 'CHP Branch One',
type: 'district_hospital'
db.medic.post.withArgs(
sinon.match((doc) => !doc.contact)
).callsFake(doc => {
chai.expect(doc.name).to.equal('CHP Family');
chai.expect(doc.parent._id).to.equal('ad06d137');
return Promise.resolve({ id: 'hc', rev: '1' });
});
db.medic.post.callsFake(doc => {
db.medic.post.withArgs(
sinon.match(doc => doc.contact)
).callsFake(doc => {
chai.expect(doc.name).to.equal('CHP Family');
chai.expect(doc.parent._id).to.equal('ad06d137');
chai.expect(doc.contact._id).to.equal('qwe');
chai.expect(doc.contact.name).to.equal(undefined); // minified
chai.expect(doc.contact.type).to.equal(undefined); // minified
return Promise.resolve({id: 'ghi'});
chai.expect(doc.contact.type).to.equal(undefined); //
return Promise.resolve({ id: 'hc', rev: '2' });
});

fetchHydratedDoc.withArgs('hc').resolves({
name: 'CHP Family',
type: 'health_center',
parent: {
_id: 'ad06d137',
name: 'CHP Branch One',
type: 'district_hospital'
}
});
fetchHydratedDoc.withArgs('ad06d137').resolves({
_id: 'ad06d137',
name: 'CHP Branch One',
type: 'district_hospital'
});

return controller._createPlaces(place).then(actual => {
chai.expect(actual).to.deep.equal({id: 'ghi'});
chai.expect(db.medic.post.callCount).to.equal(2);
chai.expect(actual).to.deep.equal({
id: 'hc',
rev: '2',
contact: {
id: 'qwe',
}
});
});
});

it('returns err if contact does not have name', done => {
const place = {
name: 'HC',
type: 'district_hospital',
contact: {
type: 'person'
}
};
const post = db.medic.post;
controller._createPlaces(place).catch(err => {
chai.expect(err.message).to.equal('Person is missing a "name" property.');
chai.expect(post.callCount).to.equal(0);
done();
});
});

it('rejects contacts with wrong type', done => {
const place = {
name: 'HC',
type: 'district_hospital',
contact: {
name: 'John Doe',
type: 'x'
}
};
const post = db.medic.post;
controller._createPlaces(place).catch(err => {
chai.expect(err.message).to.equal('Wrong type, this is not a person.');
chai.expect(post.callCount).to.equal(0);
done();
});
});


it('supports parents defined as uuids.', () => {
const place = {
name: 'CHP Area One',
Expand All @@ -375,10 +440,10 @@ describe('places controller', () => {
chai.expect(doc.parent._id).to.equal('ad06d137');
chai.expect(doc.parent.name).to.equal(undefined); // minified
chai.expect(doc.parent.type).to.equal(undefined); // minified
return Promise.resolve({id: 'abc123'});
return Promise.resolve({ id: 'abc123' });
});
return controller._createPlaces(place).then(actual => {
chai.expect(actual).to.deep.equal({id: 'abc123'});
chai.expect(actual).to.deep.equal({ id: 'abc123' });
});
});

Expand Down Expand Up @@ -457,7 +522,7 @@ describe('places controller', () => {
db.medic.post.callsFake(doc => {
chai.expect(doc.contact._id).to.equal('a');
chai.expect(doc.contact.name).to.equal(undefined); // minified
return Promise.resolve({id: 'x', rev: 'y'});
return Promise.resolve({ id: 'x', rev: 'y' });
});
return controller.updatePlace('123', data).then(actual => {
chai.expect(actual).to.deep.equal({ id: 'x', rev: 'y' });
Expand All @@ -474,7 +539,7 @@ describe('places controller', () => {
db.medic.post.callsFake(doc => {
chai.expect(doc.parent._id).to.equal('a');
chai.expect(doc.parent.name).to.equal(undefined); // minified
return Promise.resolve({id: 'x', rev: 'y'});
return Promise.resolve({ id: 'x', rev: 'y' });
});
return controller.updatePlace('123', data).then(actual => {
chai.expect(actual).to.deep.equal({ id: 'x', rev: 'y' });
Expand Down
Loading

0 comments on commit 6dec634

Please sign in to comment.