Skip to content

Commit

Permalink
fix: missing methods
Browse files Browse the repository at this point in the history
  • Loading branch information
solaris007 committed Dec 26, 2024
1 parent d54ad4b commit 9170e6e
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/

import type {
BaseCollection, BaseModel, LatestAudit, Opportunity, Site,
BaseCollection, BaseModel, LatestAudit, Opportunity, QueryOptions, Site,
} from '../index';

export interface Audit extends BaseModel {
Expand All @@ -33,7 +33,11 @@ export interface Audit extends BaseModel {

export interface AuditCollection extends BaseCollection<Audit> {
allBySiteId(siteId: string): Promise<Audit[]>;
allBySiteIdAndAuditType(siteId: string, auditType: string): Promise<Audit[]>;
allBySiteIdAndAuditType(
siteId: string,
auditType: string,
options?: QueryOptions
): Promise<Audit[]>;
allBySiteIdAndAuditTypeAndAuditedAt(
siteId: string, auditType: string, auditedAt: string
): Promise<Audit[]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,23 @@ class BaseCollection {
return this.#createInstance(record?.data);
}

/**
* Checks if an entity exists by its ID.
* @param {string} id - The UUID of the entity to check.
* @return {Promise<boolean>} - A promise that resolves to true if the entity exists,
* otherwise false.
* @throws {ValidationError} - Throws an error if the ID is not provided.
*/
async existsById(id) {
guardId(this.idName, id, this.entityName);

const record = await this.entity.get({ [this.idName]: id }).go({
attributes: [this.idName],
});

return isNonEmptyObject(record?.data);
}

/**
* Finds a single entity by index keys.
* @param {Object} keys - The index keys to use for the query.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface BaseModel {
export interface QueryOptions {
index?: string;
limit?: number;
sort?: string;
order?: string;
attributes?: string[];
}

Expand All @@ -42,6 +42,7 @@ export interface BaseCollection<T extends BaseModel> {
allByIndexKeys(keys: object, options?: QueryOptions): Promise<T[]>;
create(item: object): Promise<T>;
createMany(items: object[], parent?: T): Promise<MultiStatusCreateResult<T>>;
existsById(id: string): Promise<boolean>;
findByAll(sortKeys?: object, options?: QueryOptions): Promise<T> | null;
findById(id: string): Promise<T> | null;
findByIndexKeys(indexKeys: object): Promise<T>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/

import type {
Audit, BaseCollection, BaseModel, Opportunity, Site,
Audit, BaseCollection, BaseModel, Opportunity, QueryOptions, Site,
} from '../index';

export interface LatestAudit extends BaseModel {
Expand All @@ -32,7 +32,7 @@ export interface LatestAudit extends BaseModel {
export interface LatestAuditCollection extends BaseCollection<LatestAudit> {
allByAuditId(auditId: string): Promise<LatestAudit[]>;
allByAuditIdAndAuditType(auditId: string, auditType: string): Promise<LatestAudit[]>;
allByAuditType(auditType: string): Promise<LatestAudit[]>;
allByAuditType(auditType: string, options?: QueryOptions): Promise<LatestAudit[]>;
allBySiteId(siteId: string): Promise<LatestAudit[]>;
allBySiteIdAndAuditType(siteId: string, auditType: string): Promise<LatestAudit[]>;
findByAuditId(auditId: string): Promise<LatestAudit | null>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export interface Site extends BaseModel {
getKeyEvents(): Promise<KeyEvent[]>
getKeyEventsByTimestamp(timestamp: string): Promise<KeyEvent[]>
getLatestAudit(): Promise<LatestAudit>;
getLatestAuditByAuditType(auditType: string): Promise<LatestAudit>;
getLatestAudits(): Promise<LatestAudit>;
getLatestAuditByAuditType(auditType: string): Promise<LatestAudit>;
getOpportunities(): Promise<Opportunity[]>;
Expand Down Expand Up @@ -74,6 +73,7 @@ export interface SiteCollection extends BaseCollection<Organization> {
allByDeliveryType(deliveryType: string): Promise<Site[]>;
allByOrganizationId(organizationId: string): Promise<Site[]>;
allSitesToAudit(): Promise<string[]>;
allWithLatestAudit(auditType: string, order?: string, deliveryType?: string): Promise<Site[]>;
findByBaseURL(baseURL: string): Promise<Site | null>;
findByDeliveryType(deliveryType: string): Promise<Site | null>;
findByOrganizationId(organizationId: string): Promise<Site | null>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
* governing permissions and limitations under the License.
*/

import { hasText } from '@adobe/spacecat-shared-utils';

import DataAccessError from '../../errors/data-access.error.js';
import BaseCollection from '../base/base.collection.js';

import { DELIVERY_TYPES } from './site.model.js';

/**
* SiteCollection - A collection class responsible for managing Site entities.
* Extends the BaseCollection to provide specific methods for interacting with Site records.
Expand All @@ -23,6 +28,47 @@ class SiteCollection extends BaseCollection {
async allSitesToAudit() {
return (await this.all({ attributes: ['siteId'] })).map((site) => site.getId());
}

async allWithLatestAudit(auditType, order = 'asc', deliveryType = null) {
if (!hasText(auditType)) {
throw new DataAccessError('auditType is required', this);
}

const latestAuditCollection = this.entityRegistry.getCollection('LatestAuditCollection');

const sitesQuery = Object.values(DELIVERY_TYPES)
.includes(deliveryType)
? this.allByDeliveryType(deliveryType)
: this.all();

const [sites, latestAudits] = await Promise.all([
sitesQuery,
latestAuditCollection.all([auditType], { order }),
]);

const sitesMap = new Map(sites.map((site) => [site.getId(), site]));
const orderedSites = [];

// First, append sites with a latest audit in the sorted order
latestAudits.forEach((audit) => {
const site = sitesMap.get(audit.getSiteId());
if (site) {
// eslint-disable-next-line no-underscore-dangle
site._accessorCache.getLatestAuditByAuditType = audit;
orderedSites.push(site);
sitesMap.delete(site.getId()); // Remove the site from the map to avoid adding it again
}
});

// Then, append the remaining sites (without a latest audit)
sitesMap.forEach((site) => {
// eslint-disable-next-line no-underscore-dangle,no-param-reassign
site._accessorCache.getLatestAuditByAuditType = null;
orderedSites.push(site);
});

return orderedSites;
}
}

export default SiteCollection;
166 changes: 118 additions & 48 deletions packages/spacecat-shared-data-access/test/it/site/site.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,124 @@ describe('Site IT', async () => {
expect(site.getId()).to.equal(sampleData.sites[0].getId());
});

it('returns true when a site exists by id', async () => {
const exists = await Site.existsById(sampleData.sites[0].getId());
expect(exists).to.be.true;
});

it('returns false when a site does not exist by id', async () => {
const exists = await Site.existsById('adddd03e-bde1-4340-88ef-904070457745');
expect(exists).to.be.false;
});

it('gets all audits for a site', async () => {
const site = await Site.findById(sampleData.sites[1].getId());
const audits = await site.getAudits();

expect(audits).to.be.an('array');
expect(audits.length).to.equal(10);

for (let i = 0; i < audits.length; i += 1) {
const audit = audits[i];

expect(audit.getId()).to.be.a('string');
expect(audit.getSiteId()).to.equal(site.getId());
}
});

it('gets all audits for a site by type', async () => {
const site = await Site.findById(sampleData.sites[1].getId());
const audits = await site.getAuditsByAuditType('cwv');

expect(audits).to.be.an('array');
expect(audits.length).to.equal(5);

for (let i = 0; i < audits.length; i += 1) {
const audit = audits[i];

expect(audit.getId()).to.be.a('string');
expect(audit.getSiteId()).to.equal(site.getId());
expect(audit.getAuditType()).to.equal('cwv');
}
});

it('gets all audits for a site by type and auditAt', async () => {
const site = await Site.findById(sampleData.sites[1].getId());
const audits = await site.getAuditsByAuditTypeAndAuditedAt('cwv', '2024-12-03T08:00:55.754Z');

expect(audits).to.be.an('array');
expect(audits.length).to.equal(5);

for (let i = 0; i < audits.length; i += 1) {
const audit = audits[i];

expect(audit.getId()).to.be.a('string');
expect(audit.getSiteId()).to.equal(site.getId());
expect(audit.getAuditType()).to.equal('cwv');
expect(audit.getAuditedAt()).to.equal('2024-12-03T08:00:55.754Z');
}
});

it('gets latest audit for a site', async () => {
const site = await Site.findById(sampleData.sites[1].getId());
const audit = await site.getLatestAudit();

expect(audit.getId()).to.be.a('string');
expect(audit.getSiteId()).to.equal(site.getId());
});

it('gets latest audit for a site by type', async () => {
const site = await Site.findById(sampleData.sites[1].getId());
const audit = await site.getLatestAuditByAuditType('cwv');

expect(audit.getId()).to.be.a('string');
expect(audit.getSiteId()).to.equal(site.getId());
expect(audit.getAuditType()).to.equal('cwv');
});

it('gets all latest audits for a site', async () => {
const site = await Site.findById(sampleData.sites[1].getId());
const audits = await site.getLatestAudits();

expect(audits).to.be.an('array');
expect(audits.length).to.equal(2);

for (let i = 0; i < audits.length; i += 1) {
const audit = audits[i];

expect(audit.getId()).to.be.a('string');
expect(audit.getSiteId()).to.equal(site.getId());
}
});

it('gets all sites with latest audit by type', async () => {
const sites = await Site.allWithLatestAudit('cwv');

expect(sites).to.be.an('array');
expect(sites.length).to.equal(10);

const siteWithoutAudits = await Site.findById('5d6d4439-6659-46c2-b646-92d110fa5a52');
await checkSite(siteWithoutAudits);
await expect(siteWithoutAudits.getLatestAuditByAuditType('cwv')).to.eventually.be.null;

for (let i = 0; i < 10; i += 1) {
// eslint-disable-next-line no-loop-func
const site = sites[i];
if (site.getId() === siteWithoutAudits.getId()) {
// eslint-disable-next-line no-continue
continue;
}

await checkSite(site);

const audit = await site.getLatestAuditByAuditType('cwv');

expect(audit).to.be.an('object');
expect(audit.getSiteId()).to.equal(site.getId());
expect(audit.getAuditType()).to.equal('cwv');
}
});

it('adds a new site', async () => {
const newSiteData = {
baseURL: 'https://newexample.com',
Expand Down Expand Up @@ -230,52 +348,4 @@ describe('Site IT', async () => {
const notFound = await Site.findById(sampleData.sites[0].getId());
expect(notFound).to.be.null;
});

it('gets all audits for a site', async () => {
const site = await Site.findById(sampleData.sites[1].getId());
const audits = await site.getAudits();

expect(audits).to.be.an('array');
expect(audits.length).to.equal(10);

for (let i = 0; i < audits.length; i += 1) {
const audit = audits[i];

expect(audit.getId()).to.be.a('string');
expect(audit.getSiteId()).to.equal(site.getId());
}
});

it('gets all audits for a site by type', async () => {
const site = await Site.findById(sampleData.sites[1].getId());
const audits = await site.getAuditsByAuditType('cwv');

expect(audits).to.be.an('array');
expect(audits.length).to.equal(5);

for (let i = 0; i < audits.length; i += 1) {
const audit = audits[i];

expect(audit.getId()).to.be.a('string');
expect(audit.getSiteId()).to.equal(site.getId());
expect(audit.getAuditType()).to.equal('cwv');
}
});

it('gets all audits for a site by type and auditAt', async () => {
const site = await Site.findById(sampleData.sites[1].getId());
const audits = await site.getAuditsByAuditTypeAndAuditedAt('cwv', '2024-12-03T08:00:55.754Z');

expect(audits).to.be.an('array');
expect(audits.length).to.equal(5);

for (let i = 0; i < audits.length; i += 1) {
const audit = audits[i];

expect(audit.getId()).to.be.a('string');
expect(audit.getSiteId()).to.equal(site.getId());
expect(audit.getAuditType()).to.equal('cwv');
expect(audit.getAuditedAt()).to.equal('2024-12-03T08:00:55.754Z');
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,31 @@ describe('BaseCollection', () => {
});
});

describe('existsById', () => {
it('returns true if entity exists', async () => {
const mockFindResult = { data: mockRecord };
mockElectroService.entities.mockEntityModel.get.returns(
{ go: () => Promise.resolve(mockFindResult) },
);

const result = await baseCollectionInstance.existsById('ef39921f-9a02-41db-b491-02c98987d956');

expect(result).to.be.true;
expect(mockElectroService.entities.mockEntityModel.get.calledOnce).to.be.true;
});

it('returns false if entity does not exist', async () => {
mockElectroService.entities.mockEntityModel.get.returns(
{ go: () => Promise.resolve(null) },
);

const result = await baseCollectionInstance.existsById('ef39921f-9a02-41db-b491-02c98987d956');

expect(result).to.be.false;
expect(mockElectroService.entities.mockEntityModel.get.calledOnce).to.be.true;
});
});

describe('findByIndexKeys', () => {
it('throws error if keys is not provided', async () => {
await expect(baseCollectionInstance.findByIndexKeys())
Expand Down
Loading

0 comments on commit 9170e6e

Please sign in to comment.