@e22m4u/js-repository-mongodb-adapter
Version:
MongoDB адаптер для @e22m4u/js-repository
1,273 lines (1,192 loc) • 210 kB
JavaScript
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