From cac87f3ebdf63a5ea841783c08cc07d7e9255c2f Mon Sep 17 00:00:00 2001 From: kennsippell Date: Mon, 9 Dec 2024 01:12:37 -0800 Subject: [PATCH] Assert if core version is insufficient --- src/fn/delete-contacts.js | 1 + src/fn/merge-contacts.js | 1 + src/fn/upload-docs.js | 30 +++++++++++++---- src/lib/api.js | 7 ++-- .../hierarchy-operations/delete-hierarchy.js | 2 ++ test/fn/upload-docs.spec.js | 32 ++++++++++++++++++- 6 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/fn/delete-contacts.js b/src/fn/delete-contacts.js index 87c7a1f2..133d4342 100644 --- a/src/fn/delete-contacts.js +++ b/src/fn/delete-contacts.js @@ -15,6 +15,7 @@ module.exports = { const options = { docDirectoryPath: args.docDirectoryPath, force: args.force, + disableUsers: args.disableUsers, }; return HierarchyOperations(db, options).delete(args.sourceIds); } diff --git a/src/fn/merge-contacts.js b/src/fn/merge-contacts.js index 41bae589..edd3c9eb 100644 --- a/src/fn/merge-contacts.js +++ b/src/fn/merge-contacts.js @@ -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, }; diff --git a/src/fn/upload-docs.js b/src/fn/upload-docs.js index 2db6e1b3..2a246877 100644 --- a/src/fn/upload-docs.js +++ b/src/fn/upload-docs.js @@ -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'); @@ -123,10 +125,12 @@ 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; } @@ -134,14 +138,26 @@ async function handleUsersAtDeletedFacilities(deletedDocIds) { 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) { diff --git a/src/lib/api.js b/src/lib/api.js index e11a96a8..9035c29e 100644 --- a/src/lib/api.js +++ b/src/lib/api.js @@ -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; }, /** diff --git a/src/lib/hierarchy-operations/delete-hierarchy.js b/src/lib/hierarchy-operations/delete-hierarchy.js index ec3cff7a..4d9f470c 100644 --- a/src/lib/hierarchy-operations/delete-hierarchy.js +++ b/src/lib/hierarchy-operations/delete-hierarchy.js @@ -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) { diff --git a/test/fn/upload-docs.spec.js b/test/fn/upload-docs.spec.js index 94de5c7b..514e88ae 100644 --- a/test/fn/upload-docs.spec.js +++ b/test/fn/upload-docs.spec.js @@ -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); @@ -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(); @@ -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, @@ -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'); @@ -156,6 +162,22 @@ 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: {} }, ]); @@ -163,6 +185,8 @@ describe('upload-docs', function() { 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; @@ -170,7 +194,9 @@ describe('upload-docs', function() { 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 () => { @@ -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 }, ]); @@ -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: {} }, @@ -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 }, @@ -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, );