diff --git a/packages/spacecat-shared-data-access/src/v2/models/opportunity.collection.js b/packages/spacecat-shared-data-access/src/v2/models/opportunity.collection.js index 03b6012c..b8bae8fa 100644 --- a/packages/spacecat-shared-data-access/src/v2/models/opportunity.collection.js +++ b/packages/spacecat-shared-data-access/src/v2/models/opportunity.collection.js @@ -69,6 +69,22 @@ class OpportunityCollection extends BaseCollection { return this.findByIndexKeys({ siteId, status }); } + + async allBySiteIdAndTypeAndStatus(siteId, type, status) { + if (!hasText(siteId)) { + throw new Error('SiteId is required'); + } + + if (!hasText(type)) { + throw new Error('Type is required'); + } + + if (!hasText(status)) { + throw new Error('Status is required'); + } + + return this.findByIndexKeys({ siteId, type, status }); + } } export default OpportunityCollection; diff --git a/packages/spacecat-shared-data-access/src/v2/schema/opportunity.schema.js b/packages/spacecat-shared-data-access/src/v2/schema/opportunity.schema.js index 251ae83f..52ad9421 100644 --- a/packages/spacecat-shared-data-access/src/v2/schema/opportunity.schema.js +++ b/packages/spacecat-shared-data-access/src/v2/schema/opportunity.schema.js @@ -138,6 +138,17 @@ const OpportunitySchema = { composite: ['updatedAt'], }, }, + bySiteIdAndTypeAndStatus: { + index: 'spacecat-data-opportunity-by-site-and-type-and-status', + pk: { + field: 'gsi3pk', + composite: ['siteId', 'type', 'status'], + }, + sk: { + field: 'gsi3sk', + composite: ['updatedAt'], + }, + }, }, }; diff --git a/packages/spacecat-shared-data-access/test/it/v2/index.test.js b/packages/spacecat-shared-data-access/test/it/v2/index.test.js index fe6ccd38..4a9952d6 100755 --- a/packages/spacecat-shared-data-access/test/it/v2/index.test.js +++ b/packages/spacecat-shared-data-access/test/it/v2/index.test.js @@ -98,6 +98,8 @@ const removeElectroProperties = (record) => { /* eslint-disable no-underscore-da delete cleanedRecord.gsi1sk; delete cleanedRecord.gsi2pk; delete cleanedRecord.gsi2sk; + delete cleanedRecord.gsi3pk; + delete cleanedRecord.gsi3sk; delete cleanedRecord.__edb_e__; delete cleanedRecord.__edb_v__; @@ -309,6 +311,8 @@ describe('Opportunity & Suggestion IT', function () { delete record.gsi1sk; delete record.gsi2pk; delete record.gsi2sk; + delete record.gsi3pk; + delete record.gsi3sk; // eslint-disable-next-line no-underscore-dangle delete record.__edb_e__; // eslint-disable-next-line no-underscore-dangle @@ -498,6 +502,8 @@ describe('Opportunity & Suggestion IT', function () { delete record.gsi1sk; delete record.gsi2pk; delete record.gsi2sk; + delete record.gsi3pk; + delete record.gsi3sk; // eslint-disable-next-line no-underscore-dangle delete record.__edb_e__; // eslint-disable-next-line no-underscore-dangle diff --git a/packages/spacecat-shared-data-access/test/unit/v2/models/opportunity.collection.test.js b/packages/spacecat-shared-data-access/test/unit/v2/models/opportunity.collection.test.js index d7c5fa02..57d49375 100644 --- a/packages/spacecat-shared-data-access/test/unit/v2/models/opportunity.collection.test.js +++ b/packages/spacecat-shared-data-access/test/unit/v2/models/opportunity.collection.test.js @@ -38,6 +38,7 @@ const mockElectroService = { query: { bySiteId: stub(), bySiteIdAndStatus: stub(), + bySiteIdAndTypeAndStatus: stub(), }, put: stub(), }, @@ -151,4 +152,42 @@ describe('OpportunityCollection', () => { .to.be.rejectedWith('Status is required'); }); }); + + describe('allBySiteIdAndTypeAndStatus', () => { + it('returns an array of Opportunity instances when opportunities exist', async () => { + const mockFindResults = { data: [mockRecord] }; + mockElectroService.entities.opportunity.query.bySiteIdAndTypeAndStatus.returns( + { go: () => Promise.resolve(mockFindResults) }, + ); + + const results = await opportunityCollectionInstance.allBySiteIdAndTypeAndStatus('site67890', 'TYPE', 'IN_PROGRESS'); + expect(results).to.be.an('array').that.has.length(1); + expect(results[0]).to.be.instanceOf(Opportunity); + expect(results[0].record).to.deep.include(mockOpportunityModel.record); + }); + + it('returns an empty array if no opportunities exist for the given site ID, type and status', async () => { + mockElectroService.entities.opportunity.query.bySiteIdAndTypeAndStatus.returns( + { go: () => Promise.resolve([]) }, + ); + + const results = await opportunityCollectionInstance.allBySiteIdAndTypeAndStatus('site67890', 'TYPE', 'IN_PROGRESS'); + expect(results).to.be.an('array').that.is.empty; + }); + + it('throws an error if siteId is not provided', async () => { + await expect(opportunityCollectionInstance.allBySiteIdAndTypeAndStatus('', 'TYPE', 'IN_PROGRESS')) + .to.be.rejectedWith('SiteId is required'); + }); + + it('throws an error if type is not provided', async () => { + await expect(opportunityCollectionInstance.allBySiteIdAndTypeAndStatus('site67890', '', 'IN_PROGRESS')) + .to.be.rejectedWith('Type is required'); + }); + + it('throws an error if status is not provided', async () => { + await expect(opportunityCollectionInstance.allBySiteIdAndTypeAndStatus('site67890', 'TYPE', '')) + .to.be.rejectedWith('Status is required'); + }); + }); });