From 39bd90b7f9d62a09665d57438202f4be0cd0cae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominique=20J=C3=A4ggi?= Date: Sun, 15 Dec 2024 10:26:51 +0100 Subject: [PATCH] chore: test --- .../src/v2/models/base/base.collection.js | 29 ++- .../src/v2/models/base/base.model.js | 5 +- .../src/v2/models/base/index.d.ts | 1 + .../src/v2/models/base/schema.js | 41 ++++- .../src/v2/util/util.js | 3 + .../test/unit/v2/models/base/schema.test.js | 170 ++++++++++++++++++ 6 files changed, 228 insertions(+), 21 deletions(-) create mode 100644 packages/spacecat-shared-data-access/test/unit/v2/models/base/schema.test.js diff --git a/packages/spacecat-shared-data-access/src/v2/models/base/base.collection.js b/packages/spacecat-shared-data-access/src/v2/models/base/base.collection.js index f2c4f16c..d439174f 100755 --- a/packages/spacecat-shared-data-access/src/v2/models/base/base.collection.js +++ b/packages/spacecat-shared-data-access/src/v2/models/base/base.collection.js @@ -11,7 +11,10 @@ */ import { - hasText, isNonEmptyObject, isNumber, isObject, + hasText, + isNonEmptyObject, + isNumber, + isObject, } from '@adobe/spacecat-shared-utils'; import { ElectroValidationError } from 'electrodb'; @@ -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; @@ -153,17 +155,10 @@ 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: @@ -171,8 +166,8 @@ class BaseCollection { // 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'); diff --git a/packages/spacecat-shared-data-access/src/v2/models/base/base.model.js b/packages/spacecat-shared-data-access/src/v2/models/base/base.model.js index dd047d76..1d08c2e8 100755 --- a/packages/spacecat-shared-data-access/src/v2/models/base/base.model.js +++ b/packages/spacecat-shared-data-access/src/v2/models/base/base.model.js @@ -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]; diff --git a/packages/spacecat-shared-data-access/src/v2/models/base/index.d.ts b/packages/spacecat-shared-data-access/src/v2/models/base/index.d.ts index 8c289801..bc94417b 100644 --- a/packages/spacecat-shared-data-access/src/v2/models/base/index.d.ts +++ b/packages/spacecat-shared-data-access/src/v2/models/base/index.d.ts @@ -64,6 +64,7 @@ export interface Schema { getEntityName(): string; getIdName(): string; getIndexes(): object; + getIndexKeys(indexName: string): string[]; getModelClass(): object; getModelName(): string; getReferences(): Reference[]; diff --git a/packages/spacecat-shared-data-access/src/v2/models/base/schema.js b/packages/spacecat-shared-data-access/src/v2/models/base/schema.js index 25ba63b0..c81f5895 100644 --- a/packages/spacecat-shared-data-access/src/v2/models/base/schema.js +++ b/packages/spacecat-shared-data-access/src/v2/models/base/schema.js @@ -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 { @@ -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} [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() { diff --git a/packages/spacecat-shared-data-access/src/v2/util/util.js b/packages/spacecat-shared-data-access/src/v2/util/util.js index df2dde7a..3ccc5d36 100644 --- a/packages/spacecat-shared-data-access/src/v2/util/util.js +++ b/packages/spacecat-shared-data-access/src/v2/util/util.js @@ -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) => { @@ -66,6 +68,7 @@ export { incrementVersion, isNonEmptyArray, keyNamesToIndexName, + keyNamesToMethodName, modelNameToEntityName, sanitizeIdAndAuditFields, sanitizeTimestamps, diff --git a/packages/spacecat-shared-data-access/test/unit/v2/models/base/schema.test.js b/packages/spacecat-shared-data-access/test/unit/v2/models/base/schema.test.js new file mode 100644 index 00000000..e6b419d4 --- /dev/null +++ b/packages/spacecat-shared-data-access/test/unit/v2/models/base/schema.test.js @@ -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, + }); + }); + }); +});