UNPKG

@e22m4u/js-repository-mongodb-adapter

Version:
1,273 lines (1,192 loc) 210 kB
import {expect} from 'chai'; import {ObjectId} from 'mongodb'; import {MongoClient} from 'mongodb'; import {format} from '@e22m4u/js-format'; import {DataType} from '@e22m4u/js-repository'; import {createMongodbUrl} from './utils/index.js'; import {MongodbAdapter} from './mongodb-adapter.js'; import {DatabaseSchema} from '@e22m4u/js-repository'; import {AdapterRegistry} from '@e22m4u/js-repository'; import {InvalidOperatorValueError} from '@e22m4u/js-repository'; import {DEFAULT_PRIMARY_KEY_PROPERTY_NAME as DEF_PK} from '@e22m4u/js-repository'; const CONFIG = { host: process.env.MONGODB_HOST || 'localhost', port: process.env.MONGODB_PORT || 27017, database: process.env.MONGODB_DATABASE, }; const MDB_CLIENT = new MongoClient(createMongodbUrl(CONFIG)); const ADAPTERS_STACK = []; function createSchema() { const schema = new DatabaseSchema(); const adapter = new MongodbAdapter(schema.container, CONFIG); ADAPTERS_STACK.push(adapter); schema.defineDatasource({name: 'mongodb', adapter: 'mongodb'}); schema.getService(AdapterRegistry)._adapters['mongodb'] = adapter; return schema; } describe('MongodbAdapter', function () { this.timeout(15000); afterEach(async function () { await MDB_CLIENT.db(CONFIG.database).dropDatabase(); }); after(async function () { for await (const adapter of ADAPTERS_STACK) { await adapter.client.close(true); } await MDB_CLIENT.close(true); }); describe('_getCollectionNameByModelName', function () { it('converts model name to camel case and pluralizes it', async function () { const schema = createSchema(); const modelNamesToCollectionNames = [ ['camelCaseEntity', 'camelCaseEntities'], ['PascalCaseEntity', 'pascalCaseEntities'], ['snake_case_entity', 'snakeCaseEntities'], ['kebab-case-entity', 'kebabCaseEntities'], ['UPPER_SNAKE_CASE_ENTITY', 'upperSnakeCaseEntities'], ['UPPER-KEBAB-CASE-ENTITY', 'upperKebabCaseEntities'], ]; modelNamesToCollectionNames.forEach(tuple => schema.defineModel({name: tuple[0], datasource: 'mongodb'}), ); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); modelNamesToCollectionNames.forEach(tuple => expect(A._getCollectionNameByModelName(tuple[0])).to.be.eq(tuple[1]), ); }); // prettier-ignore it('cuts off the "Model" suffix from the model name', async function () { const schema = createSchema(); const modelNamesToCollectionNames = [ ['camelCaseEntityModel', 'camelCaseEntities'], ['PascalCaseEntityModel', 'pascalCaseEntities'], ['snake_case_entity_model', 'snakeCaseEntities'], ['kebab-case-entity-model', 'kebabCaseEntities'], ['UPPER_SNAKE_CASE_ENTITY_MODEL', 'upperSnakeCaseEntities'], ['UPPER-KEBAB-CASE-ENTITY-MODEL', 'upperKebabCaseEntities'], ]; modelNamesToCollectionNames.forEach(tuple => schema.defineModel({name: tuple[0], datasource: 'mongodb'}), ); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); modelNamesToCollectionNames.forEach(tuple => expect(A._getCollectionNameByModelName(tuple[0])).to.be.eq(tuple[1]), ); }); it('converts already pluralized model name to camel case', async function () { const schema = createSchema(); const modelNamesToCollectionNames = [ ['camelCaseEntities', 'camelCaseEntities'], ['PascalCaseEntities', 'pascalCaseEntities'], ['snake_case_entities', 'snakeCaseEntities'], ['kebab-case-entities', 'kebabCaseEntities'], ['UPPER_SNAKE_CASE_ENTITIES', 'upperSnakeCaseEntities'], ['UPPER-KEBAB-CASE-ENTITIES', 'upperKebabCaseEntities'], ]; modelNamesToCollectionNames.forEach(tuple => schema.defineModel({name: tuple[0], datasource: 'mongodb'}), ); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); modelNamesToCollectionNames.forEach(tuple => expect(A._getCollectionNameByModelName(tuple[0])).to.be.eq(tuple[1]), ); }); // prettier-ignore it('converts already pluralized model name to camel case and cut off the "Model" suffix', async function () { const schema = createSchema(); const modelNamesToCollectionNames = [ ['camelCaseEntitiesModel', 'camelCaseEntities'], ['PascalCaseEntitiesModel', 'pascalCaseEntities'], ['snake_case_entities_model', 'snakeCaseEntities'], ['kebab-case-entities-model', 'kebabCaseEntities'], ['UPPER_SNAKE_CASE_ENTITIES_MODEL', 'upperSnakeCaseEntities'], ['UPPER-KEBAB-CASE-ENTITIES-MODEL', 'upperKebabCaseEntities'], ]; modelNamesToCollectionNames.forEach(tuple => schema.defineModel({name: tuple[0], datasource: 'mongodb'}), ); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); modelNamesToCollectionNames.forEach(tuple => expect(A._getCollectionNameByModelName(tuple[0])).to.be.eq(tuple[1]), ); }); it('returns the value from the "tableName" option if defined', async function () { const schema = createSchema(); schema.defineModel({ name: 'fooBar', tableName: 'bazQux', datasource: 'mongodb', }); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); expect(A._getCollectionNameByModelName('fooBar')).to.be.eq('bazQux'); }); }); describe('_getCollection', function () { it('should create and return a new collection object on the first call', async function () { const schema = createSchema(); schema.defineModel({name: 'myTestModel', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); expect(A._collections.has('myTestModel')).to.be.false; const collection = A._getCollection('myTestModel'); expect(collection).to.exist; expect(collection.collectionName).to.equal('myTests'); expect(collection.dbName).to.equal(CONFIG.database); }); it('should cache the collection object after the first call', async function () { const schema = createSchema(); schema.defineModel({name: 'myTestModel', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); expect(A._collections.has('myTestModel')).to.be.false; A._getCollection('myTestModel'); expect(A._collections.has('myTestModel')).to.be.true; expect(A._collections.get('myTestModel')).to.exist; }); it('should return the cached collection instance on subsequent calls', async function () { const schema = createSchema(); schema.defineModel({name: 'myTestModel', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const collection1 = A._getCollection('myTestModel'); const collection2 = A._getCollection('myTestModel'); expect(collection2).to.equal(collection1); }); it('converts model name to camel case and pluralizes it', async function () { const schema = createSchema(); const modelNamesToCollectionNames = [ ['camelCaseEntity', 'camelCaseEntities'], ['PascalCaseEntity', 'pascalCaseEntities'], ['snake_case_entity', 'snakeCaseEntities'], ['kebab-case-entity', 'kebabCaseEntities'], ['UPPER_SNAKE_CASE_ENTITY', 'upperSnakeCaseEntities'], ['UPPER-KEBAB-CASE-ENTITY', 'upperKebabCaseEntities'], ]; const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); modelNamesToCollectionNames.forEach(tuple => { schema.defineModel({name: tuple[0], datasource: 'mongodb'}); const collection = A._getCollection(tuple[0]); expect(collection.collectionName).to.equal(tuple[1]); }); }); it('cuts off the "Model" suffix from the model name', async function () { const schema = createSchema(); const modelNamesToCollectionNames = [ ['camelCaseEntityModel', 'camelCaseEntities'], ['PascalCaseEntityModel', 'pascalCaseEntities'], ['snake_case_entity_model', 'snakeCaseEntities'], ['kebab-case-entity-model', 'kebabCaseEntities'], ['UPPER_SNAKE_CASE_ENTITY_MODEL', 'upperSnakeCaseEntities'], ['UPPER-KEBAB-CASE-ENTITY-MODEL', 'upperKebabCaseEntities'], ]; const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); modelNamesToCollectionNames.forEach(tuple => { schema.defineModel({name: tuple[0], datasource: 'mongodb'}); const collection = A._getCollection(tuple[0]); expect(collection.collectionName).to.equal(tuple[1]); }); }); it('converts already pluralized model name to camel case', async function () { const schema = createSchema(); const modelNamesToCollectionNames = [ ['camelCaseEntities', 'camelCaseEntities'], ['PascalCaseEntities', 'pascalCaseEntities'], ['snake_case_entities', 'snakeCaseEntities'], ['kebab-case-entities', 'kebabCaseEntities'], ['UPPER_SNAKE_CASE_ENTITIES', 'upperSnakeCaseEntities'], ['UPPER-KEBAB-CASE-ENTITIES', 'upperKebabCaseEntities'], ]; const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); modelNamesToCollectionNames.forEach(tuple => { schema.defineModel({name: tuple[0], datasource: 'mongodb'}); const collection = A._getCollection(tuple[0]); expect(collection.collectionName).to.equal(tuple[1]); }); }); it('converts already pluralized model name to camel case and cut off the "Model" suffix', async function () { const schema = createSchema(); const modelNamesToCollectionNames = [ ['camelCaseEntitiesModel', 'camelCaseEntities'], ['PascalCaseEntitiesModel', 'pascalCaseEntities'], ['snake_case_entities_model', 'snakeCaseEntities'], ['kebab-case-entities-model', 'kebabCaseEntities'], ['UPPER_SNAKE_CASE_ENTITIES_MODEL', 'upperSnakeCaseEntities'], ['UPPER-KEBAB-CASE-ENTITIES-MODEL', 'upperKebabCaseEntities'], ]; const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); modelNamesToCollectionNames.forEach(tuple => { schema.defineModel({name: tuple[0], datasource: 'mongodb'}); const collection = A._getCollection(tuple[0]); expect(collection.collectionName).to.equal(tuple[1]); }); }); it('uses the value from the "tableName" option if defined', async function () { const schema = createSchema(); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const customTableName = 'custom_baz_qux'; schema.defineModel({ name: 'fooBar', tableName: customTableName, datasource: 'mongodb', }); const collection = A._getCollection('fooBar'); expect(collection.collectionName).to.equal(customTableName); }); }); describe('_buildProjection', function () { describe('single field', function () { it('returns undefined if the second argument is undefined', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('model', undefined); expect(res).to.be.undefined; }); it('returns undefined if the second argument is null', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('model', null); expect(res).to.be.undefined; }); it('requires the second argument to be a non-empty string', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const throwable = v => () => A._buildProjection('model', v); const error = v => format( 'The provided option "fields" should be a non-empty String ' + 'or an Array of non-empty String, but %s given.', v, ); expect(throwable('')).to.throw(error('""')); expect(throwable(10)).to.throw(error('10')); expect(throwable(0)).to.throw(error('0')); expect(throwable(true)).to.throw(error('true')); expect(throwable(false)).to.throw(error('false')); expect(throwable({})).to.throw(error('Object')); expect(throwable('bar')()).to.be.eql({_id: 1, bar: 1}); expect(throwable(undefined)()).to.be.undefined; expect(throwable(null)()).to.be.undefined; }); it('converts the given property name to the column name', async function () { const schema = createSchema(); schema.defineModel({ name: 'model', datasource: 'mongodb', properties: { foo: { type: DataType.STRING, columnName: 'bar', }, }, }); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('model', 'foo'); expect(res).to.be.eql({_id: 1, bar: 1}); }); it('converts property names chain to column names chain', async function () { const schema = createSchema(); schema.defineModel({ name: 'modelA', datasource: 'mongodb', properties: { foo: { type: DataType.OBJECT, columnName: 'fooCol', model: 'modelB', }, }, }); schema.defineModel({ name: 'modelB', properties: { bar: { type: DataType.OBJECT, model: 'modelC', }, }, }); schema.defineModel({ name: 'modelC', properties: { baz: { type: DataType.OBJECT, columnName: 'bazCol', }, }, }); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('modelA', 'foo.bar.baz.qux'); expect(res).to.be.eql({_id: 1, 'fooCol.bar.bazCol.qux': 1}); }); it('includes "_id" field to the projection', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('model', 'foo'); expect(res).to.be.eql({_id: 1, foo: 1}); }); it('includes "_id" as a column name of the given property', async function () { const schema = createSchema(); schema.defineModel({ name: 'model', datasource: 'mongodb', properties: { foo: { type: DataType.STRING, primaryKey: true, columnName: '_id', }, }, }); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('model', 'foo'); expect(res).to.be.eql({_id: 1}); }); }); describe('multiple fields', function () { it('returns undefined if the second argument is an empty array', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('model', []); expect(res).to.be.undefined; }); it('requires the second argument to be an array of non-empty strings', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const throwable = v => () => A._buildProjection('model', v); const error = v => format( 'The provided option "fields" should be a non-empty String ' + 'or an Array of non-empty String, but %s given.', v, ); expect(throwable([''])).to.throw(error('""')); expect(throwable([10])).to.throw(error('10')); expect(throwable([0])).to.throw(error('0')); expect(throwable([true])).to.throw(error('true')); expect(throwable([false])).to.throw(error('false')); expect(throwable([{}])).to.throw(error('Object')); expect(throwable([undefined])).to.throw(error('undefined')); expect(throwable([null])).to.throw(error('null')); expect(throwable([])()).to.be.undefined; expect(throwable(['bar'])()).to.be.eql({_id: 1, bar: 1}); }); it('converts the given property names to column names', async function () { const schema = createSchema(); schema.defineModel({ name: 'model', datasource: 'mongodb', properties: { foo: { type: DataType.STRING, columnName: 'bar', }, baz: { type: DataType.STRING, columnName: 'qux', }, }, }); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('model', ['foo', 'baz']); expect(res).to.be.eql({_id: 1, bar: 1, qux: 1}); }); it('converts property names chain to column names chain', async function () { const schema = createSchema(); schema.defineModel({ name: 'modelA', datasource: 'mongodb', properties: { foo1: { type: DataType.OBJECT, columnName: 'foo1Col', model: 'modelB', }, foo2: { type: DataType.OBJECT, columnName: 'foo2Col', model: 'modelB', }, }, }); schema.defineModel({ name: 'modelB', properties: { bar1: { type: DataType.OBJECT, model: 'modelC', }, bar2: { type: DataType.OBJECT, model: 'modelC', }, }, }); schema.defineModel({ name: 'modelC', properties: { baz1: { type: DataType.OBJECT, columnName: 'baz1Col', }, baz2: { type: DataType.OBJECT, columnName: 'baz2Col', }, }, }); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('modelA', [ 'foo1.bar1.baz1.qux1', 'foo2.bar2.baz2.qux2', ]); expect(res).to.be.eql({ _id: 1, 'foo1Col.bar1.baz1Col.qux1': 1, 'foo2Col.bar2.baz2Col.qux2': 1, }); }); it('includes "_id" field to the projection', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('model', ['foo', 'bar']); expect(res).to.be.eql({_id: 1, foo: 1, bar: 1}); }); it('includes "_id" as a column name of the given property', async function () { const schema = createSchema(); schema.defineModel({ name: 'model', datasource: 'mongodb', properties: { foo: { type: DataType.STRING, primaryKey: true, columnName: '_id', }, }, }); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildProjection('model', ['foo', 'bar']); expect(res).to.be.eql({_id: 1, bar: 1}); }); }); }); describe('_buildSort', function () { describe('single field', function () { it('returns undefined if the second argument is undefined', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildSort('model', undefined); expect(res).to.be.undefined; }); it('returns undefined if the second argument is null', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildSort('model', null); expect(res).to.be.undefined; }); it('requires the second argument to be a non-empty string', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const throwable = v => () => A._buildSort('model', v); const error = v => format( 'The provided option "order" should be a non-empty String ' + 'or an Array of non-empty String, but %s given.', v, ); expect(throwable('')).to.throw(error('""')); expect(throwable(10)).to.throw(error('10')); expect(throwable(0)).to.throw(error('0')); expect(throwable(true)).to.throw(error('true')); expect(throwable(false)).to.throw(error('false')); expect(throwable({})).to.throw(error('Object')); expect(throwable('bar')()).to.be.eql({bar: 1}); expect(throwable(undefined)()).to.be.undefined; expect(throwable(null)()).to.be.undefined; }); it('uses ascending direction by default', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildSort('model', 'foo'); expect(res).to.be.eql({foo: 1}); }); it('uses descending direction by the "DESC" flag', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildSort('model', 'foo DESC'); expect(res).to.be.eql({foo: -1}); }); it('uses ascending direction by the "ASC" flag', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildSort('model', 'foo ASC'); expect(res).to.be.eql({foo: 1}); }); it('converts the given property name to the column name', async function () { const schema = createSchema(); schema.defineModel({ name: 'model', datasource: 'mongodb', properties: { foo: { type: DataType.STRING, columnName: 'bar', }, }, }); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res1 = A._buildSort('model', 'foo'); const res2 = A._buildSort('model', 'foo DESC'); const res3 = A._buildSort('model', 'foo ASC'); expect(res1).to.be.eql({bar: 1}); expect(res2).to.be.eql({bar: -1}); expect(res3).to.be.eql({bar: 1}); }); it('converts property names chain to column names chain', async function () { const schema = createSchema(); schema.defineModel({ name: 'modelA', datasource: 'mongodb', properties: { foo: { type: DataType.OBJECT, columnName: 'fooCol', model: 'modelB', }, }, }); schema.defineModel({ name: 'modelB', properties: { bar: { type: DataType.OBJECT, model: 'modelC', }, }, }); schema.defineModel({ name: 'modelC', properties: { baz: { type: DataType.OBJECT, columnName: 'bazCol', }, }, }); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res1 = A._buildSort('modelA', 'foo.bar.baz.qux'); const res2 = A._buildSort('modelA', 'foo.bar.baz.qux DESC'); const res3 = A._buildSort('modelA', 'foo.bar.baz.qux ASC'); expect(res1).to.be.eql({'fooCol.bar.bazCol.qux': 1}); expect(res2).to.be.eql({'fooCol.bar.bazCol.qux': -1}); expect(res3).to.be.eql({'fooCol.bar.bazCol.qux': 1}); }); }); describe('multiple fields', function () { it('returns undefined if the second argument is an empty array', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildSort('model', []); expect(res).to.be.undefined; }); it('requires the second argument to be an array of non-empty strings', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const throwable = v => () => A._buildSort('model', v); const error = v => format( 'The provided option "order" should be a non-empty String ' + 'or an Array of non-empty String, but %s given.', v, ); expect(throwable([''])).to.throw(error('""')); expect(throwable([10])).to.throw(error('10')); expect(throwable([0])).to.throw(error('0')); expect(throwable([true])).to.throw(error('true')); expect(throwable([false])).to.throw(error('false')); expect(throwable([{}])).to.throw(error('Object')); expect(throwable([undefined])).to.throw(error('undefined')); expect(throwable([null])).to.throw(error('null')); expect(throwable([])()).to.be.undefined; expect(throwable(['bar', 'baz'])()).to.be.eql({bar: 1, baz: 1}); }); it('uses ascending direction by default', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildSort('model', ['foo', 'bar']); expect(res).to.be.eql({foo: 1, bar: 1}); }); it('uses descending direction by the "DESC" flag', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildSort('model', ['foo DESC', 'bar DESC']); expect(res).to.be.eql({foo: -1, bar: -1}); }); it('uses ascending direction by the "ASC" flag', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildSort('model', ['foo ASC', 'bar ASC']); expect(res).to.be.eql({foo: 1, bar: 1}); }); it('uses multiple directions by the multiple fields', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res = A._buildSort('model', ['foo', 'bar DESC', 'baz ASC']); expect(res).to.be.eql({foo: 1, bar: -1, baz: 1}); }); it('converts the given property names to column names', async function () { const schema = createSchema(); schema.defineModel({ name: 'model', datasource: 'mongodb', properties: { foo: { type: DataType.STRING, columnName: 'bar', }, baz: { type: DataType.STRING, columnName: 'qux', }, }, }); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res1 = A._buildSort('model', ['foo', 'baz']); const res2 = A._buildSort('model', ['foo DESC', 'baz ASC']); const res3 = A._buildSort('model', ['foo ASC', 'baz DESC']); expect(res1).to.be.eql({bar: 1, qux: 1}); expect(res2).to.be.eql({bar: -1, qux: 1}); expect(res3).to.be.eql({bar: 1, qux: -1}); }); it('converts property names chain to column names chain', async function () { const schema = createSchema(); schema.defineModel({ name: 'modelA', datasource: 'mongodb', properties: { foo1: { type: DataType.OBJECT, columnName: 'foo1Col', model: 'modelB', }, foo2: { type: DataType.OBJECT, columnName: 'foo2Col', model: 'modelB', }, }, }); schema.defineModel({ name: 'modelB', properties: { bar1: { type: DataType.OBJECT, model: 'modelC', }, bar2: { type: DataType.OBJECT, model: 'modelC', }, }, }); schema.defineModel({ name: 'modelC', properties: { baz1: { type: DataType.OBJECT, columnName: 'baz1Col', }, baz2: { type: DataType.OBJECT, columnName: 'baz2Col', }, }, }); const A = await schema .getService(AdapterRegistry) .getAdapter('mongodb'); const res1 = A._buildSort('modelA', [ 'foo1.bar1.baz1.qux1', 'foo2.bar2.baz2.qux2', ]); const res2 = A._buildSort('modelA', [ 'foo1.bar1.baz1.qux1 DESC', 'foo2.bar2.baz2.qux2 DESC', ]); const res3 = A._buildSort('modelA', [ 'foo1.bar1.baz1.qux1 ASC', 'foo2.bar2.baz2.qux2 ASC', ]); expect(res1).to.be.eql({ 'foo1Col.bar1.baz1Col.qux1': 1, 'foo2Col.bar2.baz2Col.qux2': 1, }); expect(res2).to.be.eql({ 'foo1Col.bar1.baz1Col.qux1': -1, 'foo2Col.bar2.baz2Col.qux2': -1, }); expect(res3).to.be.eql({ 'foo1Col.bar1.baz1Col.qux1': 1, 'foo2Col.bar2.baz2Col.qux2': 1, }); }); }); }); describe('_buildQuery', function () { it('requires the second argument to be an object', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const throwable = v => () => A._buildQuery('model', v); const error = v => format( 'The provided option "where" should be an Object, but %s given.', v, ); expect(throwable('str')).to.throw(error('"str"')); expect(throwable('')).to.throw(error('""')); expect(throwable(10)).to.throw(error('10')); expect(throwable(0)).to.throw(error('0')); expect(throwable(true)).to.throw(error('true')); expect(throwable(false)).to.throw(error('false')); expect(throwable({foo: 'bar'})()).to.be.eql({foo: 'bar'}); expect(throwable({})()).to.be.undefined; expect(throwable(undefined)()).to.be.undefined; expect(throwable(null)()).to.be.undefined; }); it('converts the property names to column names', async function () { const schema = createSchema(); schema.defineModel({ name: 'model', datasource: 'mongodb', properties: { foo: { type: DataType.STRING, columnName: 'bar', }, baz: { type: DataType.STRING, columnName: 'qux', }, }, }); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', {foo: 'a1', baz: null}); expect(res).to.be.eql({bar: 'a1', qux: null}); }); it('converts property names chain to column names chain', async function () { const schema = createSchema(); schema.defineModel({ name: 'modelA', datasource: 'mongodb', properties: { foo: { type: DataType.OBJECT, columnName: 'fooCol', model: 'modelB', }, }, }); schema.defineModel({ name: 'modelB', properties: { bar: { type: DataType.OBJECT, model: 'modelC', }, }, }); schema.defineModel({ name: 'modelC', properties: { baz: { type: DataType.OBJECT, columnName: 'bazCol', }, }, }); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('modelA', {'foo.bar.baz.qux': 10}); expect(res).to.be.eql({'fooCol.bar.bazCol.qux': 10}); }); it('throws an error when using "$" character', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const throwable = () => A._buildQuery('model', {$and: []}); expect(throwable).to.throw( 'The symbol "$" is not supported, but "$and" given.', ); }); it('the "and" operator requires an array of objects', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const throwable = v => () => A._buildQuery('model', {and: v}); const error = v => { v = v.replace(/"/g, '$'); const e = new InvalidOperatorValueError('and', 'an Array', v); return e.message.replace(/"/g, '').replace(/\$/g, '"'); }; expect(throwable('str')).to.throw(error('"str"')); expect(throwable('')).to.throw(error('""')); expect(throwable(10)).to.throw(error('10')); expect(throwable(0)).to.throw(error('0')); expect(throwable(true)).to.throw(error('true')); expect(throwable(false)).to.throw(error('false')); }); it('the "or" operator requires an array of objects', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const throwable = v => () => A._buildQuery('model', {or: v}); const error = v => { v = v.replace(/"/g, '$'); const e = new InvalidOperatorValueError('or', 'an Array', v); return e.message.replace(/"/g, '').replace(/\$/g, '"'); }; expect(throwable('str')).to.throw(error('"str"')); expect(throwable('')).to.throw(error('""')); expect(throwable(10)).to.throw(error('10')); expect(throwable(0)).to.throw(error('0')); expect(throwable(true)).to.throw(error('true')); expect(throwable(false)).to.throw(error('false')); }); it('the "nor" operator requires an array of objects', async function () { const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const throwable = v => () => A._buildQuery('model', {nor: v}); const error = v => { v = v.replace(/"/g, '$'); const e = new InvalidOperatorValueError('nor', 'an Array', v); return e.message.replace(/"/g, '').replace(/\$/g, '"'); }; expect(throwable('str')).to.throw(error('"str"')); expect(throwable('')).to.throw(error('""')); expect(throwable(10)).to.throw(error('10')); expect(throwable(0)).to.throw(error('0')); expect(throwable(true)).to.throw(error('true')); expect(throwable(false)).to.throw(error('false')); }); it('does not include an empty value of "and" operator', async function () { const input1 = {foo: 'a1', and: []}; const input2 = {foo: 'a2', and: undefined}; const input3 = {foo: 'a3', and: null}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res1 = A._buildQuery('model', input1); const res2 = A._buildQuery('model', input2); const res3 = A._buildQuery('model', input3); expect(res1).to.be.eql({foo: 'a1'}); expect(res2).to.be.eql({foo: 'a2'}); expect(res3).to.be.eql({foo: 'a3'}); }); it('does not include an empty value of "or" operator', async function () { const input1 = {foo: 'a1', or: []}; const input2 = {foo: 'a2', or: undefined}; const input3 = {foo: 'a3', or: null}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res1 = A._buildQuery('model', input1); const res2 = A._buildQuery('model', input2); const res3 = A._buildQuery('model', input3); expect(res1).to.be.eql({foo: 'a1'}); expect(res2).to.be.eql({foo: 'a2'}); expect(res3).to.be.eql({foo: 'a3'}); }); it('does not include an empty value of "nor" operator', async function () { const input1 = {foo: 'a1', nor: []}; const input2 = {foo: 'a2', nor: undefined}; const input3 = {foo: 'a3', nor: null}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res1 = A._buildQuery('model', input1); const res2 = A._buildQuery('model', input2); const res3 = A._buildQuery('model', input3); expect(res1).to.be.eql({foo: 'a1'}); expect(res2).to.be.eql({foo: 'a2'}); expect(res3).to.be.eql({foo: 'a3'}); }); it('converts the "and" operator to "$and"', async function () { const input = {and: [{foo: 'bar'}, {baz: 'qux'}]}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({$and: [{foo: 'bar'}, {baz: 'qux'}]}); }); it('converts the "or" operator to "$or"', async function () { const input = {or: [{foo: 'bar'}, {baz: 'qux'}]}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({$or: [{foo: 'bar'}, {baz: 'qux'}]}); }); it('converts the "nor" operator to "$nor"', async function () { const input = {nor: [{foo: 'bar'}, {baz: 'qux'}]}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({$nor: [{foo: 'bar'}, {baz: 'qux'}]}); }); it('converts the "eq" operator to "$eq"', async function () { const input = {foo: {eq: 'bar'}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$eq: 'bar'}}); }); it('converts the "neq" operator to "$ne"', async function () { const input = {foo: {neq: 'bar'}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$ne: 'bar'}}); }); it('converts the "gt" operator to "$gt"', async function () { const input = {foo: {gt: 5}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$gt: 5}}); }); it('converts the "lt" operator to "$lt"', async function () { const input = {foo: {lt: 5}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$lt: 5}}); }); it('converts the "gte" operator to "$gte"', async function () { const input = {foo: {gte: 5}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$gte: 5}}); }); it('converts the "lte" operator to "$lte"', async function () { const input = {foo: {lte: 5}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$lte: 5}}); }); it('converts the "inq" operator to "$in"', async function () { const input = {foo: {inq: [1, 2, 3]}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$in: [1, 2, 3]}}); }); it('converts the "nin" operator to "$nin"', async function () { const input = {foo: {nin: ['a', 'b', 'c']}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$nin: ['a', 'b', 'c']}}); }); it('converts the "between" operator to "$gte" and "$lte"', async function () { const input = {foo: {between: [1, 10]}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); const expected = {foo: {$gte: 1, $lte: 10}}; expect(res).to.be.eql(expected); }); it('converts the "exists" operator to "$exists"', async function () { const input1 = {foo: {exists: true}}; const input2 = {foo: {exists: false}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res1 = A._buildQuery('model', input1); const res2 = A._buildQuery('model', input2); expect(res1).to.be.eql({foo: {$exists: true}}); expect(res2).to.be.eql({foo: {$exists: false}}); }); it('converts the "like" operator to "$regex"', async function () { const input = {foo: {like: 'test'}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$regex: /test/}}); }); it('converts the "nlike" operator to "$not"', async function () { const input = {foo: {nlike: 'test'}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$not: /test/}}); }); it('converts the "ilike" operator to "$regex" with "i" flag', async function () { const input = {foo: {ilike: 'test'}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$regex: /test/i}}); }); it('converts the "nilike" operator to "$not" with "i" flag', async function () { const input = {foo: {nilike: 'test'}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res).to.be.eql({foo: {$not: /test/i}}); }); it('converts property value to an instance of ObjectId', async function () { const oid = new ObjectId(); const id = String(oid); const input = {foo: id}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const res = A._buildQuery('model', input); expect(res.foo).to.be.instanceof(ObjectId); expect(res.foo).to.be.eql(oid); }); it('the "eq" operator converts ObjectId string to an instance', async function () { const oid = new ObjectId(); const id = oid.toString(); const input = {foo: {eq: id}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const { foo: {$eq: res}, } = A._buildQuery('model', input); expect(res).to.be.instanceOf(ObjectId); expect(res).to.be.eql(oid); }); it('the "neq" operator converts ObjectId string to an instance', async function () { const oid = new ObjectId(); const id = oid.toString(); const input = {foo: {neq: id}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const { foo: {$ne: res}, } = A._buildQuery('model', input); expect(res).to.be.instanceOf(ObjectId); expect(res).to.be.eql(oid); }); it('the "inq" operator converts ObjectId string to an instance', async function () { const oid = new ObjectId(); const id = oid.toString(); const input = {foo: {inq: [id]}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const { foo: {$in: res}, } = A._buildQuery('model', input); expect(res[0]).to.be.instanceOf(ObjectId); expect(res[0]).to.be.eql(oid); }); it('the "nin" operator converts ObjectId string to an instance', async function () { const oid = new ObjectId(); const id = oid.toString(); const input = {foo: {nin: [id]}}; const schema = createSchema(); schema.defineModel({name: 'model', datasource: 'mongodb'}); const A = await schema.getService(AdapterRegistry).getAdapter('mongodb'); const { foo: {$nin: res}, } = A._buildQuery('model', input); expect(res[0]).to.be.instanceOf(ObjectId); expect(res[0]).to.be.eql(oid); }); it('converts property value to an instance of Date', async function () { const date = new Date(); const isoDate = date.toISOString(); const input = {fo