@e22m4u/js-repository
Version:
Реализация репозитория для работы с базами данных в Node.js
632 lines (612 loc) • 24.6 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 {ReferencesManyResolver} from './references-many-resolver.js';
import {DEFAULT_PRIMARY_KEY_PROPERTY_NAME as DEF_PK} from '../definition/index.js';
describe('ReferencesManyResolver', function () {
describe('includeTo', function () {
it('requires the "entities" parameter to be an array', async function () {
const dbs = new DatabaseSchema();
const R = dbs.getService(ReferencesManyResolver);
const error = v =>
format(
'The parameter "entities" of ReferencesManyResolver.includeTo requires ' +
'an Array of Object, but %s was given.',
v,
);
const throwable = v =>
R.includeTo(v, 'sourceName', 'targetName', 'relationName');
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();
const R = dbs.getService(ReferencesManyResolver);
const error = v =>
format(
'The parameter "entities" of ReferencesManyResolver.includeTo requires ' +
'an Array of Object, but %s was given.',
v,
);
const throwable = v =>
R.includeTo([v], 'sourceName', 'targetName', 'relationName');
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(ReferencesManyResolver);
const error = v =>
format(
'The parameter "sourceName" of ReferencesManyResolver.includeTo requires ' +
'a non-empty String, but %s was given.',
v,
);
const throwable = v => R.includeTo([], v, 'targetName', 'relationName');
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(ReferencesManyResolver);
const error = v =>
format(
'The parameter "targetName" of ReferencesManyResolver.includeTo requires ' +
'a non-empty String, but %s was given.',
v,
);
const throwable = v => R.includeTo([], 'sourceName', v, 'relationName');
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(ReferencesManyResolver);
const error = v =>
format(
'The parameter "relationName" of ReferencesManyResolver.includeTo requires ' +
'a non-empty String, but %s was given.',
v,
);
const throwable = v => R.includeTo([], 'sourceName', 'targetName', 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 "foreignKey" to be a string', async function () {
const dbs = new DatabaseSchema();
const R = dbs.getService(ReferencesManyResolver);
const error = v =>
format(
'The provided parameter "foreignKey" of ReferencesManyResolver.includeTo ' +
'should be a String, but %s was given.',
v,
);
const throwable = v =>
R.includeTo([], 'sourceName', 'targetName', 'relationName', v);
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'));
await expect(throwable({})).to.be.rejectedWith(error('Object'));
});
it('requires the provided parameter "scope" to be an object', async function () {
const dbs = new DatabaseSchema();
const R = dbs.getService(ReferencesManyResolver);
const error = v =>
format(
'The provided parameter "scope" of ReferencesManyResolver.includeTo ' +
'should be an Object, but %s was given.',
v,
);
const throwable = v =>
R.includeTo(
[],
'sourceName',
'targetName',
'relationName',
undefined,
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(ReferencesManyResolver);
const promise = R.includeTo([], 'source', 'target', 'relation');
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: 'target'});
const R = dbs.getService(ReferencesManyResolver);
const promise = R.includeTo([], 'source', 'target', 'relation');
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({parentIds: [10, 20]});
const R = dbs.getService(ReferencesManyResolver);
await R.includeTo([source], 'source', 'target', 'parents');
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: [10, 20],
parents: [],
});
});
it('includes if a primary key is not defined in the target 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 target1 = await targetRep.create({});
const target2 = await targetRep.create({});
const target3 = await targetRep.create({});
expect(target1).to.be.eql({[DEF_PK]: target1[DEF_PK]});
expect(target2).to.be.eql({[DEF_PK]: target2[DEF_PK]});
expect(target3).to.be.eql({[DEF_PK]: target3[DEF_PK]});
const source = await sourceRep.create({
parentIds: [target1[DEF_PK], target2[DEF_PK]],
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: [target1[DEF_PK], target2[DEF_PK]],
});
const R = dbs.getService(ReferencesManyResolver);
await R.includeTo([source], 'source', 'target', 'parents');
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: source.parentIds,
parents: [target1, target2],
});
});
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 target1 = await targetRep.create({});
const target2 = await targetRep.create({});
const target3 = await targetRep.create({});
expect(target1).to.be.eql({myId: target1.myId});
expect(target2).to.be.eql({myId: target2.myId});
expect(target3).to.be.eql({myId: target3.myId});
const source = await sourceRep.create({
parentIds: [target1.myId, target2.myId],
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: [target1.myId, target2.myId],
});
const R = dbs.getService(ReferencesManyResolver);
await R.includeTo([source], 'source', 'target', 'parents');
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: source.parentIds,
parents: [target1, target2],
});
});
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 target1 = await targetRep.create({});
const target2 = await targetRep.create({});
const target3 = await targetRep.create({});
expect(target1).to.be.eql({[DEF_PK]: target1[DEF_PK]});
expect(target2).to.be.eql({[DEF_PK]: target2[DEF_PK]});
expect(target3).to.be.eql({[DEF_PK]: target3[DEF_PK]});
const source = await sourceRep.create({
parentIds: [target1[DEF_PK], target2[DEF_PK]],
});
expect(source).to.be.eql({
myId: source.myId,
parentIds: [target1[DEF_PK], target2[DEF_PK]],
});
const R = dbs.getService(ReferencesManyResolver);
await R.includeTo([source], 'source', 'target', 'parents');
expect(source).to.be.eql({
myId: source.myId,
parentIds: source.parentIds,
parents: [target1, target2],
});
});
it('includes if the property "foreignKey" is specified', 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 target1 = await targetRep.create({});
const target2 = await targetRep.create({});
const target3 = await targetRep.create({});
expect(target1).to.be.eql({[DEF_PK]: target1[DEF_PK]});
expect(target2).to.be.eql({[DEF_PK]: target2[DEF_PK]});
expect(target3).to.be.eql({[DEF_PK]: target3[DEF_PK]});
const source = await sourceRep.create({
parentIds: [target1[DEF_PK], target2[DEF_PK]],
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: [target1[DEF_PK], target2[DEF_PK]],
});
const R = dbs.getService(ReferencesManyResolver);
await R.includeTo([source], 'source', 'target', 'relations', 'parentIds');
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: source.parentIds,
relations: [target1, target2],
});
});
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 target1 = await targetRep.create({featured: false});
const target2 = await targetRep.create({featured: true});
const target3 = await targetRep.create({featured: true});
expect(target1).to.be.eql({[DEF_PK]: target1[DEF_PK], featured: false});
expect(target2).to.be.eql({[DEF_PK]: target2[DEF_PK], featured: true});
expect(target3).to.be.eql({[DEF_PK]: target3[DEF_PK], featured: true});
const source = await sourceRep.create({
parentIds: [target1[DEF_PK], target2[DEF_PK], target3[DEF_PK]],
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: [target1[DEF_PK], target2[DEF_PK], target3[DEF_PK]],
});
const R = dbs.getService(ReferencesManyResolver);
await R.includeTo([source], 'source', 'target', 'parents', undefined, {
where: {featured: false},
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: source.parentIds,
parents: [target1],
});
delete source.parents;
await R.includeTo([source], 'source', 'target', 'parents', undefined, {
where: {featured: true},
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: source.parentIds,
parents: [target2, target3],
});
});
it('uses a slice 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 target1 = await targetRep.create({});
const target2 = await targetRep.create({});
const target3 = await targetRep.create({});
const target4 = await targetRep.create({});
expect(target1).to.be.eql({[DEF_PK]: target1[DEF_PK]});
expect(target2).to.be.eql({[DEF_PK]: target2[DEF_PK]});
expect(target3).to.be.eql({[DEF_PK]: target3[DEF_PK]});
expect(target4).to.be.eql({[DEF_PK]: target4[DEF_PK]});
const source = await sourceRep.create({
parentIds: [
target1[DEF_PK],
target2[DEF_PK],
target3[DEF_PK],
target4[DEF_PK],
],
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: [
target1[DEF_PK],
target2[DEF_PK],
target3[DEF_PK],
target4[DEF_PK],
],
});
const R = dbs.getService(ReferencesManyResolver);
await R.includeTo([source], 'source', 'target', 'parents', undefined, {
skip: 1,
limit: 2,
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: source.parentIds,
parents: [target2, target3],
});
});
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 target1 = await targetRep.create({
foo: 'fooVal1',
bar: 'barVal1',
});
const target2 = await targetRep.create({
foo: 'fooVal2',
bar: 'barVal2',
});
const target3 = await targetRep.create({
foo: 'fooVal3',
bar: 'barVal3',
});
expect(target1).to.be.eql({
[DEF_PK]: target1[DEF_PK],
foo: 'fooVal1',
bar: 'barVal1',
});
expect(target2).to.be.eql({
[DEF_PK]: target2[DEF_PK],
foo: 'fooVal2',
bar: 'barVal2',
});
expect(target3).to.be.eql({
[DEF_PK]: target3[DEF_PK],
foo: 'fooVal3',
bar: 'barVal3',
});
const source = await sourceRep.create({
parentIds: [target1[DEF_PK], target2[DEF_PK]],
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: [target1[DEF_PK], target2[DEF_PK]],
});
const R = dbs.getService(ReferencesManyResolver);
await R.includeTo([source], 'source', 'target', 'parents', undefined, {
fields: [DEF_PK, 'bar'],
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: source.parentIds,
parents: [
{
[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',
},
},
});
dbs.defineModel({
name: 'modelB',
datasource: 'datasource',
properties: {
id: {
type: DataType.NUMBER,
primaryKey: true,
},
source: {
type: DataType.STRING,
default: 'modelB',
},
},
relations: {
parent: {
type: RelationType.BELONGS_TO,
model: 'modelA',
},
},
});
dbs.defineModel({
name: 'modelC',
datasource: 'datasource',
properties: {
id: {
type: DataType.NUMBER,
primaryKey: true,
},
source: {
type: DataType.STRING,
default: 'modelC',
},
},
relations: {
parents: {
type: RelationType.REFERENCES_MANY,
model: 'modelB',
},
},
});
const aRep = dbs.getRepository('modelA');
const bRep = dbs.getRepository('modelB');
const cRep = dbs.getRepository('modelC');
const a1 = await aRep.create({});
const a2 = await aRep.create({});
const b1 = await bRep.create({parentId: a1.id});
const b2 = await bRep.create({parentId: a2.id});
const c = await cRep.create({parentIds: [b1.id, b2.id]});
expect(a1).to.be.eql({
id: a1.id,
source: 'modelA',
});
expect(a2).to.be.eql({
id: a2.id,
source: 'modelA',
});
expect(b1).to.be.eql({
id: b1.id,
source: 'modelB',
parentId: a1.id,
});
expect(b2).to.be.eql({
id: b2.id,
source: 'modelB',
parentId: a2.id,
});
expect(c).to.be.eql({
id: c.id,
source: 'modelC',
parentIds: [b1.id, b2.id],
});
const R = dbs.getService(ReferencesManyResolver);
await R.includeTo([c], 'modelC', 'modelB', 'parents', undefined, {
include: 'parent',
});
expect(c).to.be.eql({
id: c.id,
source: 'modelC',
parentIds: [b1.id, b2.id],
parents: [
{
id: b1.id,
source: 'modelB',
parentId: a1.id,
parent: {
id: a1.id,
source: 'modelA',
},
},
{
id: b2.id,
source: 'modelB',
parentId: a2.id,
parent: {
id: a2.id,
source: 'modelA',
},
},
],
});
});
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 target1 = await targetRep.create({featured: false});
const target2 = await targetRep.create({featured: true});
const target3 = await targetRep.create({featured: true});
expect(target1).to.be.eql({[DEF_PK]: target1[DEF_PK], featured: false});
expect(target2).to.be.eql({[DEF_PK]: target2[DEF_PK], featured: true});
expect(target3).to.be.eql({[DEF_PK]: target3[DEF_PK], featured: true});
const source = await sourceRep.create({
parentIds: [target1[DEF_PK], target2[DEF_PK], target3[DEF_PK]],
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: [target1[DEF_PK], target2[DEF_PK], target3[DEF_PK]],
});
const R = dbs.getService(ReferencesManyResolver);
await R.includeTo([source], 'source', 'target', 'parents', undefined, {
where: {and: [{featured: false}]},
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: source.parentIds,
parents: [target1],
});
delete source.parents;
await R.includeTo([source], 'source', 'target', 'parents', undefined, {
where: {and: [{featured: true}]},
});
expect(source).to.be.eql({
[DEF_PK]: source[DEF_PK],
parentIds: source.parentIds,
parents: [target2, target3],
});
});
});
});