Skip to content

Commit

Permalink
chore: test
Browse files Browse the repository at this point in the history
  • Loading branch information
solaris007 committed Dec 15, 2024
1 parent 2d45ff5 commit 39bd90b
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
*/

import {
hasText, isNonEmptyObject, isNumber, isObject,
hasText,
isNonEmptyObject,
isNumber,
isObject,
} from '@adobe/spacecat-shared-utils';

import { ElectroValidationError } from 'electrodb';
Expand All @@ -20,14 +23,13 @@ import { removeElectroProperties } from '../../../../test/it/util/util.js';
import ValidationError from '../../errors/validation.error.js';
import { guardId } from '../../util/guards.js';
import {
capitalize, entityNameToAllPKValue, isNonEmptyArray, keyNamesToIndexName,
entityNameToAllPKValue,
isNonEmptyArray,
keyNamesToIndexName,
keyNamesToMethodName,
} from '../../util/util.js';
import Schema from './schema.js';

function keyNamesToMethodName(keyNames, prefix) {
return prefix + keyNames.map(capitalize).join('And');
}

function isValidParent(parent, child) {
if (!hasText(parent.entityName)) {
return false;
Expand Down Expand Up @@ -153,26 +155,19 @@ class BaseCollection {
* @private
*/
#initializeCollectionMethods() {
const indexes = this.schema.getIndexes();
const indexes = this.schema.getIndexes([Schema.INDEX_TYPES.PRIMARY]);

Object.keys(indexes).forEach((indexName) => {
if (indexName === Schema.INDEX_TYPES.PRIMARY) {
return;
}

const indexDef = indexes[indexName];
const pkKeys = Array.isArray(indexDef.pk?.facets) ? indexDef.pk.facets : [];
const skKeys = Array.isArray(indexDef.sk?.facets) ? indexDef.sk.facets : [indexDef.sk?.field];
const allKeys = [...pkKeys, ...skKeys];
const indexKeys = this.schema.getIndexKeys(indexName);

// generate a method for each prefix of the allKeys array
// for example, if allKeys = ['opportunityId', 'status'], we create:
// allByOpportunityId(...)
// findByOpportunityId(...)
// allByOpportunityIdAndStatus(...)
// findByOpportunityIdAndStatus(...)
for (let i = 1; i <= allKeys.length; i += 1) {
const subset = allKeys.slice(0, i); // prefix of keys
for (let i = 1; i <= indexKeys.length; i += 1) {
const subset = indexKeys.slice(0, i); // prefix of keys
const allMethodName = keyNamesToMethodName(subset, 'allBy');
const findMethodName = keyNamesToMethodName(subset, 'findBy');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ class BaseModel {
const capitalized = capitalize(name);
const getterMethodName = `get${capitalized}`;
const setterMethodName = `set${capitalized}`;
const isReference = this.schema.getReferences()
.belongs_to?.some((ref) => ref.target === idNameToEntityName(name));
const isReference = this.schema
.getReferencesByType(Reference.TYPES.BELONGS_TO)
.some((ref) => ref.getTarget() === idNameToEntityName(name));

if (!this[getterMethodName] || name === this.idName) {
this[getterMethodName] = () => this.record[name];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export interface Schema {
getEntityName(): string;
getIdName(): string;
getIndexes(): object;
getIndexKeys(indexName: string): string[];
getModelClass(): object;
getModelName(): string;
getReferences(): Reference[];
Expand Down
41 changes: 39 additions & 2 deletions packages/spacecat-shared-data-access/src/v2/models/base/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* governing permissions and limitations under the License.
*/

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

import { entityNameToIdName, modelNameToEntityName } from '../../util/util.js';

class Schema {
Expand Down Expand Up @@ -67,8 +69,43 @@ class Schema {
return entityNameToIdName(this.getModelName());
}

getIndexes() {
return this.indexes;
getIndexByName(indexName) {
return this.indexes[indexName];
}

/**
* Returns the indexes for the schema. By default, this returns all indexes.
* You can use the `exclude` parameter to exclude certain indexes.
* @param {Array<string>} [exclude] - One of the INDEX_TYPES values.
* @return {object} The indexes.
*/
getIndexes(exclude) {
if (!Array.isArray(exclude)) {
return this.indexes;
}

return Object.keys(this.indexes).reduce((acc, indexName) => {
const index = this.indexes[indexName];

if (!exclude.includes(indexName)) {
acc[indexName] = index;
}

return acc;
}, {});
}

getIndexKeys(indexName) {
const index = this.getIndexByName(indexName);

if (!isNonEmptyObject(index)) {
return [];
}

const pkKeys = Array.isArray(index.pk?.facets) ? index.pk.facets : [];
const skKeys = Array.isArray(index.sk?.facets) ? index.sk.facets : [index.sk?.field];

return [...pkKeys, ...skKeys];
}

getModelClass() {
Expand Down
3 changes: 3 additions & 0 deletions packages/spacecat-shared-data-access/src/v2/util/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const idNameToEntityName = (idName) => capitalize(pluralize.singular(idName.repl

const keyNamesToIndexName = (keyNames) => `by${keyNames.map(capitalize).join('And')}`;

const keyNamesToMethodName = (keyNames, prefix) => prefix + keyNames.map(capitalize).join('And');

const modelNameToEntityName = (modelName) => decapitalize(modelName);

const sanitizeTimestamps = (data) => {
Expand Down Expand Up @@ -66,6 +68,7 @@ export {
incrementVersion,
isNonEmptyArray,
keyNamesToIndexName,
keyNamesToMethodName,
modelNameToEntityName,
sanitizeIdAndAuditFields,
sanitizeTimestamps,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* eslint-env mocha */

// eslint-disable-next-line max-classes-per-file
import { expect, use as chaiUse } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import sinonChai from 'sinon-chai';

import BaseModel from '../../../../../src/v2/models/base/base.model.js';
import BaseCollection from '../../../../../src/v2/models/base/base.collection.js';
import Schema from '../../../../../src/v2/models/base/schema.js';
import Reference from '../../../../../src/v2/models/base/reference.js';

chaiUse(chaiAsPromised);
chaiUse(sinonChai);

const MockModel = class MockEntityModel extends BaseModel {};
const MockCollection = class MockEntityCollection extends BaseCollection {};

describe('Schema', () => {
const rawSchema = {
serviceName: 'service',
schemaVersion: '1.0',
attributes: {
id: { type: 'string' },
},
indexes: {
primary: { pk: { composite: ['id'] } },
byOrganizationId: { sk: { facets: ['organizationId'] } },
},
references: [new Reference('belongs_to', 'Organization')],
};

let instance;

beforeEach(() => {
instance = new Schema(MockModel, MockCollection, rawSchema);
});

describe('constructor', () => {
it('constructs a new Schema instance', () => {
const schema = new Schema(MockModel, MockCollection, rawSchema);

expect(schema.modelClass).to.equal(MockModel);
expect(schema.collectionClass).to.equal(MockCollection);
expect(schema.serviceName).to.equal('service');
expect(schema.schemaVersion).to.equal('1.0');
expect(schema.attributes).to.deep.equal({ id: { type: 'string' } });
expect(schema.indexes).to.deep.equal(rawSchema.indexes);
expect(schema.references).to.deep.equal([{
options: {},
target: 'Organization',
type: 'belongs_to',
}]);
});
});

describe('accessors', () => {
it('getAttribute', () => {
expect(instance.getAttribute('id')).to.deep.equal({ type: 'string' });
});

it('getAttributes', () => {
expect(instance.getAttributes()).to.deep.equal({ id: { type: 'string' } });
});

it('getCollectionName', () => {
expect(instance.getCollectionName()).to.equal('MockEntityCollection');
});

it('getEntityName', () => {
expect(instance.getEntityName()).to.equal('mockEntityModel');
});

it('getIdName', () => {
expect(instance.getIdName()).to.equal('mockEntityModelId');
});

it('getIndexByName', () => {
expect(instance.getIndexByName('primary')).to.deep.equal({ pk: { composite: ['id'] } });
});

it('getIndexes', () => {
expect(instance.getIndexes()).to.deep.equal(rawSchema.indexes);
});

it('getIndexes with exclusion', () => {
expect(instance.getIndexes(['primary'])).to.deep.equal({
byOrganizationId: { sk: { facets: ['organizationId'] } },
});
});

it('getIndexKeys', () => {
expect(instance.getIndexKeys('byOrganizationId')).to.deep.equal(['organizationId']);
});

it('getIndexKeys with non-existent index', () => {
expect(instance.getIndexKeys('non-existent')).to.deep.equal([]);
});

it('getModelClass', () => {
expect(instance.getModelClass()).to.equal(MockModel);
});

it('getModelName', () => {
expect(instance.getModelName()).to.equal('MockEntityModel');
});

it('getReferences', () => {
expect(instance.getReferences()).to.deep.equal([{
options: {},
target: 'Organization',
type: 'belongs_to',
}]);
});

it('getReferencesByType', () => {
expect(instance.getReferencesByType('belongs_to')).to.deep.equal([{
options: {},
target: 'Organization',
type: 'belongs_to',
}]);
});

it('getServiceName', () => {
expect(instance.getServiceName()).to.equal('service');
});

it('getVersion', () => {
expect(instance.getVersion()).to.equal('1.0');
});
});

describe('toElectroDBSchema', () => {
it('returns an ElectroDB-compatible schema', () => {
expect(instance.toElectroDBSchema()).to.deep.equal({
model: {
entity: 'MockEntityModel',
version: '1.0',
service: 'service',
},
attributes: { id: { type: 'string' } },
indexes: rawSchema.indexes,
});
});
});
});

0 comments on commit 39bd90b

Please sign in to comment.