Skip to content

Commit

Permalink
Assert if core version is insufficient
Browse files Browse the repository at this point in the history
  • Loading branch information
kennsippell committed Dec 9, 2024
1 parent dcff03e commit cac87f3
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/fn/delete-contacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
const options = {
docDirectoryPath: args.docDirectoryPath,
force: args.force,
disableUsers: args.disableUsers,
};
return HierarchyOperations(db, options).delete(args.sourceIds);
}
Expand Down
1 change: 1 addition & 0 deletions src/fn/merge-contacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
const args = parseExtraArgs(environment.pathToProject, environment.extraArgs);
const db = pouch();
const options = {
disableUsers: args.disableUsers,
docDirectoryPath: args.docDirectoryPath,
force: args.force,
};
Expand Down
30 changes: 23 additions & 7 deletions src/fn/upload-docs.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const path = require('path');
const minimist = require('minimist');
const semver = require('semver');

const api = require('../lib/api');
const environment = require('../lib/environment');
const fs = require('../lib/sync-fs');
const { getValidApiVersion } = require('../lib/get-api-version');
const log = require('../lib/log');
const pouch = require('../lib/db');
const progressBar = require('../lib/progress-bar');
Expand Down Expand Up @@ -123,25 +125,39 @@ function analyseFiles(filePaths) {
}

async function handleUsersAtDeletedFacilities(deletedDocIds) {
await assertCoreVersion();

const affectedUsers = await getAffectedUsers(deletedDocIds);
const usernames = affectedUsers.map(userDoc => userDoc.username).join(', ');
if (affectedUsers.length === 0) {
trace('No deleted places with potential users found.');
trace('No users found needing an update.');
return;
}

warnAndPrompt(`This operation will update permissions for ${affectedUsers.length} user accounts: ${usernames}. Are you sure you want to continue?`);
await updateAffectedUsers(affectedUsers);
}

async function assertCoreVersion() {
const actualCoreVersion = await getValidApiVersion();
if (semver.lt(actualCoreVersion, '4.7.0-dev')) {
throw Error(`CHT Core Version 4.7.0 or newer is required to use --disable-users options. Version is ${actualCoreVersion}.`);
}

trace(`Core version is ${actualCoreVersion}. Proceeding to disable users.`)
}

async function getAffectedUsers(deletedDocIds) {
const toPostApiFormat = (apiResponse) => ({
_id: apiResponse.id,
_rev: apiResponse.rev,
username: apiResponse.username,
place: apiResponse.place?.filter(Boolean).map(place => place._id),
});
const toPostApiFormat = (apiResponse) => {
const places = Array.isArray(apiResponse.place) ? apiResponse.place.filter(Boolean) : [apiResponse.place];
const placeIds = places.map(place => place?._id);
return {
_id: apiResponse.id,
_rev: apiResponse.rev,
username: apiResponse.username,
place: placeIds,
};
};

const knownUserDocs = {};
for (const facilityId of deletedDocIds) {
Expand Down
7 changes: 4 additions & 3 deletions src/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,10 @@ const api = {
}
},

version() {
return request.get({ uri: `${environment.instanceUrl}/api/deploy-info`, json: true }) // endpoint added in 3.5
.then(deploy_info => deploy_info && deploy_info.version);
async version() {
// endpoint added in 3.5
const response = await request.get({ uri: `${environment.instanceUrl}/api/deploy-info`, json: true });
return response.deploy_info?.version;
},

/**
Expand Down
2 changes: 2 additions & 0 deletions src/lib/hierarchy-operations/delete-hierarchy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const { trace, info } = require('../log');

const prettyPrintDocument = doc => `'${doc.name}' (${doc._id})`;
async function deleteHierarchy(db, options, sourceIds) {
JsDocs.prepareFolder(options);

const sourceDocs = await DataSource.getContactsByIds(db, sourceIds);
const constraints = await lineageConstraints(db, options);
for (const sourceId of sourceIds) {
Expand Down
32 changes: 31 additions & 1 deletion test/fn/upload-docs.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ uploadDocs.__set__('userPrompt', userPrompt);

let fs, expectedDocs;

const API_VERSION_RESPONSE = { status: 200, body: { deploy_info: { version: '4.10.0'} }};

describe('upload-docs', function() {
beforeEach(() => {
sinon.stub(environment, 'isArchiveMode').get(() => false);
Expand Down Expand Up @@ -41,6 +43,8 @@ describe('upload-docs', function() {
});

it('should upload docs to pouch', async () => {
apiStub.giveResponses(API_VERSION_RESPONSE);

await assertDbEmpty();
await uploadDocs.execute();
const res = await apiStub.db.allDocs();
Expand Down Expand Up @@ -82,6 +86,7 @@ describe('upload-docs', function() {
expectedDocs = new Array(10).fill('').map((x, i) => ({ _id: i.toString() }));
const clock = sinon.useFakeTimers(0);
const imported_date = new Date().toISOString();
apiStub.giveResponses(API_VERSION_RESPONSE);
return uploadDocs.__with__({
INITIAL_BATCH_SIZE: 4,
Date,
Expand Down Expand Up @@ -128,6 +133,7 @@ describe('upload-docs', function() {
});

it('should not throw if force is set', async () => {
apiStub.giveResponses(API_VERSION_RESPONSE);
userPrompt.__set__('environment', { force: () => true });
await assertDbEmpty();
sinon.stub(process, 'exit');
Expand Down Expand Up @@ -156,21 +162,41 @@ describe('upload-docs', function() {
expect(res.rows.map(doc => doc.id)).to.deep.eq(['three', 'two']);

assert.deepEqual(apiStub.requestLog(), [
{ method: 'GET', url: '/api/deploy-info', body: {} },
{ method: 'GET', url: '/api/v2/users?facility_id=one', body: {} },
{ method: 'DELETE', url: '/api/v1/users/user1', body: {} },
]);
});

it('user with single place gets deleted (old core api format)', async () => {
await setupDeletedFacilities('one');
setupApiResponses(1, [{ id: 'org.couchdb.user:user1', username: 'user1', place: { _id: 'one' } }]);

await uploadDocs.execute();
const res = await apiStub.db.allDocs();
expect(res.rows.map(doc => doc.id)).to.deep.eq(['three', 'two']);

assert.deepEqual(apiStub.requestLog(), [
{ method: 'GET', url: '/api/deploy-info', body: {} },
{ method: 'GET', url: '/api/v2/users?facility_id=one', body: {} },
{ method: 'DELETE', url: '/api/v1/users/user1', body: {} },
]);
});

it('users associated with docs without truthy deleteUser attribute are not deleted', async () => {
const writtenDoc = await apiStub.db.put({ _id: 'one' });
apiStub.giveResponses(API_VERSION_RESPONSE);

const oneDoc = expectedDocs[0];
oneDoc._rev = writtenDoc.rev;
oneDoc._deleted = true;

await uploadDocs.execute();
const res = await apiStub.db.allDocs();
expect(res.rows.map(doc => doc.id)).to.deep.eq(['three', 'two']);
assert.deepEqual(apiStub.requestLog(), []);
assert.deepEqual(apiStub.requestLog(), [
{ method: 'GET', url: '/api/deploy-info', body: {} }
]);
});

it('user with multiple places gets updated', async () => {
Expand All @@ -187,6 +213,7 @@ describe('upload-docs', function() {
place: [ 'two' ],
};
assert.deepEqual(apiStub.requestLog(), [
{ method: 'GET', url: '/api/deploy-info', body: {} },
{ method: 'GET', url: '/api/v2/users?facility_id=one', body: {} },
{ method: 'POST', url: '/api/v1/users/user1', body: expectedBody },
]);
Expand All @@ -202,6 +229,7 @@ describe('upload-docs', function() {
expect(res.rows.map(doc => doc.id)).to.deep.eq(['three']);

assert.deepEqual(apiStub.requestLog(), [
{ method: 'GET', url: '/api/deploy-info', body: {} },
{ method: 'GET', url: '/api/v2/users?facility_id=one', body: {} },
{ method: 'GET', url: '/api/v2/users?facility_id=two', body: {} },
{ method: 'DELETE', url: '/api/v1/users/user1', body: {} },
Expand All @@ -225,6 +253,7 @@ describe('upload-docs', function() {
place: ['two'],
};
assert.deepEqual(apiStub.requestLog(), [
{ method: 'GET', url: '/api/deploy-info', body: {} },
{ method: 'GET', url: '/api/v2/users?facility_id=one', body: {} },
{ method: 'DELETE', url: '/api/v1/users/user1', body: {} },
{ method: 'POST', url: '/api/v1/users/user2', body: expectedUser2 },
Expand All @@ -237,6 +266,7 @@ function setupApiResponses(writeCount, ...userDocResponseRows) {
const responseBodies = userDocResponseRows.map(body => ({ body }));
const writeResponses = new Array(writeCount).fill({ status: 200 });
apiStub.giveResponses(
API_VERSION_RESPONSE,
...responseBodies,
...writeResponses,
);
Expand Down

0 comments on commit cac87f3

Please sign in to comment.