Skip to content

Commit 3bd4759

Browse files
authored
chore(#9582): update datasource bind to not return promise (#9583)
1 parent da4b50f commit 3bd4759

File tree

6 files changed

+134
-53
lines changed

6 files changed

+134
-53
lines changed

webapp/src/ts/services/cht-datasource.service.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,25 @@ export class CHTDatasourceService {
9797
return user?.roles || this.userCtx?.roles;
9898
}
9999

100-
async bind <T>(fn: (ctx: DataContext) => T): Promise<T> {
101-
await this.isInitialized();
102-
return this.dataContext.bind(fn);
100+
/**
101+
* Binds a cht-datasource function to the data context.
102+
* (e.g. `const getPersonWithLineage = this.bind(Person.v1.getWithLineage);`)
103+
* @param fn the function to bind. It should accept a data context as the parameter and return another function that
104+
* results in a `Promise`.
105+
* @returns a "context-aware" version of the function that is bound to the data context and ready to be used
106+
*/
107+
bind<R, F extends (arg?: unknown) => Promise<R>>(fn: (ctx: DataContext) => F):
108+
(...p: Parameters<F>) => ReturnType<F> {
109+
return (...p) => {
110+
return new Promise((resolve, reject) => {
111+
this.isInitialized().then(() => {
112+
const contextualFn = this.dataContext.bind(fn);
113+
contextualFn(...p)
114+
.then(resolve)
115+
.catch(reject);
116+
});
117+
}) as ReturnType<F>;
118+
};
103119
}
104120

105121
async get() {

webapp/src/ts/services/transitions/create-user-for-contacts.transition.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@ import { CHTDatasourceService } from '@mm-services/cht-datasource.service';
1111
providedIn: 'root'
1212
})
1313
export class CreateUserForContactsTransition extends Transition {
14+
private readonly getPlace: ReturnType<typeof Place.v1.get>;
15+
private readonly getPerson: ReturnType<typeof Person.v1.get>;
16+
1417
constructor(
15-
private chtDatasourceService: CHTDatasourceService,
18+
chtDatasourceService: CHTDatasourceService,
1619
private createUserForContactsService: CreateUserForContactsService,
1720
private extractLineageService: ExtractLineageService,
1821
private userContactService: UserContactService,
1922
) {
2023
super();
24+
this.getPlace = chtDatasourceService.bind(Place.v1.get);
25+
this.getPerson = chtDatasourceService.bind(Person.v1.get);
2126
}
2227

2328
readonly name = 'create_user_for_contacts';
@@ -133,8 +138,7 @@ export class CreateUserForContactsTransition extends Transition {
133138
return;
134139
}
135140

136-
const getPlace = await this.chtDatasourceService.bind(Place.v1.get);
137-
return getPlace(Qualifier.byUuid(doc.parent._id));
141+
return this.getPlace(Qualifier.byUuid(doc.parent._id));
138142
}
139143

140144
private async getNewContact(docs: Doc[], newContactId: string) {
@@ -143,8 +147,7 @@ export class CreateUserForContactsTransition extends Transition {
143147
return newContact as Person.v1.Person;
144148
}
145149

146-
const getPerson = await this.chtDatasourceService.bind(Person.v1.get);
147-
const person = await getPerson(Qualifier.byUuid(newContactId));
150+
const person = await this.getPerson(Qualifier.byUuid(newContactId));
148151
if (!person) {
149152
throw new Error(`The new contact could not be found [${newContactId}].`);
150153
}

webapp/src/ts/services/user-contact.service.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,23 @@ import { CHTDatasourceService } from '@mm-services/cht-datasource.service';
88
providedIn: 'root'
99
})
1010
export class UserContactService {
11+
private readonly getPerson: ReturnType<typeof Person.v1.get>;
12+
private readonly getPersonWithLineage: ReturnType<typeof Person.v1.getWithLineage>;
1113
constructor(
1214
private userSettingsService: UserSettingsService,
13-
private chtDatasourceService: CHTDatasourceService,
15+
chtDatasourceService: CHTDatasourceService,
1416
) {
17+
this.getPerson = chtDatasourceService.bind(Person.v1.get);
18+
this.getPersonWithLineage = chtDatasourceService.bind(Person.v1.getWithLineage);
1519
}
1620

1721
async get({ hydrateLineage = true } = {}) {
1822
const user: any = await this.getUserSettings();
1923
if (!user?.contact_id) {
2024
return null;
2125
}
22-
const getPerson = await this.chtDatasourceService.bind(hydrateLineage ? Person.v1.getWithLineage : Person.v1.get);
23-
return await getPerson(Qualifier.byUuid(user.contact_id));
26+
const getPerson = hydrateLineage ? this.getPersonWithLineage : this.getPerson;
27+
return getPerson(Qualifier.byUuid(user.contact_id));
2428
}
2529

2630
private getUserSettings = async () => {

webapp/tests/karma/ts/services/cht-datasource.service.spec.ts

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,24 @@ describe('CHTScriptApiService service', () => {
137137
sessionService.isOnlineOnly.returns(isOnlineOnly);
138138
const expectedDb = { hello: 'medic' };
139139
dbService.get.resolves(expectedDb);
140-
const innerFn = sinon.stub();
140+
const innerFn = sinon.stub().resolves('hello world');
141141
const outerFn = sinon
142142
.stub()
143143
.returns(innerFn);
144144

145-
const result = await service.bind(outerFn);
145+
const returnedFn = service.bind(outerFn);
146146

147+
expect(outerFn.notCalled).to.be.true;
148+
expect(innerFn.notCalled).to.be.true;
149+
150+
const result = await returnedFn('hello', 'world');
151+
152+
expect(result).to.equal('hello world');
147153
expect(outerFn.calledOnce).to.be.true;
148154
const [dataContext, ...other] = outerFn.args[0];
149155
expect(other).to.be.empty;
150156
expect(dataContext.bind).to.be.a('function');
151-
expect(result).to.equal(innerFn);
152-
expect(innerFn.notCalled).to.be.true;
157+
expect(innerFn.calledOnceWithExactly('hello', 'world')).to.be.true;
153158
expect(changesService.subscribe.calledOnce).to.be.true;
154159
expect(changesService.subscribe.args[0][0].key).to.equal('cht-script-api-settings-changes');
155160
expect(changesService.subscribe.args[0][0].filter).to.be.a('function');
@@ -161,6 +166,43 @@ describe('CHTScriptApiService service', () => {
161166
expect(dbService.get.callCount).to.equal(isOnlineOnly ? 0 : 1);
162167
});
163168
});
169+
170+
it('surfaces exceptions thrown by bound function', async () => {
171+
const settings = { hello: 'settings' } as const;
172+
settingsService.get.resolves(settings);
173+
const userCtx = { hello: 'world' };
174+
sessionService.userCtx.returns(userCtx);
175+
sessionService.isOnlineOnly.returns(true);
176+
const expectedDb = { hello: 'medic' };
177+
dbService.get.resolves(expectedDb);
178+
const expectedError = new Error('hello world');
179+
const innerFn = sinon.stub().rejects(expectedError);
180+
const outerFn = sinon
181+
.stub()
182+
.returns(innerFn);
183+
184+
const returnedFn = service.bind(outerFn);
185+
186+
expect(outerFn.notCalled).to.be.true;
187+
expect(innerFn.notCalled).to.be.true;
188+
189+
await expect(returnedFn()).to.be.rejectedWith(expectedError);
190+
191+
expect(outerFn.calledOnce).to.be.true;
192+
const [dataContext, ...other] = outerFn.args[0];
193+
expect(other).to.be.empty;
194+
expect(dataContext.bind).to.be.a('function');
195+
expect(innerFn.calledOnceWithExactly()).to.be.true;
196+
expect(changesService.subscribe.calledOnce).to.be.true;
197+
expect(changesService.subscribe.args[0][0].key).to.equal('cht-script-api-settings-changes');
198+
expect(changesService.subscribe.args[0][0].filter).to.be.a('function');
199+
expect(changesService.subscribe.args[0][0].callback).to.be.a('function');
200+
expect(sessionService.userCtx.calledOnceWithExactly()).to.be.true;
201+
expect(settingsService.get.calledOnceWithExactly()).to.be.true;
202+
expect(http.get.calledOnceWithExactly('/extension-libs', { responseType: 'json' })).to.be.true;
203+
expect(sessionService.isOnlineOnly.calledOnceWithExactly(userCtx)).to.be.true;
204+
expect(dbService.get.notCalled).to.be.true;
205+
});
164206
});
165207

166208
describe('v1.hasPermissions()', () => {

webapp/tests/karma/ts/services/transitions/create-user-for-contacts.transition.spec.ts

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ describe('Create User for Contacts Transition', () => {
7979
chtDatasourceService = {
8080
bind: sinon.stub()
8181
};
82-
chtDatasourceService.bind.withArgs(Person.v1.get).resolves(getPerson);
83-
chtDatasourceService.bind.withArgs(Place.v1.get).resolves(getPlace);
82+
chtDatasourceService.bind.withArgs(Person.v1.get).returns(getPerson);
83+
chtDatasourceService.bind.withArgs(Place.v1.get).returns(getPlace);
8484
createUserForContactsService = {
8585
isBeingReplaced: sinon.stub(),
8686
setReplaced: sinon.stub(),
@@ -103,10 +103,8 @@ describe('Create User for Contacts Transition', () => {
103103
});
104104

105105
afterEach(() => {
106-
if (chtDatasourceService.bind.notCalled) {
107-
expect(getPerson.notCalled).to.be.true;
108-
expect(getPlace.notCalled).to.be.true;
109-
}
106+
expect(chtDatasourceService.bind.args).to.deep.equal([[Place.v1.get], [Person.v1.get]]);
107+
sinon.restore();
110108
});
111109

112110
describe('init', () => {
@@ -116,7 +114,10 @@ describe('Create User for Contacts Transition', () => {
116114
consoleWarn = sinon.stub(console, 'warn');
117115
});
118116

119-
afterEach(() => sinon.restore());
117+
afterEach(() => {
118+
expect(getPerson.notCalled).to.be.true;
119+
expect(getPlace.notCalled).to.be.true;
120+
});
120121

121122
it('returns true when replace forms have been configured', () => {
122123
const settings = { create_user_for_contacts: { replace_forms: ['replace_user'] } };
@@ -145,6 +146,11 @@ describe('Create User for Contacts Transition', () => {
145146
});
146147

147148
describe('filter', () => {
149+
afterEach(() => {
150+
expect(getPerson.notCalled).to.be.true;
151+
expect(getPlace.notCalled).to.be.true;
152+
});
153+
148154
[
149155
[{ type: 'data_record' }],
150156
[{ type: 'person' }, { type: 'user-settings' }, { type: 'data_record' }],
@@ -181,7 +187,8 @@ describe('Create User for Contacts Transition', () => {
181187

182188
expect(docs).to.be.empty;
183189
expect(userContactService.get.callCount).to.equal(0);
184-
expect(chtDatasourceService.bind.notCalled).to.be.true;
190+
expect(getPerson.notCalled).to.be.true;
191+
expect(getPlace.notCalled).to.be.true;
185192
expect(createUserForContactsService.setReplaced.callCount).to.equal(0);
186193
expect(createUserForContactsService.isBeingReplaced.callCount).to.equal(0);
187194
});
@@ -193,7 +200,8 @@ describe('Create User for Contacts Transition', () => {
193200

194201
expect(docs).to.deep.equal([REPLACE_USER_DOC]);
195202
expect(userContactService.get.callCount).to.equal(1);
196-
expect(chtDatasourceService.bind.notCalled).to.be.true;
203+
expect(getPerson.notCalled).to.be.true;
204+
expect(getPlace.notCalled).to.be.true;
197205
expect(createUserForContactsService.setReplaced.callCount).to.equal(0);
198206
expect(createUserForContactsService.isBeingReplaced.callCount).to.equal(0);
199207
});
@@ -208,7 +216,8 @@ describe('Create User for Contacts Transition', () => {
208216

209217
expect(docs).to.deep.equal([submittedDocs[0], submittedDocs[2], submittedDocs[3]]);
210218
expect(userContactService.get.callCount).to.equal(1);
211-
expect(chtDatasourceService.bind.notCalled).to.be.true;
219+
expect(getPerson.notCalled).to.be.true;
220+
expect(getPlace.notCalled).to.be.true;
212221
expect(createUserForContactsService.setReplaced.callCount).to.equal(0);
213222
expect(createUserForContactsService.isBeingReplaced.callCount).to.equal(1);
214223
expect(createUserForContactsService.isBeingReplaced.args[0]).to.deep.equal([ORIGINAL_CONTACT]);
@@ -232,7 +241,6 @@ describe('Create User for Contacts Transition', () => {
232241
}
233242
}]);
234243
expect(userContactService.get.callCount).to.equal(1);
235-
expect(chtDatasourceService.bind.args).to.deep.equal([[Person.v1.get], [Place.v1.get]]);
236244
expect(getPerson.calledOnceWithExactly(Qualifier.byUuid(NEW_CONTACT._id))).to.be.true;
237245
expect(getPlace.calledOnceWithExactly(Qualifier.byUuid(parentPlace._id))).to.be.true;
238246
expect(createUserForContactsService.setReplaced.callCount).to.equal(1);
@@ -256,7 +264,7 @@ describe('Create User for Contacts Transition', () => {
256264
}
257265
}]);
258266
expect(userContactService.get.callCount).to.equal(1);
259-
expect(chtDatasourceService.bind.args).to.deep.equal([[Place.v1.get]]);
267+
expect(getPerson.notCalled).to.be.true;
260268
expect(getPlace.calledOnceWithExactly(Qualifier.byUuid(parentPlace._id))).to.be.true;
261269
expect(createUserForContactsService.setReplaced.callCount).to.equal(1);
262270
expect(createUserForContactsService.setReplaced.args[0]).to.deep.equal([originalUser, NEW_CONTACT]);
@@ -311,10 +319,13 @@ describe('Create User for Contacts Transition', () => {
311319
expect(createUserForContactsService.getReplacedBy.args).to.deep.equal([[originalUser], [originalUser]]);
312320
// User replaced again
313321
expect(userContactService.get.callCount).to.equal(1);
314-
expect(chtDatasourceService.bind.args).to.deep.equal([[Place.v1.get]]);
322+
expect(getPerson.notCalled).to.be.true;
315323
expect(getPlace.calledOnceWithExactly(Qualifier.byUuid(parentPlace._id))).to.be.true;
316324
expect(createUserForContactsService.setReplaced.callCount).to.equal(1);
317325
expect(createUserForContactsService.setReplaced.args[0]).to.deep.equal([originalUser, secondNewContact]);
326+
// Hack to keep the afterEach assertion happy since we called resetHistory
327+
chtDatasourceService.bind(Place.v1.get);
328+
chtDatasourceService.bind(Person.v1.get);
318329
});
319330

320331
it('does not assign new contact as primary contact when original contact was not primary', async () => {
@@ -329,7 +340,6 @@ describe('Create User for Contacts Transition', () => {
329340
expect(docs).to.deep.equal([REPLACE_USER_DOC, originalUser]);
330341
expect(parentPlace.contact).to.deep.equal({ _id: 'different-contact', });
331342
expect(userContactService.get.callCount).to.equal(1);
332-
expect(chtDatasourceService.bind.args).to.deep.equal([[Person.v1.get], [Place.v1.get]]);
333343
expect(getPerson.calledOnceWithExactly(Qualifier.byUuid(NEW_CONTACT._id))).to.be.true;
334344
expect(getPlace.calledOnceWithExactly(Qualifier.byUuid(parentPlace._id))).to.be.true;
335345
expect(createUserForContactsService.setReplaced.callCount).to.equal(1);
@@ -347,7 +357,6 @@ describe('Create User for Contacts Transition', () => {
347357

348358
expect(docs).to.deep.equal([REPLACE_USER_DOC, originalUser]);
349359
expect(userContactService.get.callCount).to.equal(1);
350-
expect(chtDatasourceService.bind.args).to.deep.equal([[Person.v1.get], [Place.v1.get]]);
351360
expect(getPerson.calledOnceWithExactly(Qualifier.byUuid(NEW_CONTACT._id))).to.be.true;
352361
expect(getPlace.calledOnceWithExactly(Qualifier.byUuid(PARENT_PLACE._id))).to.be.true;
353362
expect(createUserForContactsService.setReplaced.callCount).to.equal(1);
@@ -369,8 +378,8 @@ describe('Create User for Contacts Transition', () => {
369378

370379
expect(docs).to.deep.equal([REPLACE_USER_DOC, originalUser]);
371380
expect(userContactService.get.callCount).to.equal(1);
372-
expect(chtDatasourceService.bind.args).to.deep.equal([[Person.v1.get]]);
373381
expect(getPerson.calledOnceWithExactly(Qualifier.byUuid(newContact._id))).to.be.true;
382+
expect(getPlace.notCalled).to.be.true;
374383
expect(createUserForContactsService.setReplaced.callCount).to.equal(1);
375384
expect(createUserForContactsService.setReplaced.args[0]).to.deep.equal([originalUser, newContact]);
376385
expect(createUserForContactsService.isBeingReplaced.callCount).to.equal(2);
@@ -392,7 +401,8 @@ describe('Create User for Contacts Transition', () => {
392401
}
393402

394403
expect(userContactService.get.callCount).to.equal(1);
395-
expect(chtDatasourceService.bind.notCalled).to.be.true;
404+
expect(getPerson.notCalled).to.be.true;
405+
expect(getPlace.notCalled).to.be.true;
396406
expect(createUserForContactsService.setReplaced.callCount).to.equal(0);
397407
expect(createUserForContactsService.isBeingReplaced.callCount).to.equal(2);
398408
});
@@ -412,7 +422,8 @@ describe('Create User for Contacts Transition', () => {
412422
}
413423

414424
expect(userContactService.get.callCount).to.equal(1);
415-
expect(chtDatasourceService.bind.notCalled).to.be.true;
425+
expect(getPerson.notCalled).to.be.true;
426+
expect(getPlace.notCalled).to.be.true;
416427
expect(createUserForContactsService.setReplaced.callCount).to.equal(0);
417428
expect(createUserForContactsService.isBeingReplaced.callCount).to.equal(1);
418429
});
@@ -430,8 +441,8 @@ describe('Create User for Contacts Transition', () => {
430441
}
431442

432443
expect(userContactService.get.callCount).to.equal(1);
433-
expect(chtDatasourceService.bind.args).to.deep.equal([[Person.v1.get]]);
434444
expect(getPerson.calledOnceWithExactly(Qualifier.byUuid(NEW_CONTACT._id))).to.be.true;
445+
expect(getPlace.notCalled).to.be.true;
435446
expect(createUserForContactsService.setReplaced.callCount).to.equal(0);
436447
expect(createUserForContactsService.isBeingReplaced.callCount).to.equal(2);
437448
});
@@ -448,8 +459,8 @@ describe('Create User for Contacts Transition', () => {
448459
}
449460

450461
expect(userContactService.get.callCount).to.equal(1);
451-
expect(chtDatasourceService.bind.args).to.deep.equal([[Person.v1.get]]);
452462
expect(getPerson.calledOnceWithExactly(Qualifier.byUuid(NEW_CONTACT._id))).to.be.true;
463+
expect(getPlace.notCalled).to.be.true;
453464
expect(createUserForContactsService.setReplaced.callCount).to.equal(0);
454465
expect(createUserForContactsService.isBeingReplaced.callCount).to.equal(2);
455466
});
@@ -468,7 +479,8 @@ describe('Create User for Contacts Transition', () => {
468479
}
469480

470481
expect(userContactService.get.callCount).to.equal(1);
471-
expect(chtDatasourceService.bind.notCalled).to.be.true;
482+
expect(getPerson.notCalled).to.be.true;
483+
expect(getPlace.notCalled).to.be.true;
472484
expect(createUserForContactsService.setReplaced.callCount).to.equal(0);
473485
expect(createUserForContactsService.isBeingReplaced.callCount).to.equal(1);
474486
});
@@ -477,7 +489,8 @@ describe('Create User for Contacts Transition', () => {
477489
describe(`when the reports submitted do not include a replace user report, but the user is replaced`, () => {
478490
afterEach(() => {
479491
// Functions from the user replace flow should not be called
480-
expect(chtDatasourceService.bind.notCalled).to.be.true;
492+
expect(getPerson.notCalled).to.be.true;
493+
expect(getPlace.notCalled).to.be.true;
481494
expect(createUserForContactsService.setReplaced.callCount).to.equal(0);
482495
});
483496

0 commit comments

Comments
 (0)