@e22m4u/js-repository
Version:
Реализация репозитория для работы с базами данных в Node.js
1,485 lines (1,450 loc) • 93.1 kB
JavaScript
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