Skip to content

Commit

Permalink
feat(#9065): add cht-datasource to support get person by uuid (#9090)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkuester authored Jun 12, 2024
1 parent be647f4 commit 59b42e2
Show file tree
Hide file tree
Showing 82 changed files with 2,236 additions and 280 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"api/src/public/login/lib-bowser.js",
"build/**",
"jsdocs/**",
"shared-libs/cht-datasource/dist/**",
"tests/scalability/report*/**",
"tests/scalability/jmeter/**",
"webapp/src/ts/providers/xpath-element-path.provider.ts"
Expand Down
6 changes: 6 additions & 0 deletions .mocharc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const chaiExclude = require('chai-exclude');
const chaiAsPromised = require('chai-as-promised');
const chai = require('chai');

chai.use(chaiExclude);
chai.use(chaiAsPromised);
4 changes: 2 additions & 2 deletions admin/package-lock.json

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

7 changes: 4 additions & 3 deletions admin/src/js/services/auth.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const chtScriptApi = require('@medic/cht-script-api');
const cht = require('@medic/cht-datasource');
const chtDatasource = cht.getDatasource(cht.getRemoteDataContext());

angular.module('inboxServices').factory('Auth',
function(
Expand Down Expand Up @@ -36,7 +37,7 @@ angular.module('inboxServices').factory('Auth',
return false;
}

return chtScriptApi.v1.hasAnyPermission(permissionsGroupList, userCtx.roles, settings.permissions);
return chtDatasource.v1.hasAnyPermission(permissionsGroupList, userCtx.roles, settings.permissions);
})
.catch(() => false);
};
Expand All @@ -62,7 +63,7 @@ angular.module('inboxServices').factory('Auth',
return false;
}

return chtScriptApi.v1.hasPermissions(permissions, userCtx.roles, settings.permissions);
return chtDatasource.v1.hasPermissions(permissions, userCtx.roles, settings.permissions);
})
.catch(() => false);
};
Expand Down
2 changes: 1 addition & 1 deletion api/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
{
"allowModules": [
"@medic/bulk-docs-utils",
"@medic/cht-script-api",
"@medic/cht-datasource",
"@medic/contact-types-utils",
"@medic/contacts",
"@medic/couch-request",
Expand Down
3 changes: 2 additions & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
},
"scripts": {
"toc": "doctoc --github --maxlevel 2 README.md",
"postinstall": "patch-package"
"postinstall": "patch-package",
"run-watch": "TZ=UTC nodemon --inspect=0.0.0.0:9229 --ignore 'build/static' --ignore 'build/public' --watch ./ --watch '../shared-libs/**/src/**' server.js -- --allow-cors"
},
"dependencies": {
"@medic/logger": "file:../shared-libs/logger",
Expand Down
20 changes: 20 additions & 0 deletions api/src/controllers/person.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const { Person, Qualifier } = require('@medic/cht-datasource');
const ctx = require('../services/data-context');
const serverUtils = require('../server-utils');
const auth = require('../auth');

const getPerson = (qualifier) => ctx.bind(Person.v1.get)(qualifier);

module.exports = {
v1: {
get: serverUtils.doOrError(async (req, res) => {
await auth.check(req, 'can_view_contacts');
const { uuid } = req.params;
const person = await getPerson(Qualifier.byUuid(uuid));
if (!person) {
return serverUtils.error({ status: 404, message: 'Person not found' }, req, res);
}
return res.json(person);
})
}
};
3 changes: 3 additions & 0 deletions api/src/routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const exportData = require('./controllers/export-data');
const records = require('./controllers/records');
const forms = require('./controllers/forms');
const users = require('./controllers/users');
const person = require('./controllers/person');
const { people, places } = require('@medic/contacts')(config, db);
const upgrade = require('./controllers/upgrade');
const settings = require('./controllers/settings');
Expand Down Expand Up @@ -475,6 +476,8 @@ app.postJson('/api/v1/people', function(req, res) {
.catch(err => serverUtils.error(err, req, res));
});

app.get('/api/v1/person/:uuid', person.v1.get);

app.postJson('/api/v1/bulk-delete', bulkDocs.bulkDelete);

// offline users are not allowed to hydrate documents via the hydrate API
Expand Down
8 changes: 8 additions & 0 deletions api/src/server-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,12 @@ module.exports = {
},

wantsJSON,

doOrError: (fn) => async (req, res) => {
try {
return await fn(req, res);
} catch (err) {
module.exports.error(err, req, res);
}
}
};
5 changes: 5 additions & 0 deletions api/src/services/data-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { getLocalDataContext } = require('@medic/cht-datasource');
const db = require('../db');
const config = require('../config');

module.exports = getLocalDataContext(config, db);
90 changes: 90 additions & 0 deletions api/tests/mocha/controllers/person.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const sinon = require('sinon');
const { expect } = require('chai');
const { Person, Qualifier } = require('@medic/cht-datasource');
const auth = require('../../../src/auth');
const controller = require('../../../src/controllers/person');
const dataContext = require('../../../src/services/data-context');
const serverUtils = require('../../../src/server-utils');

describe('Person Controller', () => {
let authCheck;
let dataContextBind;
let serverUtilsError;
let req;
let res;

beforeEach(() => {
authCheck = sinon.stub(auth, 'check');
dataContextBind = sinon.stub(dataContext, 'bind');
serverUtilsError = sinon.stub(serverUtils, 'error');
res = {
json: sinon.stub(),
};
});

afterEach(() => sinon.restore());

describe('v1', () => {
describe('get', () => {
const qualifier = Object.freeze({ uuid: 'uuid' });
let byUuid;
let personGet;

beforeEach(() => {
req = { params: { uuid: 'uuid' } };
byUuid = sinon
.stub(Qualifier, 'byUuid')
.returns(qualifier);
personGet = sinon.stub();
dataContextBind
.withArgs(Person.v1.get)
.returns(personGet);
});

it('returns a person', async () => {
const person = { name: 'John Doe' };
personGet.resolves(person);

await controller.v1.get(req, res);

expect(authCheck.calledOnceWithExactly(req, 'can_view_contacts')).to.be.true;
expect(dataContextBind.calledOnceWithExactly(Person.v1.get)).to.be.true;
expect(byUuid.calledOnceWithExactly(req.params.uuid)).to.be.true;
expect(personGet.calledOnceWithExactly(qualifier)).to.be.true;
expect(res.json.calledOnceWithExactly(person)).to.be.true;
expect(serverUtilsError.notCalled).to.be.true;
});

it('returns a 404 error if person is not found', async () => {
personGet.resolves(null);

await controller.v1.get(req, res);

expect(authCheck.calledOnceWithExactly(req, 'can_view_contacts')).to.be.true;
expect(dataContextBind.calledOnceWithExactly(Person.v1.get)).to.be.true;
expect(byUuid.calledOnceWithExactly(req.params.uuid)).to.be.true;
expect(personGet.calledOnceWithExactly(qualifier)).to.be.true;
expect(res.json.notCalled).to.be.true;
expect(serverUtilsError.calledOnceWithExactly(
{ status: 404, message: 'Person not found' },
req,
res
)).to.be.true;
});

it('returns error if user unauthorized', async () => {
const error = new Error('Unauthorized');
authCheck.rejects(error);

await controller.v1.get(req, res);

expect(authCheck.calledOnceWithExactly(req, 'can_view_contacts')).to.be.true;
expect(dataContextBind.notCalled).to.be.true;
expect(byUuid.notCalled).to.be.true;
expect(personGet.notCalled).to.be.true;
expect(res.json.notCalled).to.be.true;
expect(serverUtilsError.calledOnceWithExactly(error, req, res)).to.be.true;
});
});
});
});
27 changes: 27 additions & 0 deletions api/tests/mocha/server-utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,31 @@ describe('Server utils', () => {
});
});

describe('doOrError', () => {
let serverUtilsError;

beforeEach(() => {
serverUtilsError = sinon.stub(serverUtils, 'error');
});

it('returns the function output when no error is thrown', async () => {
const fn = sinon.stub().resolves('result');

const result = await serverUtils.doOrError(fn)(req, res);

chai.expect(result).to.equal('result');
chai.expect(fn.calledOnceWithExactly(req, res)).to.be.true;
chai.expect(serverUtilsError.notCalled).to.be.true;
});

it('calls error when an error is thrown', async () => {
const error = new Error('error');
const fn = sinon.stub().rejects(error);

await serverUtils.doOrError(fn)(req, res);

chai.expect(fn.calledOnceWithExactly(req, res)).to.be.true;
chai.expect(serverUtilsError.calledOnceWithExactly(error, req, res)).to.be.true;
});
});
});
14 changes: 14 additions & 0 deletions api/tests/mocha/services/data-context.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const dataSource = require('@medic/cht-datasource');
const { expect } = require('chai');
const db = require('../../../src/db');
const config = require('../../../src/config');
const dataContext = require('../../../src/services/data-context');

describe('Data context service', () => {
it('is initialized with the methods from the data context', () => {
const expectedDataContext = dataSource.getLocalDataContext(config, db);

expect(dataContext.bind).is.a('function');
expect(dataContext).excluding('bind').to.deep.equal(expectedDataContext);
});
});
Loading

0 comments on commit 59b42e2

Please sign in to comment.