From 8272fa4ed247cfef74565ea7eae97e44ad89d853 Mon Sep 17 00:00:00 2001 From: Davide Aquaro Date: Mon, 16 Jan 2023 15:37:08 +0000 Subject: [PATCH] MIG-156 : added flag to exclude eTemplates from study export --- .../__tests__/MIG-154/MIG-154.test.js | 31 ++- .../__tests__/MIG-156/MIG-156.test.js | 251 ++++++++++++++++++ .../lib/StudyManifestTools.js | 50 ++-- packages/mdctl-cli/tasks/study.js | 15 +- packages/mdctl-cli/test/tasks/study.js | 241 ++++++++++++++++- 5 files changed, 546 insertions(+), 42 deletions(-) create mode 100644 packages/mdctl-axon-tools/__tests__/MIG-156/MIG-156.test.js diff --git a/packages/mdctl-axon-tools/__tests__/MIG-154/MIG-154.test.js b/packages/mdctl-axon-tools/__tests__/MIG-154/MIG-154.test.js index c1939d5e..ced98710 100644 --- a/packages/mdctl-axon-tools/__tests__/MIG-154/MIG-154.test.js +++ b/packages/mdctl-axon-tools/__tests__/MIG-154/MIG-154.test.js @@ -41,21 +41,24 @@ describe('MIG-154 - Check new methods', () => { }) it('Test getKeyName', async() => { - const orgObjects = { - find: (filter) => { - const mockedObjects = [ - { - name: 'c_task', - uniqueKey: 'c_key' - }, - { - name: 'c_visit', - uniqueKey: '' - } - ] - return mockedObjects.filter(e => e.name === filter.name) + const orgObjects = [ + { + name: 'c_fault', + uniqueKey: 'c_key' + }, + { + name: 'c_group', + uniqueKey: 'c_key' + }, + { + name: 'c_task', + uniqueKey: 'c_key' + }, + { + name: 'c_visit', + uniqueKey: '' } - } + ] jest.mock('@medable/mdctl-core-utils/privates', () => ({ privatesAccessor: () => ({ orgObjects }) })) // eslint-disable-next-line global-require diff --git a/packages/mdctl-axon-tools/__tests__/MIG-156/MIG-156.test.js b/packages/mdctl-axon-tools/__tests__/MIG-156/MIG-156.test.js new file mode 100644 index 00000000..2b5d2287 --- /dev/null +++ b/packages/mdctl-axon-tools/__tests__/MIG-156/MIG-156.test.js @@ -0,0 +1,251 @@ +/* eslint-disable import/order */ + +jest.mock('@medable/mdctl-api-driver', () => ({ Driver: class {} }), { virtual: true }) +jest.mock('@medable/mdctl-api-driver/lib/cortex.object', () => ({ Object: class {}, Org: class {} }), { virtual: true }) +jest.mock('../../lib/mappings') + +const fs = require('fs'), + StudyManifestTools = require('../../lib/StudyManifestTools') + +describe('MIG-156 - Test eTemplate exclusion in StudyManifestTools', () => { + + let manifestTools + const mockGetExportedObjects = jest.fn(() => []), + existingStudy = { + _id: '1', + c_name: 'Study', + c_key: 'abc' + }, + hasNextStudyMock = jest.fn(() => true), + nextStudyMock = jest.fn(() => existingStudy), + entities = [{ + _id: '615bcd016631cc0100d2766c', + object: 'c_study', + c_key: 'key-001' + }, + { + _id: '615b60d1bf2e4301008f4d68', + object: 'c_dummy_object', + c_key: 'key-002' + }, + { + _id: '619aaaafe44c6e01003f7313', + object: 'c_task', + c_key: 'key-003' + }, + { + _id: '61981246ca9563010037bfa8', + object: 'c_task', + c_key: 'key-004' + }, + { + _id: '61981246ca95714c14e61a8c', + object: 'c_step', + c_key: 'key-005' + }, + { + _id: '61981246ca966caef6108f28', + object: 'c_step', + c_key: 'key-006' + }, + { + _id: '61981246ca9592ee0e41a3dd', + object: 'ec__document_template', + c_key: 'key-007' + }, + { + _id: '61980eb292466ea32e087378', + object: 'ec__document_template', + c_key: 'key-008' + }, + { + _id: '6d525cf2e328e7300d97c399', + object: 'ec__default_document_css', + c_key: 'key-009' + }, + { + _id: '6d525cfe328e64ac0833baef', + object: 'ec__knowledge_check', + c_key: 'key-010' + }, + { + _id: '6d525f2e328e7f1e48262523', + object: 'ec__knowledge_check', + c_key: 'key-011' + }, + { + _id: '6d525gbed28e7f1e4826bb76', + object: 'c_visit_schedule', + c_key: 'key-012' + }, + { + _id: '6d525gc1408e7f1e4826bb11', + object: 'c_visit', + c_key: 'key-013' + }, + { + _id: '6d525gbe28e7fc4ff43c310', + object: 'c_group', + c_key: 'key-014' + }, + { + _id: '67725gbe28e7f98ee3c8667', + object: 'c_group_task', + c_key: 'key-015' + }], + dummyReferences = [ + { + name: 'c_study', + array: false, + object: 'c_study', + type: 'Reference', + required: false + } + ], + org = { + objects: { + c_study: { + readOne: () => ({ + execute: () => ({ + hasNext: hasNextStudyMock, + next: nextStudyMock + }) + }) + }, + c_task: { + find: () => ({ + limit: () => ({ + toArray: () => entities.filter(e => e.object === 'c_task') + }) + }) + }, + ec__document_template: { + find: () => ({ + limit: () => ({ + toArray: () => entities.filter(e => e.object === 'ec__document_template') + }) + }) + }, + object: { + find: () => ({ + paths: () => ({ + toArray: () => [{ uniqueKey: 'c_key' }] + }) + }) + } + } + } + + beforeAll(async() => { + manifestTools = new StudyManifestTools({}) + manifestTools.getExportObjects = mockGetExportedObjects + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + it('Test eTemplates not excluded from manifest', async() => { + const manifestEntitiesToCompare = [ + { + _id: '619aaaafe44c6e01003f7313', + object: 'c_task', + c_key: 'key-003' + }, + { + _id: '61981246ca9563010037bfa8', + object: 'c_task', + c_key: 'key-004' + }, + { + _id: '61981246ca95714c14e61a8c', + object: 'c_step', + c_key: 'key-005' + }, + { + _id: '61981246ca966caef6108f28', + object: 'c_step', + c_key: 'key-006' + }, + { + _id: '61981246ca9592ee0e41a3dd', + object: 'ec__document_template', + c_key: 'key-007' + }, + { + _id: '61980eb292466ea32e087378', + object: 'ec__document_template', + c_key: 'key-008' + }, + { + _id: '6d525cf2e328e7300d97c399', + object: 'ec__default_document_css', + c_key: 'key-009' + }, + { + _id: '6d525cfe328e64ac0833baef', + object: 'ec__knowledge_check', + c_key: 'key-010' + }, + { + _id: '6d525f2e328e7f1e48262523', + object: 'ec__knowledge_check', + c_key: 'key-011' + } + ], + exportableObject = manifestTools.getAvailableObjectNames(), + keyName = 'c_key' + + jest.spyOn(StudyManifestTools.prototype, 'getExportableObjects').mockImplementation(() => exportableObject) + jest.spyOn(StudyManifestTools.prototype, 'getKeyName').mockImplementation(key => ((key === 'ec__document_template') ? 'ec__key' : keyName)) + jest.spyOn(StudyManifestTools.prototype, 'getTaskManifestEntities').mockImplementation(() => entities.filter(o => ['c_task', 'c_step', 'c_branch'].includes(o.object))) + jest.spyOn(StudyManifestTools.prototype, 'getConsentManifestEntities').mockImplementation(() => entities.filter(o => ['ec__document_template', 'ec__default_document_css', 'ec__knowledge_check'].includes(o.object))) + jest.spyOn(StudyManifestTools.prototype, 'getObjectIDsArray').mockImplementation(key => entities.filter(o => o.object === key)) + jest.spyOn(StudyManifestTools.prototype, 'mapObjectNameToPlural').mockImplementation(key => `${key}s`) + // eslint-disable-next-line one-var, max-len + const manifestEntities = await manifestTools.getStudyManifestEntities(org, existingStudy, {}, dummyReferences, false) + + expect(manifestEntities) + .toStrictEqual(manifestEntitiesToCompare) + }) + + it('Test eTemplates excluded from manifest', async() => { + const manifestEntitiesToCompare = [ + { + _id: '619aaaafe44c6e01003f7313', + object: 'c_task', + c_key: 'key-003' + }, + { + _id: '61981246ca9563010037bfa8', + object: 'c_task', + c_key: 'key-004' + }, + { + _id: '61981246ca95714c14e61a8c', + object: 'c_step', + c_key: 'key-005' + }, + { + _id: '61981246ca966caef6108f28', + object: 'c_step', + c_key: 'key-006' + } + ], + exportableObject = manifestTools.getAvailableObjectNames(), + keyName = 'c_key' + + jest.spyOn(StudyManifestTools.prototype, 'getExportableObjects').mockImplementation(() => exportableObject) + jest.spyOn(StudyManifestTools.prototype, 'getKeyName').mockImplementation(key => ((key === 'ec__document_template') ? 'ec__key' : keyName)) + jest.spyOn(StudyManifestTools.prototype, 'getTaskManifestEntities').mockImplementation(() => entities.filter(o => ['c_task', 'c_step', 'c_branch'].includes(o.object))) + jest.spyOn(StudyManifestTools.prototype, 'getConsentManifestEntities').mockImplementation(() => entities.filter(o => ['ec__document_template', 'ec__default_document_css', 'ec__knowledge_check'].includes(o.object))) + jest.spyOn(StudyManifestTools.prototype, 'getObjectIDsArray').mockImplementation(key => entities.filter(o => o.object === key)) + jest.spyOn(StudyManifestTools.prototype, 'mapObjectNameToPlural').mockImplementation(key => `${key}s`) + // eslint-disable-next-line one-var, max-len + const manifestEntities = await manifestTools.getStudyManifestEntities(org, existingStudy, {}, dummyReferences, true) + + expect(manifestEntities) + .toStrictEqual(manifestEntitiesToCompare) + }) + +}) diff --git a/packages/mdctl-axon-tools/lib/StudyManifestTools.js b/packages/mdctl-axon-tools/lib/StudyManifestTools.js index 388bb63f..3e5879d8 100644 --- a/packages/mdctl-axon-tools/lib/StudyManifestTools.js +++ b/packages/mdctl-axon-tools/lib/StudyManifestTools.js @@ -148,9 +148,9 @@ class StudyManifestTools { return privatesAccessor(this).orgObjects.find(v => v.pluralName === pluralName).name } - async getStudyManifest(manifestObject) { + async getStudyManifest(manifestObject, excludeTemplates = false) { console.log('Building Manifest') - const manifestAndDeps = await this.buildManifestAndDependencies(manifestObject) + const manifestAndDeps = await this.buildManifestAndDependencies(manifestObject, excludeTemplates) await this.writeStudyToDisk(manifestAndDeps) return manifestAndDeps } @@ -176,7 +176,12 @@ class StudyManifestTools { return { manifest, removedEntities } } - async buildManifestAndDependencies(manifestJSON) { + // This function is to facilitate unit testing + async getMappings(org) { + return getMappingScript(org) + } + + async buildManifestAndDependencies(manifestJSON, excludeTemplates = false) { let ignoreKeys = [], cleanManifest, study @@ -192,8 +197,8 @@ class StudyManifestTools { study = await this.getFirstStudy(org) } - const mappingScript = await getMappingScript(org), - allEntities = await this.getStudyManifestEntities(org, study, cleanManifest, orgReferenceProps), + const mappingScript = await this.getMappings(org), + allEntities = await this.getStudyManifestEntities(org, study, cleanManifest, orgReferenceProps, excludeTemplates), { manifest, removedEntities } = this .validateAndCreateManifest(allEntities, orgReferenceProps, ignoreKeys) @@ -300,15 +305,14 @@ class StudyManifestTools { createManifest(entities) { const manifest = { - object: 'manifest', - dependencies: false, - exportOwner: false, - importOwner: false - }, - { orgObjects } = privatesAccessor(this) + object: 'manifest', + dependencies: false, + exportOwner: false, + importOwner: false + } entities.forEach((entity) => { - const { uniqueKey } = orgObjects.find(v => v.name === entity.object) + const uniqueKey = this.getKeyName(entity.object) if (!manifest[entity.object]) { manifest[entity.object] = { includes: [] @@ -703,11 +707,11 @@ class StudyManifestTools { } getKeyName(key) { - return first(privatesAccessor(this).orgObjects.find({ name: key }).map(({ uniqueKey }) => uniqueKey)) + return privatesAccessor(this).orgObjects + .find(({ name }) => name === key).uniqueKey } - async getStudyManifestEntities(org, study, manifestObject, orgReferenceProps) { - + async getStudyManifestEntities(org, study, manifestObject, orgReferenceProps, excludeTemplates = false) { const manifestEntities = [], // Get all objects that can be exported exportableObjects = this.getExportableObjects(), @@ -736,12 +740,14 @@ class StudyManifestTools { break } case 'ec__document_template': { - // Get the eConsents ID's from the study or the manifest - // econsent template properties are namespaced ec__, rather than c_ - const ecProp = property === 'c_study' ? 'ec__study' : property - ids = (await this.getObjectIDsArray(org, key, ecProp, values)).map(v => v._id) - // Load the manifest for the current ID's and their dependencies - objectAndDependencies = await this.getConsentManifestEntities(org, ids, orgReferenceProps) + if (!excludeTemplates) { + // Get the eConsents ID's from the study or the manifest + // econsent template properties are namespaced ec__, rather than c_ + const ecProp = property === 'c_study' ? 'ec__study' : property + ids = (await this.getObjectIDsArray(org, key, ecProp, values)).map(v => v._id) + // Load the manifest for the current ID's and their dependencies + objectAndDependencies = await this.getConsentManifestEntities(org, ids, orgReferenceProps) + } break } case 'c_visit_schedule': { @@ -788,7 +794,7 @@ class StudyManifestTools { } } // Push the deconstructed object - manifestEntities.push(...objectAndDependencies) + manifestEntities.push(...(objectAndDependencies || [])) } return manifestEntities diff --git a/packages/mdctl-cli/tasks/study.js b/packages/mdctl-cli/tasks/study.js index 852c8d4a..351cb379 100644 --- a/packages/mdctl-cli/tasks/study.js +++ b/packages/mdctl-cli/tasks/study.js @@ -50,6 +50,10 @@ class Study extends Task { preserveTemplateStatus: { type: 'boolean', default: false + }, + excludeTemplates: { + type: 'boolean', + default: false } } @@ -84,14 +88,15 @@ class Study extends Task { const client = await cli.getApiClient({ credentials: await cli.getAuthOptions() }), params = await cli.getArguments(this.optionKeys), studyTools = new StudyManifestTools(client, params), - manifestObj = params.manifestObject + manifestObj = params.manifestObject, + excludeTemplates = !!params.excludeTemplates try { let manifestJSON if (manifestObj) { manifestJSON = this.validateManifest(manifestObj, studyTools.getAvailableObjectNames()) } - const { manifest } = await studyTools.getStudyManifest(manifestJSON) + const { manifest } = await studyTools.getStudyManifest(manifestJSON, excludeTemplates) if (!params.manifestOnly) { const options = { @@ -104,7 +109,7 @@ class Study extends Task { } console.log('Study Export finished...!') - + return manifest } catch (e) { throw e @@ -278,7 +283,7 @@ class Study extends Task { Usage: - mdctl study [command] --manifestObject + mdctl study [command] Arguments: @@ -296,6 +301,8 @@ class Study extends Task { can be exported through "mdctl env export" command --preserveTemplateStatus - If set, keep template status as is while importing + + --excludeTemplates - for study export: exclude eTemplates from manifest and export folder. Default false. Notes diff --git a/packages/mdctl-cli/test/tasks/study.js b/packages/mdctl-cli/test/tasks/study.js index c4a010bf..3bf8869f 100644 --- a/packages/mdctl-cli/test/tasks/study.js +++ b/packages/mdctl-cli/test/tasks/study.js @@ -1,7 +1,128 @@ -// eslint-disable-next-line import/no-extraneous-dependencies +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable no-unused-vars */ const { assert, expect } = require('chai'), + sinon = require('sinon'), { StudyManifestTools } = require('@medable/mdctl-axon-tools'), - Study = require('../../tasks/study') + MdCtlCli = require('../../mdctl'), + Study = require('../../tasks/study'), + existingStudy = { + _id: '1', + c_name: 'Study', + c_key: 'abc' + }, + entities = [{ + _id: '615bcd016631cc0100d2766c', + object: 'c_study', + c_key: 'key-001' + }, + { + _id: '619aaaafe44c6e01003f7313', + object: 'c_task', + c_key: 'key-003' + }, + { + _id: '61981246ca9563010037bfa8', + object: 'c_task', + c_key: 'key-004' + }, + { + _id: '61981246ca95714c14e61a8c', + object: 'c_step', + c_key: 'key-005' + }, + { + _id: '61981246ca966caef6108f28', + object: 'c_step', + c_key: 'key-006' + }, + { + _id: '61981246ca9592ee0e41a3dd', + object: 'ec__document_template', + ec__key: 'key-007' + }, + { + _id: '61980eb292466ea32e087378', + object: 'ec__document_template', + ec__key: 'key-008' + }, + { + _id: '6d525cf2e328e7300d97c399', + object: 'ec__default_document_css', + c_key: 'key-009' + }, + { + _id: '6d525cfe328e64ac0833baef', + object: 'ec__knowledge_check', + c_key: 'key-010' + }, + { + _id: '6d525f2e328e7f1e48262523', + object: 'ec__knowledge_check', + c_key: 'key-011' + }, + { + _id: '6d525gbed28e7f1e4826bb76', + object: 'c_visit_schedule', + c_key: 'key-012' + }, + { + _id: '6d525gc1408e7f1e4826bb11', + object: 'c_visit', + c_key: 'key-013' + }, + { + _id: '6d525gbe28e7fc4ff43c310', + object: 'c_group', + c_key: 'key-014' + }, + { + _id: '67725gbe28e7f98ee3c8667', + object: 'c_group_task', + c_key: 'key-015' + }], + dummyReferences = [ + { + name: 'c_study', + array: false, + object: 'c_study', + type: 'Reference', + required: false + } + ], + org = { + objects: { + c_study: { + readOne: () => ({ + execute: () => ({ + hasNext: true, + next: existingStudy + }) + }) + }, + c_task: { + find: () => ({ + limit: () => ({ + toArray: () => entities.filter(e => e.object === 'c_task') + }) + }) + }, + ec__document_template: { + find: () => ({ + limit: () => ({ + toArray: () => entities.filter(e => e.object === 'ec__document_template') + }) + }) + }, + object: { + find: () => ({ + paths: () => ({ + toArray: () => [{ uniqueKey: 'c_key' }] + }) + }) + } + } + } + let study, studyManifest @@ -132,3 +253,119 @@ describe('MIG-85 - Test partial migrations in study.js module', () => { }) }) + +describe('MIG-156 - Test eTemplate exclusion flag in study.js module', () => { + beforeEach(() => { + sinon.stub(MdCtlCli.prototype, 'getAuthOptions').returns({ env: 'local', endpoint: 'https://api.local.medable.com' }) + sinon.stub(MdCtlCli.prototype, 'getApiClient').returns({}) + study = new Study() + studyManifest = new StudyManifestTools() + }) + + afterEach(() => { + sinon.restore() + }) + + it('Test study export include templates', async() => { + const manifestEntitiesToCompare = { + object: 'manifest', + dependencies: false, + exportOwner: false, + importOwner: false, + c_study: { + includes: ['key-001'], + defer: [ + 'c_public_group', + 'c_default_subject_site', + 'c_default_subject_visit_schedule', + 'c_default_subject_group', + 'c_default_participant_schedule', + 'c_menu_config.c_group_id' + ] + }, + c_task: { includes: ['key-003', 'key-004'] }, + c_step: { includes: ['key-005', 'key-006'] }, + c_visit_schedule: { includes: ['key-012'] }, + c_visit: { includes: ['key-013'] }, + c_group: { includes: ['key-014'] }, + c_group_task: { includes: ['key-015'] } + }, + exportableObject = studyManifest.getAvailableObjectNames(), + keyName = 'c_key', + cli = new MdCtlCli() + + sinon.stub(MdCtlCli.prototype, 'getArguments').returns(({ excludeTemplates: false, manifestOnly: true })) + sinon.stub(StudyManifestTools.prototype, 'getExportableObjects').returns(exportableObject) + sinon.stub(StudyManifestTools.prototype, 'getExportObjects').returns([]) + sinon.stub(StudyManifestTools.prototype, 'getFirstStudy').returns(existingStudy) + sinon.stub(StudyManifestTools.prototype, 'getMappings').callsFake(() => '') + sinon.stub(StudyManifestTools.prototype, 'getOrgObjectInfo').callsFake(() => dummyReferences) + sinon.stub(StudyManifestTools.prototype, 'getKeyName').callsFake(key => ((key === 'ec__document_template') ? 'ec__key' : keyName)) + sinon.stub(StudyManifestTools.prototype, 'getTaskManifestEntities').callsFake(() => entities.filter(o => ['c_task', 'c_step', 'c_branch'].includes(o.object))) + sinon.stub(StudyManifestTools.prototype, 'getConsentManifestEntities').callsFake(() => entities.filter(o => ['ec__document_template', 'ec__default_document_css', 'ec__knowledge_check'].includes(o.object))) + sinon.stub(StudyManifestTools.prototype, 'getObjectIDsArray').callsFake((_org, key, property, values) => entities.filter(o => o.object === key)) + sinon.stub(StudyManifestTools.prototype, 'validateReferences').callsFake(() => ({ outputEntities: entities.filter(o => !['ec__document_template', 'ec__default_document_css', 'ec__knowledge_check'].includes(o.object)), removedEntities: {} })) + sinon.stub(StudyManifestTools.prototype, 'mapObjectNameToPlural').callsFake(key => `${key}s`) + sinon.stub(StudyManifestTools.prototype, 'writeToDisk').callsFake(() => {}) + + // eslint-disable-next-line one-var + const res = await study['study@export'](cli) + + expect(res) + .to.deep.equal(manifestEntitiesToCompare) + + }) + + it('Test study export exclude eTemplates', async() => { + const manifestEntitiesToCompare = { + object: 'manifest', + dependencies: false, + exportOwner: false, + importOwner: false, + c_study: { + includes: ['key-001'], + defer: [ + 'c_public_group', + 'c_default_subject_site', + 'c_default_subject_visit_schedule', + 'c_default_subject_group', + 'c_default_participant_schedule', + 'c_menu_config.c_group_id' + ] + }, + c_task: { includes: ['key-003', 'key-004'] }, + c_step: { includes: ['key-005', 'key-006'] }, + ec__document_template: { includes: ['key-007', 'key-008'] }, + ec__default_document_css: { includes: ['key-009'] }, + ec__knowledge_check: { includes: ['key-010', 'key-011'] }, + c_visit_schedule: { includes: ['key-012'] }, + c_visit: { includes: ['key-013'] }, + c_group: { includes: ['key-014'] }, + c_group_task: { includes: ['key-015'] } + }, + exportableObject = studyManifest.getAvailableObjectNames(), + keyName = 'c_key', + cli = new MdCtlCli() + + sinon.stub(MdCtlCli.prototype, 'getArguments').returns(({ excludeTemplates: true, manifestOnly: true })) + sinon.stub(StudyManifestTools.prototype, 'getExportableObjects').returns(exportableObject) + sinon.stub(StudyManifestTools.prototype, 'getExportObjects').returns([]) + sinon.stub(StudyManifestTools.prototype, 'getFirstStudy').returns(existingStudy) + sinon.stub(StudyManifestTools.prototype, 'getMappings').callsFake(() => '') + sinon.stub(StudyManifestTools.prototype, 'getOrgObjectInfo').callsFake(() => dummyReferences) + sinon.stub(StudyManifestTools.prototype, 'getKeyName').callsFake(key => ((key === 'ec__document_template') ? 'ec__key' : keyName)) + sinon.stub(StudyManifestTools.prototype, 'getTaskManifestEntities').callsFake(() => entities.filter(o => ['c_task', 'c_step', 'c_branch'].includes(o.object))) + sinon.stub(StudyManifestTools.prototype, 'getConsentManifestEntities').callsFake(() => entities.filter(o => ['ec__document_template', 'ec__default_document_css', 'ec__knowledge_check'].includes(o.object))) + sinon.stub(StudyManifestTools.prototype, 'getObjectIDsArray').callsFake((_org, key, property, values) => entities.filter(o => o.object === key)) + sinon.stub(StudyManifestTools.prototype, 'validateReferences').callsFake(() => ({ outputEntities: entities, removedEntities: {} })) + sinon.stub(StudyManifestTools.prototype, 'mapObjectNameToPlural').callsFake(key => `${key}s`) + sinon.stub(StudyManifestTools.prototype, 'writeToDisk').callsFake(() => {}) + + // eslint-disable-next-line one-var + const res = await study['study@export'](cli) + + expect(res) + .to.deep.equal(manifestEntitiesToCompare) + + }) +})