UNPKG

@e22m4u/js-repository

Version:

Реализация репозитория для работы с базами данных в Node.js

1,485 lines (1,450 loc) 93.1 kB
import {expect} from 'chai'; import {format} from '@e22m4u/js-format'; import {DataType} from '../definition/index.js'; import {RelationType} from '../definition/index.js'; import {DatabaseSchema} from '../database-schema.js'; import {HasManyResolver} from './has-many-resolver.js'; import {DEFAULT_PRIMARY_KEY_PROPERTY_NAME as DEF_PK} from '../definition/index.js'; describe('HasManyResolver', function () { describe('includeTo', function () { it('requires the "entities" parameter to be an array', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "entities" of HasManyResolver.includeTo requires ' + 'an Array of Object, but %s was given.', v, ); const throwable = v => R.includeTo( v, 'sourceName', 'targetName', 'relationName', 'foreignKey', ); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable('str')).to.be.rejectedWith(error('"str"')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires elements of the "entities" parameter to be an Object', async function () { const dbs = new DatabaseSchema(); dbs.defineModel({name: 'source'}); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "entities" of HasManyResolver.includeTo requires ' + 'an Array of Object, but %s was given.', v, ); const throwable = v => R.includeTo([v], 'source', 'target', 'relationName', 'foreignKey'); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable('str')).to.be.rejectedWith(error('"str"')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the "sourceName" parameter to be a non-empty string', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "sourceName" of HasManyResolver.includeTo requires ' + 'a non-empty String, but %s was given.', v, ); const throwable = v => R.includeTo([], v, 'targetName', 'relationName', 'foreignKey'); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the "targetName" parameter to be a non-empty string', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "targetName" of HasManyResolver.includeTo requires ' + 'a non-empty String, but %s was given.', v, ); const throwable = v => R.includeTo([], 'sourceName', v, 'relationName', 'foreignKey'); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the "relationName" parameter to be a non-empty string', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "relationName" of HasManyResolver.includeTo requires ' + 'a non-empty String, but %s was given.', v, ); const throwable = v => R.includeTo([], 'sourceName', 'targetName', v, 'foreignKey'); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the "foreignKey" parameter to be a non-empty string', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "foreignKey" of HasManyResolver.includeTo requires ' + 'a non-empty String, but %s was given.', v, ); const throwable = v => R.includeTo([], 'sourceName', 'targetName', 'relationName', v); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the provided parameter "scope" to be an object', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The provided parameter "scope" of HasManyResolver.includeTo ' + 'should be an Object, but %s was given.', v, ); const throwable = v => R.includeTo( [], 'sourceName', 'targetName', 'relationName', 'foreignKey', v, ); await expect(throwable('str')).to.be.rejectedWith(error('"str"')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable([])).to.be.rejectedWith(error('Array')); }); it('throws an error if a target model is not found', async function () { const dbs = new DatabaseSchema(); dbs.defineModel({name: 'source'}); const R = dbs.getService(HasManyResolver); const promise = R.includeTo( [], 'source', 'target', 'relationName', 'foreignKey', ); await expect(promise).to.be.rejectedWith( 'The model "target" is not defined', ); }); it('throws an error if a target model does not have datasource', async function () { const dbs = new DatabaseSchema(); dbs.defineModel({name: 'source'}); dbs.defineModel({name: 'target'}); const R = dbs.getService(HasManyResolver); const promise = R.includeTo( [], 'source', 'target', 'relationName', 'foreignKey', ); await expect(promise).to.be.rejectedWith( 'The model "target" does not have a specified datasource.', ); }); it('does not throw an error if a relation target is not exist', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRel = dbs.getRepository('source'); const source = await sourceRel.create({}); const R = dbs.getService(HasManyResolver); await R.includeTo([source], 'source', 'target', 'children', 'parentId'); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [], }); }); it('includes if a primary key is not defined in the source model', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({[DEF_PK]: source[DEF_PK]}); const target1 = await targetRep.create({parentId: source[DEF_PK]}); const target2 = await targetRep.create({parentId: source[DEF_PK]}); const target3 = await targetRep.create({parentId: -1}); expect(target1).to.be.eql({ [DEF_PK]: target1[DEF_PK], parentId: source[DEF_PK], }); expect(target2).to.be.eql({ [DEF_PK]: target2[DEF_PK], parentId: source[DEF_PK], }); expect(target3).to.be.eql({ [DEF_PK]: target3[DEF_PK], parentId: -1, }); const R = dbs.getService(HasManyResolver); await R.includeTo([source], 'source', 'target', 'children', 'parentId'); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { id: target1[DEF_PK], parentId: source[DEF_PK], }, { id: target2[DEF_PK], parentId: source[DEF_PK], }, ], }); }); it('includes if the source model has a custom primary key', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({ name: 'source', datasource: 'datasource', properties: { myId: { type: DataType.NUMBER, primaryKey: true, }, }, }); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({myId: source.myId}); const target1 = await targetRep.create({parentId: source.myId}); const target2 = await targetRep.create({parentId: source.myId}); const target3 = await targetRep.create({parentId: -1}); expect(target1).to.be.eql({ [DEF_PK]: target1[DEF_PK], parentId: source.myId, }); expect(target2).to.be.eql({ [DEF_PK]: target2[DEF_PK], parentId: source.myId, }); expect(target3).to.be.eql({ [DEF_PK]: target3[DEF_PK], parentId: -1, }); const R = dbs.getService(HasManyResolver); await R.includeTo([source], 'source', 'target', 'children', 'parentId'); expect(source).to.be.eql({ myId: source.myId, children: [ { [DEF_PK]: target1[DEF_PK], parentId: source.myId, }, { [DEF_PK]: target2[DEF_PK], parentId: source.myId, }, ], }); }); it('includes if the target model has a custom primary key', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({ name: 'target', datasource: 'datasource', properties: { myId: { type: DataType.NUMBER, primaryKey: true, }, }, }); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({[DEF_PK]: source[DEF_PK]}); const target1 = await targetRep.create({parentId: source[DEF_PK]}); const target2 = await targetRep.create({parentId: source[DEF_PK]}); const target3 = await targetRep.create({parentId: -1}); expect(target1).to.be.eql({ myId: target1.myId, parentId: source[DEF_PK], }); expect(target2).to.be.eql({ myId: target2.myId, parentId: source[DEF_PK], }); expect(target3).to.be.eql({ myId: target3.myId, parentId: -1, }); const R = dbs.getService(HasManyResolver); await R.includeTo([source], 'source', 'target', 'children', 'parentId'); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { myId: target1.myId, parentId: source[DEF_PK], }, { myId: target2.myId, parentId: source[DEF_PK], }, ], }); }); it('uses a where clause of the given scope to filter the relation target', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({[DEF_PK]: source[DEF_PK]}); const target1 = await targetRep.create({ featured: false, parentId: source[DEF_PK], }); expect(target1).to.be.eql({ [DEF_PK]: target1[DEF_PK], featured: false, parentId: source[DEF_PK], }); const target2 = await targetRep.create({ featured: true, parentId: source[DEF_PK], }); expect(target2).to.be.eql({ [DEF_PK]: target2[DEF_PK], featured: true, parentId: source[DEF_PK], }); const target3 = await targetRep.create({ featured: true, parentId: source[DEF_PK], }); expect(target3).to.be.eql({ [DEF_PK]: target3[DEF_PK], featured: true, parentId: source[DEF_PK], }); const R = dbs.getService(HasManyResolver); await R.includeTo([source], 'source', 'target', 'children', 'parentId', { where: {featured: false}, }); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { [DEF_PK]: target1[DEF_PK], featured: false, parentId: source[DEF_PK], }, ], }); await R.includeTo([source], 'source', 'target', 'children', 'parentId', { where: {featured: true}, }); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { [DEF_PK]: target2[DEF_PK], featured: true, parentId: source[DEF_PK], }, { [DEF_PK]: target3[DEF_PK], featured: true, parentId: source[DEF_PK], }, ], }); }); it('uses a fields clause of the given scope to filter the relation target', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], }); const target1 = await targetRep.create({ foo: 'fooVal1', bar: 'barVal1', parentId: source[DEF_PK], }); expect(target1).to.be.eql({ [DEF_PK]: target1[DEF_PK], foo: 'fooVal1', bar: 'barVal1', parentId: source[DEF_PK], }); const target2 = await targetRep.create({ foo: 'fooVal2', bar: 'barVal2', parentId: source[DEF_PK], }); expect(target2).to.be.eql({ [DEF_PK]: target2[DEF_PK], foo: 'fooVal2', bar: 'barVal2', parentId: source[DEF_PK], }); const target3 = await targetRep.create({ foo: 'fooVal3', bar: 'barVal3', parentId: -1, }); expect(target3).to.be.eql({ [DEF_PK]: target3[DEF_PK], foo: 'fooVal3', bar: 'barVal3', parentId: -1, }); const R = dbs.getService(HasManyResolver); await R.includeTo([source], 'source', 'target', 'children', 'parentId', { fields: [DEF_PK, 'bar'], }); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { [DEF_PK]: target1[DEF_PK], bar: target1.bar, }, { [DEF_PK]: target2[DEF_PK], bar: target2.bar, }, ], }); }); it('uses an include clause of the given scope to resolve target relations', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({ name: 'datasource', adapter: 'memory', }); dbs.defineModel({ name: 'modelA', datasource: 'datasource', properties: { id: { type: DataType.NUMBER, primaryKey: true, }, source: { type: DataType.STRING, default: 'modelA', }, }, relations: { children: { type: RelationType.HAS_MANY, model: 'modelB', foreignKey: 'parentId', }, }, }); dbs.defineModel({ name: 'modelB', datasource: 'datasource', properties: { id: { type: DataType.NUMBER, primaryKey: true, }, source: { type: DataType.STRING, default: 'modelB', }, }, relations: { children: { type: RelationType.HAS_MANY, model: 'modelC', foreignKey: 'parentId', }, }, }); dbs.defineModel({ name: 'modelC', datasource: 'datasource', properties: { id: { type: DataType.NUMBER, primaryKey: true, }, source: { type: DataType.STRING, default: 'modelC', }, }, }); const aRep = dbs.getRepository('modelA'); const bRep = dbs.getRepository('modelB'); const cRep = dbs.getRepository('modelC'); const a = await aRep.create({}); const b1 = await bRep.create({parentId: a.id}); const b2 = await bRep.create({parentId: a.id}); const b3 = await bRep.create({parentId: -1}); const c1 = await cRep.create({parentId: b1.id}); const c2 = await cRep.create({parentId: b1.id}); const c3 = await cRep.create({parentId: b2.id}); const c4 = await cRep.create({parentId: b2.id}); expect(a).to.be.eql({ id: a.id, source: 'modelA', }); expect(b1).to.be.eql({ id: b1.id, source: 'modelB', parentId: a.id, }); expect(b2).to.be.eql({ id: b2.id, source: 'modelB', parentId: a.id, }); expect(b3).to.be.eql({ id: b3.id, source: 'modelB', parentId: -1, }); expect(c1).to.be.eql({ id: c1.id, source: 'modelC', parentId: b1.id, }); expect(c2).to.be.eql({ id: c2.id, source: 'modelC', parentId: b1.id, }); expect(c3).to.be.eql({ id: c3.id, source: 'modelC', parentId: b2.id, }); expect(c4).to.be.eql({ id: c4.id, source: 'modelC', parentId: b2.id, }); const R = dbs.getService(HasManyResolver); await R.includeTo([a], 'modelA', 'modelB', 'children', 'parentId', { include: 'children', }); expect(a).to.be.eql({ id: a.id, source: 'modelA', children: [ { id: b1.id, source: 'modelB', parentId: a.id, children: [ { id: c1.id, source: 'modelC', parentId: b1.id, }, { id: c2.id, source: 'modelC', parentId: b1.id, }, ], }, { id: b2.id, source: 'modelB', parentId: a.id, children: [ { id: c3.id, source: 'modelC', parentId: b2.id, }, { id: c4.id, source: 'modelC', parentId: b2.id, }, ], }, ], }); }); it('does not break the "and" operator of the given "where" clause', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], }); const target1 = await targetRep.create({ featured: false, parentId: source[DEF_PK], }); expect(target1).to.be.eql({ [DEF_PK]: target1[DEF_PK], featured: false, parentId: source[DEF_PK], }); const target2 = await targetRep.create({ featured: true, parentId: source[DEF_PK], }); expect(target2).to.be.eql({ [DEF_PK]: target2[DEF_PK], featured: true, parentId: source[DEF_PK], }); const target3 = await targetRep.create({ featured: true, parentId: source[DEF_PK], }); expect(target3).to.be.eql({ [DEF_PK]: target3[DEF_PK], featured: true, parentId: source[DEF_PK], }); const R = dbs.getService(HasManyResolver); await R.includeTo([source], 'source', 'target', 'children', 'parentId', { where: {and: [{featured: false}]}, }); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { [DEF_PK]: target1[DEF_PK], featured: false, parentId: source[DEF_PK], }, ], }); delete source.children; await R.includeTo([source], 'source', 'target', 'children', 'parentId', { where: {and: [{featured: true}]}, }); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { [DEF_PK]: target2[DEF_PK], featured: true, parentId: source[DEF_PK], }, { [DEF_PK]: target3[DEF_PK], featured: true, parentId: source[DEF_PK], }, ], }); }); }); describe('includePolymorphicTo', function () { it('requires the "entities" parameter to be an array', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "entities" of HasManyResolver.includePolymorphicTo requires ' + 'an Array of Object, but %s was given.', v, ); const throwable = v => R.includePolymorphicTo( v, 'sourceName', 'targetName', 'relationName', 'foreignKey', 'discriminator', ); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable('str')).to.be.rejectedWith(error('"str"')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires elements of the "entities" parameter to be an Object', async function () { const dbs = new DatabaseSchema(); dbs.defineModel({name: 'source'}); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "entities" of HasManyResolver.includePolymorphicTo requires ' + 'an Array of Object, but %s was given.', v, ); const throwable = v => R.includePolymorphicTo( [v], 'source', 'target', 'relationName', 'foreignKey', 'discriminator', ); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable('str')).to.be.rejectedWith(error('"str"')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the "sourceName" parameter to be a non-empty string', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "sourceName" of HasManyResolver.includePolymorphicTo requires ' + 'a non-empty String, but %s was given.', v, ); const throwable = v => R.includePolymorphicTo( [], v, 'targetName', 'relationName', 'foreignKey', 'discriminator', ); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the "targetName" parameter to be a non-empty string', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "targetName" of HasManyResolver.includePolymorphicTo requires ' + 'a non-empty String, but %s was given.', v, ); const throwable = v => R.includePolymorphicTo( [], 'sourceName', v, 'relationName', 'foreignKey', 'discriminator', ); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the "relationName" parameter to be a non-empty string', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "relationName" of HasManyResolver.includePolymorphicTo requires ' + 'a non-empty String, but %s was given.', v, ); const throwable = v => R.includePolymorphicTo( [], 'sourceName', 'targetName', v, 'foreignKey', 'discriminator', ); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the "foreignKey" parameter to be a non-empty string', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "foreignKey" of HasManyResolver.includePolymorphicTo requires ' + 'a non-empty String, but %s was given.', v, ); const throwable = v => R.includePolymorphicTo( [], 'sourceName', 'targetName', 'relationName', v, 'discriminator', ); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the "discriminator" parameter to be a non-empty string', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The parameter "discriminator" of HasManyResolver.includePolymorphicTo requires ' + 'a non-empty String, but %s was given.', v, ); const throwable = v => R.includePolymorphicTo( [], 'sourceName', 'targetName', 'relationName', 'foreignKey', v, ); await expect(throwable('')).to.be.rejectedWith(error('""')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable(false)).to.be.rejectedWith(error('false')); await expect(throwable([])).to.be.rejectedWith(error('Array')); await expect(throwable({})).to.be.rejectedWith(error('Object')); await expect(throwable(undefined)).to.be.rejectedWith(error('undefined')); await expect(throwable(null)).to.be.rejectedWith(error('null')); }); it('requires the provided parameter "scope" to be an object', async function () { const dbs = new DatabaseSchema(); const R = dbs.getService(HasManyResolver); const error = v => format( 'The provided parameter "scope" of HasManyResolver.includePolymorphicTo ' + 'should be an Object, but %s was given.', v, ); const throwable = v => R.includePolymorphicTo( [], 'sourceName', 'targetName', 'relationName', 'foreignKey', 'discriminator', v, ); await expect(throwable('str')).to.be.rejectedWith(error('"str"')); await expect(throwable(10)).to.be.rejectedWith(error('10')); await expect(throwable(true)).to.be.rejectedWith(error('true')); await expect(throwable([])).to.be.rejectedWith(error('Array')); }); it('throws an error if the given target model is not found', async function () { const dbs = new DatabaseSchema(); dbs.defineModel({name: 'source'}); const R = dbs.getService(HasManyResolver); const entity = {[DEF_PK]: 1}; const promise = R.includePolymorphicTo( [entity], 'source', 'target', 'children', 'parentId', 'parentType', ); await expect(promise).to.be.rejectedWith( 'The model "target" is not defined', ); }); it('throws an error if the given target model does not have a datasource', async function () { const dbs = new DatabaseSchema(); dbs.defineModel({name: 'source'}); dbs.defineModel({name: 'target'}); const R = dbs.getService(HasManyResolver); const entity = {[DEF_PK]: 1}; const promise = R.includePolymorphicTo( [entity], 'source', 'target', 'children', 'parentId', 'parentType', ); await expect(promise).to.be.rejectedWith( 'The model "target" does not have a specified datasource.', ); }); it('does not throw an error if a relation target is not found', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRel = dbs.getRepository('source'); const source = await sourceRel.create({}); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], }); const R = dbs.getService(HasManyResolver); await R.includePolymorphicTo( [source], 'source', 'target', 'children', 'parentId', 'parentType', ); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [], }); }); it('does not include an entity with a not matched discriminator value', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRel = dbs.getRepository('source'); const targetRel = dbs.getRepository('target'); const source = await sourceRel.create({}); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], }); const target = await targetRel.create({ parentId: source[DEF_PK], parentType: 'unknown', }); expect(target).to.be.eql({ [DEF_PK]: target[DEF_PK], parentId: source[DEF_PK], parentType: 'unknown', }); const R = dbs.getService(HasManyResolver); await R.includePolymorphicTo( [source], 'source', 'target', 'children', 'parentId', 'parentType', ); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [], }); }); it('includes if a primary key is not defined in the source model', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({[DEF_PK]: source[DEF_PK]}); const target1 = await targetRep.create({ parentId: source[DEF_PK], parentType: 'source', }); expect(target1).to.be.eql({ [DEF_PK]: target1[DEF_PK], parentId: source[DEF_PK], parentType: 'source', }); const target2 = await targetRep.create({ parentId: source[DEF_PK], parentType: 'source', }); expect(target2).to.be.eql({ [DEF_PK]: target2[DEF_PK], parentId: source[DEF_PK], parentType: 'source', }); const target3 = await targetRep.create({ parentId: -1, parentType: 'source', }); expect(target3).to.be.eql({ [DEF_PK]: target3[DEF_PK], parentId: -1, parentType: 'source', }); const R = dbs.getService(HasManyResolver); await R.includePolymorphicTo( [source], 'source', 'target', 'children', 'parentId', 'parentType', ); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { id: target1[DEF_PK], parentId: source[DEF_PK], parentType: target1.parentType, }, { id: target2[DEF_PK], parentId: source[DEF_PK], parentType: target2.parentType, }, ], }); }); it('includes if the source model has a custom primary key', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({ name: 'source', datasource: 'datasource', properties: { myId: { type: DataType.NUMBER, primaryKey: true, }, }, }); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({myId: source.myId}); const target1 = await targetRep.create({ parentId: source.myId, parentType: 'source', }); expect(target1).to.be.eql({ [DEF_PK]: target1[DEF_PK], parentId: source.myId, parentType: 'source', }); const target2 = await targetRep.create({ parentId: source.myId, parentType: 'source', }); expect(target2).to.be.eql({ [DEF_PK]: target2[DEF_PK], parentId: source.myId, parentType: 'source', }); const target3 = await targetRep.create({ parentId: -1, parentType: 'source', }); expect(target3).to.be.eql({ [DEF_PK]: target3[DEF_PK], parentId: -1, parentType: 'source', }); const R = dbs.getService(HasManyResolver); await R.includePolymorphicTo( [source], 'source', 'target', 'children', 'parentId', 'parentType', ); expect(source).to.be.eql({ myId: source.myId, children: [ { [DEF_PK]: target1[DEF_PK], parentId: source.myId, parentType: target1.parentType, }, { [DEF_PK]: target2[DEF_PK], parentId: source.myId, parentType: target2.parentType, }, ], }); }); it('includes if the target model has a custom primary key', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({ name: 'target', datasource: 'datasource', properties: { myId: { type: DataType.NUMBER, primaryKey: true, }, }, }); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({[DEF_PK]: source[DEF_PK]}); const target1 = await targetRep.create({ parentId: source[DEF_PK], parentType: 'source', }); expect(target1).to.be.eql({ myId: target1.myId, parentId: source[DEF_PK], parentType: 'source', }); const target2 = await targetRep.create({ parentId: source[DEF_PK], parentType: 'source', }); expect(target2).to.be.eql({ myId: target2.myId, parentId: source[DEF_PK], parentType: 'source', }); const target3 = await targetRep.create({ parentId: -1, parentType: 'source', }); expect(target3).to.be.eql({ myId: target3.myId, parentId: -1, parentType: 'source', }); const R = dbs.getService(HasManyResolver); await R.includePolymorphicTo( [source], 'source', 'target', 'children', 'parentId', 'parentType', ); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { myId: target1.myId, parentId: source[DEF_PK], parentType: target1.parentType, }, { myId: target2.myId, parentId: source[DEF_PK], parentType: target2.parentType, }, ], }); }); it('uses a where clause of the given scope to filter the relation target', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({[DEF_PK]: source[DEF_PK]}); const target1 = await targetRep.create({ featured: false, parentId: source[DEF_PK], parentType: 'source', }); expect(target1).to.be.eql({ [DEF_PK]: target1[DEF_PK], featured: false, parentId: source[DEF_PK], parentType: 'source', }); const target2 = await targetRep.create({ featured: true, parentId: source[DEF_PK], parentType: 'source', }); expect(target2).to.be.eql({ [DEF_PK]: target2[DEF_PK], featured: true, parentId: source[DEF_PK], parentType: 'source', }); const target3 = await targetRep.create({ featured: true, parentId: source[DEF_PK], parentType: 'source', }); expect(target3).to.be.eql({ [DEF_PK]: target3[DEF_PK], featured: true, parentId: source[DEF_PK], parentType: 'source', }); const R = dbs.getService(HasManyResolver); await R.includePolymorphicTo( [source], 'source', 'target', 'children', 'parentId', 'parentType', {where: {featured: false}}, ); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { [DEF_PK]: target1[DEF_PK], featured: false, parentId: source[DEF_PK], parentType: target1.parentType, }, ], }); await R.includePolymorphicTo( [source], 'source', 'target', 'children', 'parentId', 'parentType', {where: {featured: true}}, ); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { [DEF_PK]: target2[DEF_PK], featured: true, parentId: source[DEF_PK], parentType: target2.parentType, }, { [DEF_PK]: target3[DEF_PK], featured: true, parentId: source[DEF_PK], parentType: target3.parentType, }, ], }); }); it('uses a fields clause of the given scope to filter the relation target', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({name: 'datasource', adapter: 'memory'}); dbs.defineModel({name: 'source', datasource: 'datasource'}); dbs.defineModel({name: 'target', datasource: 'datasource'}); const sourceRep = dbs.getRepository('source'); const targetRep = dbs.getRepository('target'); const source = await sourceRep.create({}); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], }); const target1 = await targetRep.create({ foo: 'fooVal1', bar: 'barVal1', parentId: source[DEF_PK], parentType: 'source', }); expect(target1).to.be.eql({ [DEF_PK]: target1[DEF_PK], foo: 'fooVal1', bar: 'barVal1', parentId: source[DEF_PK], parentType: 'source', }); const target2 = await targetRep.create({ foo: 'fooVal2', bar: 'barVal2', parentId: source[DEF_PK], parentType: 'source', }); expect(target2).to.be.eql({ [DEF_PK]: target2[DEF_PK], foo: 'fooVal2', bar: 'barVal2', parentId: source[DEF_PK], parentType: 'source', }); const target3 = await targetRep.create({ foo: 'fooVal3', bar: 'barVal3', parentId: -1, parentType: 'source', }); expect(target3).to.be.eql({ [DEF_PK]: target3[DEF_PK], foo: 'fooVal3', bar: 'barVal3', parentId: -1, parentType: 'source', }); const R = dbs.getService(HasManyResolver); await R.includePolymorphicTo( [source], 'source', 'target', 'children', 'parentId', 'parentType', {fields: [DEF_PK, 'bar']}, ); expect(source).to.be.eql({ [DEF_PK]: source[DEF_PK], children: [ { [DEF_PK]: target1[DEF_PK], bar: target1.bar, }, { [DEF_PK]: target2[DEF_PK], bar: target2.bar, }, ], }); }); it('uses an include clause of the given scope to resolve target relations', async function () { const dbs = new DatabaseSchema(); dbs.defineDatasource({ name: 'datasource', adapter: 'memory', }); dbs.defineModel({ name: 'modelA', datasource: 'datasource', properties: { id: { type: DataType.NUMBER, primaryKey: true, }, source: { type: DataType.STRING, default: 'modelA', }, }, relations: { children: { type: RelationType.HAS_MANY, model: 'modelB', polymorphic: true, foreignKey: 'parentId', discriminator: 'parentType', }, }, }); dbs.defineModel({ name: 'modelB', datasource: 'datasource', properties: { id: { type: DataType.NUMBER, primaryKey: true, }, source: { type: DataType.STRING, default: 'modelB', }, }, relations: { children: { type: RelationType.HAS_MANY, model: 'modelC', polymorphic: true, foreignKey: 'parentId', discriminator: 'parentType', }, }, }); dbs.defineModel({ name: 'modelC', datasource: 'datasource', properties: { id: { type: DataType.NUMBER, primaryKey: true, }, source: { type: DataType.STRING, default: 'modelC', }, }, }); const aRep = dbs.getRepository('modelA'); const bRep = dbs.getRepository('modelB'); const cRep = dbs.getRepository('modelC'); const a = await aRep.create({}); const b1 = await bRep.create({parentId: a.id, parentType: 'modelA'}); const b2 = await bRep.create({parentId: a.id, parentType: 'modelA'}); const c1 = await cRep.create({parentId: b1.id, parentType: 'modelB'}); const c2 = await