diff --git a/src/controller/cve-id.controller/cve-id.controller.js b/src/controller/cve-id.controller/cve-id.controller.js index e33b86411..2289b0362 100644 --- a/src/controller/cve-id.controller/cve-id.controller.js +++ b/src/controller/cve-id.controller/cve-id.controller.js @@ -40,8 +40,8 @@ async function getFilteredCveId (req, res, next) { // Create map of orgUUID to shortnames and users to simplify aggregation later // Only project the fields needed for the maps to avoid fetching full documents - const orgs = await orgRepo.getAllOrgs({}, { UUID: 1, short_name: 1, _id: 0 }) - const users = await userRepo.getAllUsers({}, { UUID: 1, username: 1, org_UUID: 1, _id: 0 }) + const orgs = await orgRepo.getCveIdMapOrgs() + const users = await userRepo.getCveIdMapUsers() const orgMap = {} const userMap = {} @@ -694,7 +694,7 @@ async function nonSequentialReservation (year, amount, shortName, orgShortName, } } - available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit }) // get available ids + available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit, lean: true }) // get available ids // Case 1: Not enough IDs in the 'AVAILABLE' pool if (available.length < availableLimit) { @@ -708,7 +708,7 @@ async function nonSequentialReservation (year, amount, shortName, orgShortName, } await allocateAvailableCveIds(result.ids, year, req) // Pool was incremented. Create 'AVAILABLE' cve ids. - available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit }) // get available ids + available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit, lean: true }) // get available ids } // Case 2: Enough IDs in the 'AVAILABLE' pool @@ -734,7 +734,7 @@ async function nonSequentialReservation (year, amount, shortName, orgShortName, available.splice(index, 1) // remove reserved cve id from the 'AVAILABLE' pool counter++ } else { - available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit }) // get available ids + available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit, lean: true }) // get available ids availableLimit = Math.max(3 * (amount - counter), CONSTANTS.DEFAULT_AVAILABLE_POOL) // recalculate the available limit since some ids might have been reserved // Case 1: Not enough IDs in the 'AVAILABLE' pool @@ -750,7 +750,7 @@ async function nonSequentialReservation (year, amount, shortName, orgShortName, } await allocateAvailableCveIds(result.ids, year, req) // Pool was incremented. Create 'AVAILABLE' cve ids. - available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit }) // get available ids + available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit, lean: true }) // get available ids } } } diff --git a/src/repositories/auditRepository.js b/src/repositories/auditRepository.js index 5ba66f691..e6b24e361 100644 --- a/src/repositories/auditRepository.js +++ b/src/repositories/auditRepository.js @@ -80,7 +80,7 @@ class AuditRepository extends BaseRepository { return null } const query = { target_uuid: org.UUID } - return this.collection.findOne(query, null, options) + return this.collection.findOne(query, null, options).lean() } /** @@ -88,7 +88,7 @@ class AuditRepository extends BaseRepository { */ async findOneByTargetUUID (targetUUID, options = {}) { const query = { target_uuid: targetUUID } - const auditObject = await Audit.findOne(query, null, options) + const auditObject = await Audit.findOne(query, null, options).lean() return auditObject } @@ -97,22 +97,21 @@ class AuditRepository extends BaseRepository { */ async findOneByUUID (auditUUID, options = {}) { const query = { uuid: auditUUID } - return this.collection.findOne(query, null, options) + return this.collection.findOne(query, null, options).lean() } /** * Find all audit documents */ async findAllAuditDocuments (options = {}) { - const audits = await Audit.find({}, null, options) - return audits.map(audit => audit.toObject()) + return Audit.find({}, null, options).lean() } /** * Get the last X changes for a target UUID */ async getLastXChanges (targetUUID, numberOfChanges, options = {}) { - const audit = await Audit.findOne({ target_uuid: targetUUID }, null, options) + const audit = await Audit.findOne({ target_uuid: targetUUID }, null, options).lean() if (!audit || !audit.history || audit.history.length === 0) { return [] } diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index 8585ef2aa..573075d37 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -150,6 +150,45 @@ function getOrgProjection (isSecretariat = false) { return projection } +function applyLeanRegistryOrgDefaults (org) { + const defaultArrayFields = [ + 'aliases', + 'authority', + 'users', + 'admins', + 'private_contacts', + 'advisory_locations' + ] + + defaultArrayFields.forEach(field => { + if (!Array.isArray(org[field])) { + org[field] = [] + } + }) + + if (!org.contact_info || typeof org.contact_info !== 'object') { + org.contact_info = {} + } + if (!Array.isArray(org.contact_info.websites)) { + org.contact_info.websites = [] + } + if (!Array.isArray(org.contact_info.emails)) { + org.contact_info.emails = [] + } + + return org +} + +function applyLeanLegacyOrgDefaults (org) { + if (!org.authority || typeof org.authority !== 'object' || Array.isArray(org.authority)) { + org.authority = {} + } + if (!Array.isArray(org.authority.active_roles)) { + org.authority.active_roles = [] + } + return org +} + function filterOrg (orgObj, isSecretariat = false, applyResponseMask = false, fieldsToPreserve = []) { const CONSTANTS = getConstants() const _ = require('lodash') @@ -197,7 +236,8 @@ class BaseOrgRepository extends BaseRepository { const OrgRepository = require('./orgRepository') const legacyOrgRepo = new OrgRepository() if (returnLegacyFormat) return await legacyOrgRepo.findOneByShortName(shortName, options, projection) - const data = await BaseOrgModel.findOne({ short_name: shortName }, projection, options) + const query = BaseOrgModel.findOne({ short_name: shortName }, projection, options) + const data = await (options.lean ? query.lean() : query) return data } @@ -215,7 +255,8 @@ class BaseOrgRepository extends BaseRepository { const OrgRepository = require('./orgRepository') const legacyOrgRepo = new OrgRepository() if (returnLegacyFormat) return await legacyOrgRepo.findOneByUUID(UUID, options, projection) - return await BaseOrgModel.findOne({ UUID: UUID }, projection, options) + const query = BaseOrgModel.findOne({ UUID: UUID }, projection, options) + return await (options.lean ? query.lean() : query) } /** @@ -257,7 +298,7 @@ class BaseOrgRepository extends BaseRepository { { users: { $in: userUUIDs } }, { _id: 0, UUID: 1, short_name: 1, users: 1 }, options - ) + ).lean() } /** @@ -311,7 +352,7 @@ class BaseOrgRepository extends BaseRepository { async orgExists (shortName, options = {}, returnLegacyFormat = false) { if (!shortName) return false const query = { short_name: exactCaseInsensitiveRegex(shortName) } - const exists = await BaseOrgModel.findOne(query, null, options) + const exists = await BaseOrgModel.findOne(query, { _id: 1 }, options).lean() if (exists) { return true } @@ -349,7 +390,7 @@ class BaseOrgRepository extends BaseRepository { ] } - const collisionOrg = await BaseOrgModel.findOne(query, 'short_name long_name aliases', options) + const collisionOrg = await BaseOrgModel.findOne(query, 'short_name long_name aliases', options).lean() if (collisionOrg) { // Determine which string collided for better error reporting for (const str of searchStrings) { @@ -594,10 +635,12 @@ class BaseOrgRepository extends BaseRepository { const { deepRemoveEmpty } = require('../utils/utils') const projection = getOrgProjection(isSecretariat) const data = identifierIsUUID - ? await this.findOneByUUID(identifier, options, returnLegacyFormat, projection) - : await this.findOneByShortName(identifier, options, returnLegacyFormat, projection) + ? await this.findOneByUUID(identifier, { ...options, lean: true }, returnLegacyFormat, projection) + : await this.findOneByShortName(identifier, { ...options, lean: true }, returnLegacyFormat, projection) if (!data) return null - const result = data.toObject() + const result = returnLegacyFormat + ? applyLeanLegacyOrgDefaults(data) + : applyLeanRegistryOrgDefaults(data) const parentOrg = await BaseOrgModel.findOne({ oversees: result.UUID }).select('UUID').lean() if (parentOrg) { @@ -1268,11 +1311,8 @@ class BaseOrgRepository extends BaseRepository { * @returns {Promise} True if the organization is a Secretariat, false otherwise. */ async isSecretariatByShortName (shortname, options = {}, isLegacyObject = false) { - const org = await BaseOrgModel.findOne({ short_name: shortname }, null, options) - if (org.authority.includes('SECRETARIAT')) { - return true - } - return false + const org = await BaseOrgModel.findOne({ short_name: shortname }, 'authority', options).lean() + return Array.isArray(org?.authority) && org.authority.includes('SECRETARIAT') } /** @@ -1297,11 +1337,8 @@ class BaseOrgRepository extends BaseRepository { * @returns {Promise} True if the organization is a Bulk Download provider, false otherwise. */ async isBulkDownloadByShortname (orgShortname, options = {}, isLegacyObject = false) { - const org = await BaseOrgModel.findOne({ short_name: orgShortname }, null, options) - if (org.authority.includes('BULK_DOWNLOAD')) { - return true - } - return false + const org = await BaseOrgModel.findOne({ short_name: orgShortname }, 'authority', options).lean() + return Array.isArray(org?.authority) && org.authority.includes('BULK_DOWNLOAD') } /** diff --git a/src/repositories/baseRepository.js b/src/repositories/baseRepository.js index 9ba588f54..bb65f1931 100644 --- a/src/repositories/baseRepository.js +++ b/src/repositories/baseRepository.js @@ -45,13 +45,10 @@ class BaseRepository { if (count) { return results.countDocuments().exec() - } else if (lean) { - return results.lean().exec() - } else if (limit) { - return results.limit(limit).exec() - } else { - return results.exec() } + if (limit) results.limit(limit) + if (lean) results.lean() + return results.exec() } async findOne (query = {}) { diff --git a/src/repositories/baseUserRepository.js b/src/repositories/baseUserRepository.js index ae84f9764..4160a8bbc 100644 --- a/src/repositories/baseUserRepository.js +++ b/src/repositories/baseUserRepository.js @@ -87,7 +87,7 @@ class BaseUserRepository extends BaseRepository { * @returns {Promise} True if the organization has the user, false otherwise. */ async orgHasUserByUUID (orgShortName, uuid, options = {}, isLegacyObject = false) { - const org = await BaseOrgModel.findOne({ short_name: orgShortName }, null, options) + const org = await BaseOrgModel.findOne({ short_name: orgShortName }, 'users', options).lean() if (!org || !Array.isArray(org.users)) { return false } @@ -108,13 +108,13 @@ class BaseUserRepository extends BaseRepository { */ async orgHasUser (orgShortName, username, options = {}, isLegacyObject = false) { // 1. Find the org - const org = await BaseOrgModel.findOne({ short_name: orgShortName }, null, options) + const org = await BaseOrgModel.findOne({ short_name: orgShortName }, 'users', options).lean() if (!org || !Array.isArray(org.users)) { return false } // 2. Check if a user with this username exists in the org - const user = await BaseUser.findOne({ username, UUID: { $in: org.users } }, null, options) + const user = await BaseUser.findOne({ username, UUID: { $in: org.users } }, { _id: 1 }, options).lean() return !!user } @@ -213,7 +213,7 @@ class BaseUserRepository extends BaseRepository { { UUID: { $in: uuids } }, { _id: 0, UUID: 1, username: 1, name: 1 }, options - ) + ).lean() } /** @@ -231,7 +231,7 @@ class BaseUserRepository extends BaseRepository { return false } - const org = await BaseOrgModel.findOne({ UUID: orgUUID }, null, options).select('admins users') + const org = await BaseOrgModel.findOne({ UUID: orgUUID }, null, options).select('admins users').lean() if (!org) { return false } @@ -326,8 +326,8 @@ class BaseUserRepository extends BaseRepository { * @returns {Promise} An array of user UUIDs. */ async findUsersByOrgShortname (shortName, options = {}) { - const org = await BaseOrgModel.findOne({ short_name: shortName }, null, options) - return org.users + const org = await BaseOrgModel.findOne({ short_name: shortName }, 'users', options).lean() + return Array.isArray(org?.users) ? org.users : [] } /** diff --git a/src/repositories/conversationRepository.js b/src/repositories/conversationRepository.js index 7835990b6..82c902733 100644 --- a/src/repositories/conversationRepository.js +++ b/src/repositories/conversationRepository.js @@ -59,8 +59,8 @@ class ConversationRepository extends BaseRepository { posted_at: 1, UUID: 1 } - }) - return conversations.map(convo => convo.toObject()).filter(conv => isSecretariat || conv.visibility === 'public').map(conv => { + }).lean() + return conversations.filter(conv => isSecretariat || conv.visibility === 'public').map(conv => { normalizeConversationAuthorName(conv) if (!isSecretariat && conv.author_role === 'Secretariat') { delete conv.author_id @@ -77,7 +77,7 @@ class ConversationRepository extends BaseRepository { posted_at: 1, UUID: 1 } - }).skip(index).limit(1) + }).skip(index).limit(1).lean() return conversation[0] } diff --git a/src/repositories/cveIdRepository.js b/src/repositories/cveIdRepository.js index 797dcae23..8db3b5072 100644 --- a/src/repositories/cveIdRepository.js +++ b/src/repositories/cveIdRepository.js @@ -7,7 +7,7 @@ class CveIdRepository extends BaseRepository { } async findOneByCveId (id) { - return this.collection.findOne().byCveId(id) + return this.collection.findOne().byCveId(id).lean() } async updateByCveId (id, cveIdObj, options = {}) { diff --git a/src/repositories/cveRepository.js b/src/repositories/cveRepository.js index 34a1451fb..7b48f79ea 100644 --- a/src/repositories/cveRepository.js +++ b/src/repositories/cveRepository.js @@ -7,7 +7,7 @@ class CveRepository extends BaseRepository { } async findOneByCveId (id) { - const results = this.collection.findOne().byCveId(id) + const results = this.collection.findOne().byCveId(id).lean() return results } diff --git a/src/repositories/glossaryRepository.js b/src/repositories/glossaryRepository.js index 3ff74abe4..e69bfc0e9 100644 --- a/src/repositories/glossaryRepository.js +++ b/src/repositories/glossaryRepository.js @@ -7,11 +7,11 @@ class GlossaryRepository extends BaseRepository { } async getAll () { - return this.collection.find({}, { _id: 0, __v: 0, createdAt: 0, updatedAt: 0 }).exec() + return this.collection.find({}, { _id: 0, __v: 0, createdAt: 0, updatedAt: 0 }).lean().exec() } async findOneByServicesShortName (servicesShortName) { - return this.collection.findOne({ services_short_name: servicesShortName }, { _id: 0, __v: 0, createdAt: 0, updatedAt: 0 }).exec() + return this.collection.findOne({ services_short_name: servicesShortName }, { _id: 0, __v: 0, createdAt: 0, updatedAt: 0 }).lean().exec() } async updateByServicesShortName (servicesShortName, newGlossaryData) { diff --git a/src/repositories/orgRepository.js b/src/repositories/orgRepository.js index 48f47ee93..00666a893 100644 --- a/src/repositories/orgRepository.js +++ b/src/repositories/orgRepository.js @@ -9,11 +9,13 @@ class OrgRepository extends BaseRepository { async findOneByShortName (shortName, options = {}, projection = {}) { const query = { short_name: shortName } - return this.collection.findOne(query, projection, options) + const result = this.collection.findOne(query, projection, options) + return options.lean ? result.lean() : result } async findOneByUUID (UUID, options = {}, projection = {}) { - return this.collection.findOne({ UUID: UUID }, projection, options) + const result = this.collection.findOne({ UUID: UUID }, projection, options) + return options.lean ? result.lean() : result } async getOrgUUID (shortName, options = {}) { @@ -51,6 +53,10 @@ class OrgRepository extends BaseRepository { return this.collection.find({}, projection, options) } + async getCveIdMapOrgs (options = {}) { + return this.collection.find({}, { _id: 0, UUID: 1, short_name: 1 }, options).lean() + } + async deleteOneByShortName (shortName, options = {}) { return this.collection.deleteOne({ short_name: shortName }, options) } diff --git a/src/repositories/reviewObjectRepository.js b/src/repositories/reviewObjectRepository.js index 921c2d5b5..5fab8a1f4 100644 --- a/src/repositories/reviewObjectRepository.js +++ b/src/repositories/reviewObjectRepository.js @@ -41,9 +41,9 @@ class ReviewObjectRepository extends BaseRepository { const conversationRepository = new ConversationRepository() let reviewObject const query = pending ? { uuid: UUID, status: 'pending' } : { uuid: UUID } - const reviewObjectRaw = await ReviewObjectModel.findOne(query, { _id: 0, __v: 0 }, options) + const reviewObjectRaw = await ReviewObjectModel.findOne(query, { _id: 0, __v: 0 }, options).lean() if (reviewObjectRaw) { - reviewObject = reviewObjectRaw.toObject() + reviewObject = reviewObjectRaw const conversations = await conversationRepository.getAllByTargetUUID(reviewObject.target_object_uuid, isSecretariat, options) reviewObject.conversation = conversations?.length ? conversations : undefined reviewObject.new_review_data = filterReviewOrgData(reviewObject.new_review_data, isSecretariat) @@ -112,9 +112,9 @@ class ReviewObjectRepository extends BaseRepository { ...options, sort: { created: -1 } } - ) + ).lean() if (reviewObjectRaw) { - reviewObject = reviewObjectRaw.toObject() + reviewObject = reviewObjectRaw const conversations = await conversationRepository.getAllByTargetUUID(org.UUID, isSecretariat, options) reviewObject.conversation = conversations?.length ? conversations : undefined reviewObject.new_review_data = filterReviewOrgData(reviewObject.new_review_data, isSecretariat) @@ -142,9 +142,9 @@ class ReviewObjectRepository extends BaseRepository { ...options, sort: { created: -1 } } - ) + ).lean() if (reviewObjectRaw) { - reviewObject = reviewObjectRaw.toObject() + reviewObject = reviewObjectRaw const conversations = await conversationRepository.getAllByTargetUUID(org.UUID, isSecretariat, options) reviewObject.conversation = conversations?.length ? conversations : undefined reviewObject.new_review_data = filterReviewOrgData(reviewObject.new_review_data, isSecretariat) diff --git a/src/repositories/userRepository.js b/src/repositories/userRepository.js index 346710878..74afa451b 100644 --- a/src/repositories/userRepository.js +++ b/src/repositories/userRepository.js @@ -64,6 +64,10 @@ class UserRepository extends BaseRepository { async getAllUsers (options = {}, projection = {}) { return this.collection.find({}, projection, options) } + + async getCveIdMapUsers (options = {}) { + return this.collection.find({}, { _id: 0, UUID: 1, username: 1, org_UUID: 1 }, options).lean() + } } module.exports = UserRepository diff --git a/test/integration-tests/cve-id/reserveCveIdTest.js b/test/integration-tests/cve-id/reserveCveIdTest.js index 42e8f58d6..16db34b0e 100644 --- a/test/integration-tests/cve-id/reserveCveIdTest.js +++ b/test/integration-tests/cve-id/reserveCveIdTest.js @@ -2,17 +2,49 @@ const chai = require('chai') chai.use(require('chai-http')) +const sinon = require('sinon') const expect = chai.expect const constants = require('../constants.js') const app = require('../../../src/index.js') +const BaseRepository = require('../../../src/repositories/baseRepository.js') const requestLength = 10 describe('Testing Reserve CVE-ID Endpoints', () => { // beforeEach(() => { }) context('Positive Tests', () => { + it('Reserves an available CVE-ID through the endpoint when the pool query uses limit and lean', async () => { + const findSpy = sinon.spy(BaseRepository.prototype, 'find') + let availablePoolCall + + try { + const res = await chai.request(app) + .post('/api/cve-id?amount=1&cve_year=2023&short_name=mitre&batch_type=non-sequential') + .set(constants.headers) + + availablePoolCall = findSpy.getCalls().find(call => ( + call.args[0]?.cve_year === '2023' && + call.args[0]?.state === 'AVAILABLE' + )) + + expect(res).to.have.status(200) + expect(res.body.cve_ids).to.have.length(1) + expect(res.body.cve_ids[0]).to.include({ + cve_year: '2023', + state: 'RESERVED' + }) + expect(res.body.cve_ids[0]).to.have.property('cve_id') + } finally { + findSpy.restore() + } + + expect(availablePoolCall).to.not.be.undefined + expect(availablePoolCall.args[1]).to.include({ lean: true }) + expect(availablePoolCall.args[1].limit).to.be.a('number').and.to.be.greaterThan(0) + }) + it('Should return 200 and have correct number of cve-id results for a successful non-squential cve-id reservation ', (done) => { chai.request(app) .post(`/api/cve-id?amount=${requestLength}&cve_year=2023&short_name=mitre&batch_type=non-sequential`) diff --git a/test/integration-tests/middleware/authenticatedContextTest.js b/test/integration-tests/middleware/authenticatedContextTest.js index 6ad751346..3cf908765 100644 --- a/test/integration-tests/middleware/authenticatedContextTest.js +++ b/test/integration-tests/middleware/authenticatedContextTest.js @@ -155,13 +155,13 @@ describe('Authenticated request context middleware integration', () => { return false } - async getAllOrgs () { + async getCveIdMapOrgs () { return [legacyAuthenticatedOrg] } } class LegacyUserRepo { - async getAllUsers () { + async getCveIdMapUsers () { return [] } } diff --git a/test/integration-tests/org/leanSparseOrgResponseTest.js b/test/integration-tests/org/leanSparseOrgResponseTest.js new file mode 100644 index 000000000..c478e2197 --- /dev/null +++ b/test/integration-tests/org/leanSparseOrgResponseTest.js @@ -0,0 +1,69 @@ +/* eslint-disable no-unused-expressions */ + +const chai = require('chai') +chai.use(require('chai-http')) +const expect = chai.expect + +const app = require('../../../src/index') +const BaseOrg = require('../../../src/model/baseorg') +const Org = require('../../../src/model/org') +const constants = require('../constants') + +const sparseOrgUUID = 'b88b6a9c-1a8b-4e5d-a1d8-6a8c48b98c4d' +const sparseOrgShortName = 'lean_sparse_org' + +describe('Lean sparse organization response compatibility', () => { + before(async () => { + await BaseOrg.deleteMany({ UUID: sparseOrgUUID }) + await Org.deleteMany({ UUID: sparseOrgUUID }) + + await BaseOrg.collection.insertOne({ + UUID: sparseOrgUUID, + short_name: sparseOrgShortName, + long_name: 'Lean Sparse Organization', + authority: ['CNA'], + __t: 'CNAOrg' + }) + + await Org.collection.insertOne({ + UUID: sparseOrgUUID, + short_name: sparseOrgShortName, + name: 'Lean Sparse Organization', + authority: { active_roles: ['CNA'] }, + policies: { id_quota: 100 } + }) + }) + + after(async () => { + await BaseOrg.deleteMany({ UUID: sparseOrgUUID }) + await Org.deleteMany({ UUID: sparseOrgUUID }) + }) + + it('preserves registry empty-array defaults for a sparse organization', async () => { + const res = await chai.request(app) + .get(`/api/registry/org/${sparseOrgShortName}`) + .set(constants.headers) + + expect(res).to.have.status(200) + expect(res.body.authority).to.deep.equal(['CNA']) + expect(res.body.aliases).to.deep.equal([]) + expect(res.body.users).to.deep.equal([]) + expect(res.body.admins).to.deep.equal([]) + expect(res.body.private_contacts).to.deep.equal([]) + expect(res.body.advisory_locations).to.deep.equal([]) + expect(res.body.contact_info).to.deep.equal({ websites: [], emails: [] }) + }) + + it('preserves legacy authority without injecting registry fields', async () => { + const res = await chai.request(app) + .get(`/api/org/${sparseOrgShortName}`) + .set(constants.headers) + + expect(res).to.have.status(200) + expect(res.body.authority).to.deep.equal({ active_roles: ['CNA'] }) + expect(res.body).to.not.have.property('aliases') + expect(res.body).to.not.have.property('users') + expect(res.body).to.not.have.property('admins') + expect(res.body).to.not.have.property('contact_info') + }) +}) diff --git a/test/unit-tests/conversation/conversationRepositoryTest.js b/test/unit-tests/conversation/conversationRepositoryTest.js index 5865edeed..d8cab9b19 100644 --- a/test/unit-tests/conversation/conversationRepositoryTest.js +++ b/test/unit-tests/conversation/conversationRepositoryTest.js @@ -60,9 +60,9 @@ describe('Testing Conversation Repository', () => { }) it('normalizes stored Secretariat author names when conversations are returned to Secretariat', async () => { - sinon.stub(ConversationModel, 'find').resolves([ - { - toObject: () => ({ + sinon.stub(ConversationModel, 'find').returns({ + lean: sinon.stub().resolves([ + { UUID: 'conversation-uuid', target_uuid: 'target-uuid', author_id: 'secretariat-user-uuid', @@ -70,9 +70,9 @@ describe('Testing Conversation Repository', () => { author_role: 'Secretariat', visibility: 'public', body: 'Existing Secretariat comment' - }) - } - ]) + } + ]) + }) const repo = new ConversationRepository() const result = await repo.getAllByTargetUUID('target-uuid', true) @@ -106,9 +106,9 @@ describe('Testing Conversation Repository', () => { }) it('continues stripping Secretariat author fields when conversations are returned to non-Secretariat', async () => { - sinon.stub(ConversationModel, 'find').resolves([ - { - toObject: () => ({ + sinon.stub(ConversationModel, 'find').returns({ + lean: sinon.stub().resolves([ + { UUID: 'conversation-uuid', target_uuid: 'target-uuid', author_id: 'secretariat-user-uuid', @@ -116,9 +116,9 @@ describe('Testing Conversation Repository', () => { author_role: 'Secretariat', visibility: 'public', body: 'Existing Secretariat comment' - }) - } - ]) + } + ]) + }) const repo = new ConversationRepository() const result = await repo.getAllByTargetUUID('target-uuid', false) diff --git a/test/unit-tests/cve-id/cveIdGetAllTest.js b/test/unit-tests/cve-id/cveIdGetAllTest.js index 2946f871c..bd7228bbf 100644 --- a/test/unit-tests/cve-id/cveIdGetAllTest.js +++ b/test/unit-tests/cve-id/cveIdGetAllTest.js @@ -108,10 +108,10 @@ describe('Testing getFilteredCveId function', () => { sandbox.stub(orgRepo, 'getOrgUUID').returns(stubOrg.UUID) sandbox.stub(orgRepo, 'isSecretariat').returns(true) sandbox.stub(orgRepo, 'isBulkDownload').returns(false) - sandbox.stub(orgRepo, 'getAllOrgs').returns([stubOrg, stubOrg2]) + sandbox.stub(orgRepo, 'getCveIdMapOrgs').returns([stubOrg, stubOrg2]) sandbox.stub(userRepo, 'getUserUUID').returns(stubUser.UUID) - sandbox.stub(userRepo, 'getAllUsers').returns([stubUser]) + sandbox.stub(userRepo, 'getCveIdMapUsers').returns([stubUser]) sandbox.spy(cveIdController, 'CVEID_GET_FILTER') @@ -145,8 +145,8 @@ describe('Testing getFilteredCveId function', () => { it('Should request only the fields needed to build the org and user maps', async () => { await cveIdController.CVEID_GET_FILTER(req, res, next) - expect(orgRepo.getAllOrgs.calledOnceWith({}, { UUID: 1, short_name: 1, _id: 0 })).to.equal(true) - expect(userRepo.getAllUsers.calledOnceWith({}, { UUID: 1, username: 1, org_UUID: 1, _id: 0 })).to.equal(true) + expect(orgRepo.getCveIdMapOrgs.calledOnceWithExactly()).to.equal(true) + expect(userRepo.getCveIdMapUsers.calledOnceWithExactly()).to.equal(true) }) it('Should swap UUIDs for names in Cve-ids', async () => { diff --git a/test/unit-tests/org/baseOrgRepositoryTest.js b/test/unit-tests/org/baseOrgRepositoryTest.js index cad1297d7..f14244ac0 100644 --- a/test/unit-tests/org/baseOrgRepositoryTest.js +++ b/test/unit-tests/org/baseOrgRepositoryTest.js @@ -11,7 +11,7 @@ describe('Testing BaseOrgRepository', () => { }) it('Checks org existence without using $expr', async () => { - const findOne = sinon.stub(BaseOrgModel, 'findOne').resolves(null) + const findOne = sinon.stub(BaseOrgModel, 'findOne').returns({ lean: sinon.stub().resolves(null) }) const repo = new BaseOrgRepository() const options = { session: 'session' } @@ -28,7 +28,7 @@ describe('Testing BaseOrgRepository', () => { }) it('Checks alias collisions without using $expr', async () => { - const findOne = sinon.stub(BaseOrgModel, 'findOne').resolves(null) + const findOne = sinon.stub(BaseOrgModel, 'findOne').returns({ lean: sinon.stub().resolves(null) }) const repo = new BaseOrgRepository() const collision = await repo.checkAliasCollisions( diff --git a/test/unit-tests/repository/baseRepositoryLeanTest.js b/test/unit-tests/repository/baseRepositoryLeanTest.js new file mode 100644 index 000000000..8fd0d95a2 --- /dev/null +++ b/test/unit-tests/repository/baseRepositoryLeanTest.js @@ -0,0 +1,69 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const BaseRepository = require('../../../src/repositories/baseRepository') + +function createQuery (result) { + return { + limit: sinon.stub().returnsThis(), + lean: sinon.stub().returnsThis(), + exec: sinon.stub().resolves(result), + countDocuments: sinon.stub() + } +} + +describe('BaseRepository.find', () => { + it('applies both limit and lean before executing a collection query', async () => { + const query = createQuery([{ cve_id: 'CVE-2026-1' }]) + const model = { find: sinon.stub().returns(query) } + const repository = new BaseRepository(model) + + const result = await repository.find( + { state: 'AVAILABLE' }, + { limit: 10, lean: true } + ) + + expect(result).to.deep.equal([{ cve_id: 'CVE-2026-1' }]) + expect(model.find.calledOnceWithExactly({ state: 'AVAILABLE' })).to.equal(true) + expect(query.limit.calledOnceWithExactly(10)).to.equal(true) + expect(query.lean.calledOnce).to.equal(true) + expect(query.exec.calledOnce).to.equal(true) + }) + + it('applies lean to single-document queries', async () => { + const query = createQuery({ cve_id: 'CVE-2026-1' }) + const model = { findOne: sinon.stub().returns(query) } + const repository = new BaseRepository(model) + + const result = await repository.find( + { cve_id: 'CVE-2026-1' }, + { multiple: false, lean: true } + ) + + expect(result).to.deep.equal({ cve_id: 'CVE-2026-1' }) + expect(model.findOne.calledOnceWithExactly({ cve_id: 'CVE-2026-1' })).to.equal(true) + expect(query.limit.called).to.equal(false) + expect(query.lean.calledOnce).to.equal(true) + expect(query.exec.calledOnce).to.equal(true) + }) + + it('uses the count query without applying document query modifiers', async () => { + const countExec = sinon.stub().resolves(3) + const query = createQuery([]) + query.countDocuments.returns({ exec: countExec }) + const model = { find: sinon.stub().returns(query) } + const repository = new BaseRepository(model) + + const result = await repository.find( + { state: 'AVAILABLE' }, + { count: true, limit: 10, lean: true } + ) + + expect(result).to.equal(3) + expect(query.countDocuments.calledOnce).to.equal(true) + expect(countExec.calledOnce).to.equal(true) + expect(query.limit.called).to.equal(false) + expect(query.lean.called).to.equal(false) + expect(query.exec.called).to.equal(false) + }) +}) diff --git a/test/unit-tests/repository/leanRepositoryQueriesTest.js b/test/unit-tests/repository/leanRepositoryQueriesTest.js new file mode 100644 index 000000000..582716cb2 --- /dev/null +++ b/test/unit-tests/repository/leanRepositoryQueriesTest.js @@ -0,0 +1,367 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const Audit = require('../../../src/model/audit') +const BaseOrg = require('../../../src/model/baseorg') +const BaseUser = require('../../../src/model/baseuser') +const Conversation = require('../../../src/model/conversation') +const Cve = require('../../../src/model/cve') +const CveId = require('../../../src/model/cve-id') +const Glossary = require('../../../src/model/glossary') +const Org = require('../../../src/model/org') +const ReviewObject = require('../../../src/model/reviewobject') +const User = require('../../../src/model/user') + +const AuditRepository = require('../../../src/repositories/auditRepository') +const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository') +const BaseUserRepository = require('../../../src/repositories/baseUserRepository') +const ConversationRepository = require('../../../src/repositories/conversationRepository') +const CveIdRepository = require('../../../src/repositories/cveIdRepository') +const CveRepository = require('../../../src/repositories/cveRepository') +const GlossaryRepository = require('../../../src/repositories/glossaryRepository') +const OrgRepository = require('../../../src/repositories/orgRepository') +const ReviewObjectRepository = require('../../../src/repositories/reviewObjectRepository') +const UserRepository = require('../../../src/repositories/userRepository') + +function leanQuery (result) { + return { + lean: sinon.stub().resolves(result) + } +} + +function selectLeanQuery (result) { + const query = leanQuery(result) + query.select = sinon.stub().returns(query) + return query +} + +function paginatedLeanQuery (result) { + const query = leanQuery(result) + query.skip = sinon.stub().returns(query) + query.limit = sinon.stub().returns(query) + return query +} + +describe('Lean repository queries', () => { + afterEach(() => { + sinon.restore() + }) + + it('returns plain CVE and CVE-ID lookups through their custom query helpers', async () => { + const cveQuery = leanQuery({ cveMetadata: { cveId: 'CVE-2026-1' } }) + cveQuery.byCveId = sinon.stub().returns(cveQuery) + const cveIdQuery = leanQuery({ cve_id: 'CVE-2026-1' }) + cveIdQuery.byCveId = sinon.stub().returns(cveIdQuery) + sinon.stub(Cve, 'findOne').returns(cveQuery) + sinon.stub(CveId, 'findOne').returns(cveIdQuery) + + await new CveRepository().findOneByCveId('CVE-2026-1') + await new CveIdRepository().findOneByCveId('CVE-2026-1') + + expect(cveQuery.byCveId.calledOnceWithExactly('CVE-2026-1')).to.equal(true) + expect(cveQuery.lean.calledOnce).to.equal(true) + expect(cveIdQuery.byCveId.calledOnceWithExactly('CVE-2026-1')).to.equal(true) + expect(cveIdQuery.lean.calledOnce).to.equal(true) + }) + + it('uses projected lean queries to load the legacy CVE-ID maps', async () => { + const orgQuery = leanQuery([]) + const userQuery = leanQuery([]) + sinon.stub(Org, 'find').returns(orgQuery) + sinon.stub(User, 'find').returns(userQuery) + + await new OrgRepository().getCveIdMapOrgs() + await new UserRepository().getCveIdMapUsers() + + expect(orgQuery.lean.calledOnce).to.equal(true) + expect(userQuery.lean.calledOnce).to.equal(true) + }) + + it('supports opt-in lean legacy organization lookups', async () => { + const shortNameQuery = leanQuery({ UUID: 'org-uuid' }) + const uuidQuery = leanQuery({ UUID: 'org-uuid' }) + const findOne = sinon.stub(Org, 'findOne') + findOne.onCall(0).returns(shortNameQuery) + findOne.onCall(1).returns(uuidQuery) + const repository = new OrgRepository() + + await repository.findOneByShortName('example', { lean: true }) + await repository.findOneByUUID('org-uuid', { lean: true }) + + expect(shortNameQuery.lean.calledOnce).to.equal(true) + expect(uuidQuery.lean.calledOnce).to.equal(true) + }) + + it('supports opt-in lean base organization lookups without changing default lookup behavior', async () => { + const shortNameQuery = leanQuery({ UUID: 'org-uuid' }) + const uuidQuery = leanQuery({ UUID: 'org-uuid' }) + const findOne = sinon.stub(BaseOrg, 'findOne') + findOne.onCall(0).returns(shortNameQuery) + findOne.onCall(1).returns(uuidQuery) + const repository = new BaseOrgRepository() + + await repository.findOneByShortName('example', { lean: true }) + await repository.findOneByUUID('org-uuid', { lean: true }) + + expect(shortNameQuery.lean.calledOnce).to.equal(true) + expect(uuidQuery.lean.calledOnce).to.equal(true) + }) + + it('uses lean for base organization map, existence, collision, and role checks', async () => { + const organizationsQuery = leanQuery([]) + const existsQuery = leanQuery(null) + const collisionQuery = leanQuery(null) + const secretariatQuery = leanQuery({ authority: ['SECRETARIAT'] }) + const bulkDownloadQuery = leanQuery({ authority: ['BULK_DOWNLOAD'] }) + sinon.stub(BaseOrg, 'find').returns(organizationsQuery) + const findOne = sinon.stub(BaseOrg, 'findOne') + findOne.onCall(0).returns(existsQuery) + findOne.onCall(1).returns(collisionQuery) + findOne.onCall(2).returns(secretariatQuery) + findOne.onCall(3).returns(bulkDownloadQuery) + const repository = new BaseOrgRepository() + + await repository.findOrgsByUserUUIDs(['user-uuid']) + await repository.orgExists('example') + await repository.checkAliasCollisions('example', 'Example', ['example-alias']) + await repository.isSecretariatByShortName('secretariat') + await repository.isBulkDownloadByShortname('bulk-download') + + expect(organizationsQuery.lean.calledOnce).to.equal(true) + expect(existsQuery.lean.calledOnce).to.equal(true) + expect(collisionQuery.lean.calledOnce).to.equal(true) + expect(secretariatQuery.lean.calledOnce).to.equal(true) + expect(bulkDownloadQuery.lean.calledOnce).to.equal(true) + }) + + it('treats a missing authority array as not Secretariat', async () => { + const secretariatQuery = leanQuery({}) + sinon.stub(BaseOrg, 'findOne').returns(secretariatQuery) + const repository = new BaseOrgRepository() + + expect(await repository.isSecretariatByShortName('sparse-org')).to.equal(false) + + expect(secretariatQuery.lean.calledOnce).to.equal(true) + }) + + it('treats a missing authority array as not Bulk Download', async () => { + const bulkDownloadQuery = leanQuery({}) + sinon.stub(BaseOrg, 'findOne').returns(bulkDownloadQuery) + const repository = new BaseOrgRepository() + + expect(await repository.isBulkDownloadByShortname('sparse-org')).to.equal(false) + + expect(bulkDownloadQuery.lean.calledOnce).to.equal(true) + }) + + it('requests a lean organization before constructing a registry organization response', async () => { + const parentQuery = selectLeanQuery(null) + sinon.stub(BaseOrg, 'findOne').returns(parentQuery) + const repository = new BaseOrgRepository() + const lookup = sinon.stub(repository, 'findOneByShortName').resolves({ + UUID: 'org-uuid', + short_name: 'example', + authority: ['CNA'] + }) + + await repository.getOrg('example') + + expect(lookup.calledOnce).to.equal(true) + expect(lookup.firstCall.args[1]).to.include({ lean: true }) + expect(parentQuery.lean.calledOnce).to.equal(true) + }) + + it('preserves hydrated empty-array defaults in sparse registry organization responses', async () => { + const sparseOrganization = { + UUID: 'org-uuid', + short_name: 'sparse-org', + authority: ['CNA'] + } + const organizationQuery = leanQuery(sparseOrganization) + const parentQuery = selectLeanQuery(null) + const findOne = sinon.stub(BaseOrg, 'findOne') + findOne.onCall(0).returns(organizationQuery) + findOne.onCall(1).returns(parentQuery) + const repository = new BaseOrgRepository() + + const result = await repository.getOrg('sparse-org', false, {}, false, true) + + expect(result.aliases).to.deep.equal([]) + expect(result.users).to.deep.equal([]) + expect(result.admins).to.deep.equal([]) + expect(result.private_contacts).to.deep.equal([]) + expect(result.advisory_locations).to.deep.equal([]) + expect(result).to.not.have.property('oversees') + expect(result.contact_info).to.deep.equal({ websites: [], emails: [] }) + }) + + it('preserves legacy authority objects without injecting registry defaults', async () => { + const legacyOrganizationQuery = leanQuery({ + UUID: 'legacy-org-uuid', + short_name: 'legacy-org', + authority: { active_roles: ['CNA'] } + }) + const parentQuery = selectLeanQuery(null) + sinon.stub(Org, 'findOne').returns(legacyOrganizationQuery) + sinon.stub(BaseOrg, 'findOne').returns(parentQuery) + const repository = new BaseOrgRepository() + + const result = await repository.getOrg('legacy-org', false, {}, true) + + expect(result.authority).to.deep.equal({ active_roles: ['CNA'] }) + expect(result).to.not.have.property('aliases') + expect(result).to.not.have.property('users') + expect(result).to.not.have.property('admins') + expect(result).to.not.have.property('contact_info') + expect(legacyOrganizationQuery.lean.calledOnce).to.equal(true) + }) + + it('preserves the hydrated legacy authority default for sparse organizations', async () => { + const legacyOrganizationQuery = leanQuery({ + UUID: 'legacy-org-uuid', + short_name: 'sparse-legacy-org' + }) + const parentQuery = selectLeanQuery(null) + sinon.stub(Org, 'findOne').returns(legacyOrganizationQuery) + sinon.stub(BaseOrg, 'findOne').returns(parentQuery) + const repository = new BaseOrgRepository() + + const result = await repository.getOrg('sparse-legacy-org', false, {}, true) + + expect(result.authority).to.deep.equal({ active_roles: [] }) + expect(legacyOrganizationQuery.lean.calledOnce).to.equal(true) + }) + + it('uses lean projected organization and user queries for base user membership checks', async () => { + const byUuidQuery = leanQuery({ users: ['user-uuid'] }) + const byUsernameOrgQuery = leanQuery({ users: ['user-uuid'] }) + const userQuery = leanQuery({ _id: 'user-id' }) + const findOne = sinon.stub(BaseOrg, 'findOne') + findOne.onCall(0).returns(byUuidQuery) + findOne.onCall(1).returns(byUsernameOrgQuery) + sinon.stub(BaseUser, 'findOne').returns(userQuery) + const repository = new BaseUserRepository() + + expect(await repository.orgHasUserByUUID('example', 'user-uuid')).to.equal(true) + expect(await repository.orgHasUser('example', 'user')).to.equal(true) + + expect(byUuidQuery.lean.calledOnce).to.equal(true) + expect(byUsernameOrgQuery.lean.calledOnce).to.equal(true) + expect(userQuery.lean.calledOnce).to.equal(true) + }) + + it('uses lean user and organization queries for base user maps and role checks', async () => { + const usersQuery = leanQuery([]) + const adminOrgQuery = selectLeanQuery({ admins: ['user-uuid'], users: [] }) + const shortNameOrgQuery = leanQuery({ users: ['user-uuid'] }) + sinon.stub(BaseUser, 'find').returns(usersQuery) + const findOne = sinon.stub(BaseOrg, 'findOne') + findOne.onCall(0).returns(adminOrgQuery) + findOne.onCall(1).returns(shortNameOrgQuery) + const repository = new BaseUserRepository() + + await repository.findUsersByUUIDs(['user-uuid']) + expect(await repository.isUserAdminOfOrgUUID('user-uuid', 'org-uuid')).to.equal(true) + expect(await repository.findUsersByOrgShortname('example')).to.deep.equal(['user-uuid']) + + expect(usersQuery.lean.calledOnce).to.equal(true) + expect(adminOrgQuery.lean.calledOnce).to.equal(true) + expect(shortNameOrgQuery.lean.calledOnce).to.equal(true) + }) + + it('returns an empty user list when a sparse organization has no users field', async () => { + const organizationQuery = leanQuery({ UUID: 'org-uuid' }) + sinon.stub(BaseOrg, 'findOne').returns(organizationQuery) + const repository = new BaseUserRepository() + + expect(await repository.findUsersByOrgShortname('sparse-org')).to.deep.equal([]) + expect(organizationQuery.lean.calledOnce).to.equal(true) + }) + + it('uses lean conversation queries for both target lists and indexed authorization lookups', async () => { + const conversationsQuery = leanQuery([]) + const indexedQuery = paginatedLeanQuery([{ UUID: 'conversation-uuid' }]) + const find = sinon.stub(Conversation, 'find') + find.onCall(0).returns(conversationsQuery) + find.onCall(1).returns(indexedQuery) + const repository = new ConversationRepository() + + await repository.getAllByTargetUUID('target-uuid', true) + expect(await repository.findByTargetUUIDAndIndex('target-uuid', 2)).to.deep.equal({ UUID: 'conversation-uuid' }) + + expect(conversationsQuery.lean.calledOnce).to.equal(true) + expect(indexedQuery.skip.calledOnceWithExactly(2)).to.equal(true) + expect(indexedQuery.limit.calledOnceWithExactly(1)).to.equal(true) + expect(indexedQuery.lean.calledOnce).to.equal(true) + }) + + it('uses lean review object queries before attaching conversation response data', async () => { + const byUuidQuery = leanQuery({ uuid: 'review-uuid', target_object_uuid: 'org-uuid', new_review_data: {} }) + const byShortNameQuery = leanQuery({ uuid: 'review-uuid', target_object_uuid: 'org-uuid', new_review_data: {} }) + const byOrgUuidQuery = leanQuery({ uuid: 'review-uuid', target_object_uuid: 'org-uuid', new_review_data: {} }) + const findOne = sinon.stub(ReviewObject, 'findOne') + findOne.onCall(0).returns(byUuidQuery) + findOne.onCall(1).returns(byShortNameQuery) + findOne.onCall(2).returns(byOrgUuidQuery) + sinon.stub(BaseOrgRepository.prototype, 'findOneByShortName').resolves({ UUID: 'org-uuid' }) + sinon.stub(BaseOrgRepository.prototype, 'findOneByUUID').resolves({ UUID: 'org-uuid' }) + sinon.stub(ConversationRepository.prototype, 'getAllByTargetUUID').resolves([]) + const repository = new ReviewObjectRepository() + + await repository.findOneByUUIDWithConversation('review-uuid', true) + await repository.getOrgReviewObjectByOrgShortname('example', true) + await repository.getOrgReviewObjectByOrgUUID('org-uuid', true) + + expect(byUuidQuery.lean.calledOnce).to.equal(true) + expect(byShortNameQuery.lean.calledOnce).to.equal(true) + expect(byOrgUuidQuery.lean.calledOnce).to.equal(true) + }) + + it('uses lean audit queries before returning audit data or sorting history', async () => { + const byShortNameQuery = leanQuery({ target_uuid: 'org-uuid' }) + const byTargetQuery = leanQuery({ target_uuid: 'org-uuid' }) + const byUuidQuery = leanQuery({ uuid: 'audit-uuid' }) + const allQuery = leanQuery([]) + const historyQuery = leanQuery({ history: [] }) + const findOne = sinon.stub(Audit, 'findOne') + findOne.onCall(0).returns(byShortNameQuery) + findOne.onCall(1).returns(byTargetQuery) + findOne.onCall(2).returns(byUuidQuery) + findOne.onCall(3).returns(historyQuery) + sinon.stub(Audit, 'find').returns(allQuery) + sinon.stub(BaseOrgRepository.prototype, 'findOneByShortName').resolves({ UUID: 'org-uuid' }) + const repository = new AuditRepository() + + await repository.findOneByOrgShortname('example') + await repository.findOneByTargetUUID('org-uuid') + await repository.findOneByUUID('audit-uuid') + await repository.findAllAuditDocuments() + await repository.getLastXChanges('org-uuid', 1) + + expect(byShortNameQuery.lean.calledOnce).to.equal(true) + expect(byTargetQuery.lean.calledOnce).to.equal(true) + expect(byUuidQuery.lean.calledOnce).to.equal(true) + expect(allQuery.lean.calledOnce).to.equal(true) + expect(historyQuery.lean.calledOnce).to.equal(true) + }) + + it('uses lean glossary queries for list and lookup responses', async () => { + const allQuery = leanQuery([]) + allQuery.exec = sinon.stub().resolves([]) + allQuery.lean.returns(allQuery) + const byShortNameQuery = leanQuery({ services_short_name: 'example' }) + byShortNameQuery.exec = sinon.stub().resolves({ services_short_name: 'example' }) + byShortNameQuery.lean.returns(byShortNameQuery) + const find = sinon.stub(Glossary, 'find').returns(allQuery) + const findOne = sinon.stub(Glossary, 'findOne').returns(byShortNameQuery) + const repository = new GlossaryRepository() + + await repository.getAll() + await repository.findOneByServicesShortName('example') + + expect(find.calledOnce).to.equal(true) + expect(findOne.calledOnce).to.equal(true) + expect(allQuery.lean.calledOnce).to.equal(true) + expect(byShortNameQuery.lean.calledOnce).to.equal(true) + }) +})