Skip to content

Commit

Permalink
fixup! apply feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
nabdelgadir committed Aug 13, 2019
1 parent e746763 commit f455a65
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import {
} from '../../../relations';

describe('relation helpers', () => {
describe('findByForeignKeys', () => {
let productRepo: ProductRepository;
let productRepo: ProductRepository;
let categoryRepo: CategoryRepository;

before(() => {
productRepo = new ProductRepository(testdb);
});
before(() => {
productRepo = new ProductRepository(testdb);
categoryRepo = new CategoryRepository(testdb, async () => productRepo);
});

describe('findByForeignKeys', () => {
beforeEach(async () => {
await productRepo.deleteAll();
});
Expand Down Expand Up @@ -133,19 +135,16 @@ describe('relation helpers', () => {
});

describe('includeRelatedModels', () => {
let productRepo: ProductRepository;
let categoryRepo: CategoryRepository;

before(() => {
productRepo = new ProductRepository(testdb);
categoryRepo = new CategoryRepository(testdb, async () => productRepo);
});

beforeEach(async () => {
await productRepo.deleteAll();
await categoryRepo.deleteAll();
});

it("defines a repository's inclusionResolvers property", () => {
expect(categoryRepo.inclusionResolvers).to.not.be.undefined();
expect(productRepo.inclusionResolvers).to.not.be.undefined();
});

it('returns source model if no filter is passed in', async () => {
const category = await categoryRepo.create({name: 'category 1'});
await categoryRepo.create({name: 'category 2'});
Expand All @@ -154,34 +153,21 @@ describe('relation helpers', () => {
});

it('throws error if the target repository does not have the registered resolver', async () => {
let errorMessage, errorCode;
try {
const category = await categoryRepo.create({name: 'category 1'});
await productRepo.create({id: 1, name: 'product1', categoryId: 1});
// no resolvers registered here
await includeRelatedModels(categoryRepo, [category], {
const category = await categoryRepo.create({name: 'category 1'});
await productRepo.create({id: 1, name: 'product1', categoryId: 1});
await expect(
includeRelatedModels(categoryRepo, [category], {
include: [{relation: 'products'}],
});
} catch (error) {
errorCode = error.code;
errorMessage = error.message;
}
expect(errorCode).to.eql('INVALID_INCLUSION_FILTER');
expect(errorMessage).to.eql(
'Invalid "filter.include" entries: {"relation":"products"}',
}),
).to.be.rejectedWith(
/Invalid "filter.include" entries: {"relation":"products"}/,
);
});

it('returns an empty array if target model of the source entity does not have any matched instances', async () => {
const category = await categoryRepo.create({name: 'category'});
// stubbed resolver
const resolver: InclusionResolver = async entities => {
const c = entities[0] as Category;
const product: Product[] = await categoryRepo.products(c.id).find();
return [product];
};

categoryRepo.inclusionResolvers.set('products', resolver);
categoryRepo.inclusionResolvers.set('products', hasManyResolver);

const categories = await includeRelatedModels(categoryRepo, [category], {
include: [{relation: 'products'}],
Expand All @@ -196,17 +182,8 @@ describe('relation helpers', () => {
name: 'product',
categoryId: category.id,
});
// stubbed resolver
const resolver: InclusionResolver = async entities => {
const p = entities[0] as Product;
const categories: Category[] = [
await categoryRepo.findById(p.categoryId),
];

return categories;
};

productRepo.inclusionResolvers.set('category', resolver);
productRepo.inclusionResolvers.set('category', belongsToResolver);

const productWithCategories = await includeRelatedModels(
productRepo,
Expand All @@ -216,9 +193,10 @@ describe('relation helpers', () => {
},
);

expect(toJSON(productWithCategories)).to.deepEqual([
{...toJSON(product), category: toJSON(category)},
]);
expect(productWithCategories[0].toJSON()).to.deepEqual({
...product.toJSON(),
category: category.toJSON(),
});
});

it('includes related model for more than one instance - belongsTo', async () => {
Expand All @@ -238,20 +216,8 @@ describe('relation helpers', () => {
name: 'product 3',
categoryId: categoryTwo.id,
});
// stubbed resolver
const resolver: InclusionResolver = async entities => {
const categories: Category[] = [];

for (const entity of entities) {
const p = entity as Product;
const c = await categoryRepo.findById(p.categoryId);
categories.push(c);
}

return categories;
};

productRepo.inclusionResolvers.set('category', resolver);
productRepo.inclusionResolvers.set('category', belongsToResolver);

const productWithCategories = await includeRelatedModels(
productRepo,
Expand All @@ -262,9 +228,9 @@ describe('relation helpers', () => {
);

expect(toJSON(productWithCategories)).to.deepEqual([
{...toJSON(productOne), category: toJSON(categoryOne)},
{...toJSON(productTwo), category: toJSON(categoryTwo)},
{...toJSON(productThree), category: toJSON(categoryTwo)},
{...productOne.toJSON(), category: categoryOne.toJSON()},
{...productTwo.toJSON(), category: categoryTwo.toJSON()},
{...productThree.toJSON(), category: categoryTwo.toJSON()},
]);
});

Expand All @@ -279,18 +245,8 @@ describe('relation helpers', () => {
name: 'product 2',
categoryId: category.id,
});
// stubbed resolver
const resolver: InclusionResolver = async entities => {
let products: Product[] = [];

for (const entity of entities) {
const c = entity as Category;
products = await categoryRepo.products(c.id).find();
}
return [products];
};

categoryRepo.inclusionResolvers.set('products', resolver);
categoryRepo.inclusionResolvers.set('products', hasManyResolver);

const categoryWithProducts = await includeRelatedModels(
categoryRepo,
Expand All @@ -300,8 +256,8 @@ describe('relation helpers', () => {

expect(toJSON(categoryWithProducts)).to.deepEqual([
{
...toJSON(category),
products: [toJSON(productOne), toJSON(productTwo)],
...category.toJSON(),
products: [productOne.toJSON(), productTwo.toJSON()],
},
]);
});
Expand All @@ -325,18 +281,7 @@ describe('relation helpers', () => {
categoryId: categoryTwo.id,
});

const resolver: InclusionResolver = async entities => {
const products: Product[][] = [];

for (const entity of entities) {
const c = entity as Category;
const p = await categoryRepo.products(c.id).find();
products.push(p);
}
return products;
};

categoryRepo.inclusionResolvers.set('products', resolver);
categoryRepo.inclusionResolvers.set('products', hasManyResolver);

const categoryWithProducts = await includeRelatedModels(
categoryRepo,
Expand All @@ -345,18 +290,45 @@ describe('relation helpers', () => {
);

expect(toJSON(categoryWithProducts)).to.deepEqual([
{...toJSON(categoryOne), products: [toJSON(productOne)]},
{...categoryOne.toJSON(), products: [productOne.toJSON()]},
{
...toJSON(categoryTwo),
products: [toJSON(productTwo), toJSON(productThree)],
...categoryTwo.toJSON(),
products: [productTwo.toJSON(), productThree.toJSON()],
},
{...toJSON(categoryThree), products: []},
{...categoryThree.toJSON(), products: []},
]);
});
});

/******************* HELPERS *******************/

// stubbed resolvers

const belongsToResolver: InclusionResolver = async entities => {
const categories: Category[] = [];

for (const entity of entities) {
const p = entity as Product;
const c = await categoryRepo.findById(p.categoryId);
categories.push(c);
}

return categories;
};

const hasManyResolver: InclusionResolver = async entities => {
const products: Product[][] = [];

for (const entity of entities) {
const c = entity as Category;
const p = await categoryRepo.products(c.id).find();
products.push(p);
}
return products;
};

// models and repositories

@model()
class Product extends Entity {
@property({id: true})
Expand All @@ -377,13 +349,13 @@ describe('relation helpers', () => {
>;
constructor(
dataSource: juggler.DataSource,
categoryRepo?: Getter<CategoryRepository>,
categoryRepository?: Getter<CategoryRepository>,
) {
super(Product, dataSource);
if (categoryRepo)
if (categoryRepository)
this.category = this.createBelongsToAccessorFor(
'category',
categoryRepo,
categoryRepository,
);
}
}
Expand Down Expand Up @@ -412,12 +384,12 @@ describe('relation helpers', () => {
>;
constructor(
dataSource: juggler.DataSource,
productRepo: Getter<ProductRepository>,
productRepository: Getter<ProductRepository>,
) {
super(Category, dataSource);
this.products = this.createHasManyRepositoryFactoryFor(
'products',
productRepo,
productRepository,
);
}
}
Expand Down
27 changes: 18 additions & 9 deletions packages/repository/src/relations/relation.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,23 +83,25 @@ export async function includeRelatedModels<
if (!include) return result;

const invalidInclusions = include.filter(
i => !isInclusionAllowed(targetRepository, i),
inclusionFilter => !isInclusionAllowed(targetRepository, inclusionFilter),
);
if (invalidInclusions.length) {
const msg =
'Invalid "filter.include" entries: ' +
invalidInclusions.map(i => JSON.stringify(i)).join('; ');
invalidInclusions
.map(inclusionFilter => JSON.stringify(inclusionFilter))
.join('; ');
const err = new Error(msg);
Object.assign(err, {
code: 'INVALID_INCLUSION_FILTER',
});
throw err;
}

const resolveTasks = include.map(async i => {
const relationName = i.relation;
const resolveTasks = include.map(async inclusionFilter => {
const relationName = inclusionFilter.relation;
const resolver = targetRepository.inclusionResolvers.get(relationName)!;
const targets = await resolver(entities, i, options);
const targets = await resolver(entities, inclusionFilter, options);

for (const ix in result) {
const src = result[ix];
Expand All @@ -120,14 +122,21 @@ export async function includeRelatedModels<
*/
function isInclusionAllowed<T extends Entity, Relations extends object = {}>(
targetRepository: EntityCrudRepository<T, unknown, Relations>,
inclusion: Inclusion,
inclusionFilter: Inclusion,
): boolean {
const relationName = inclusion.relation;
const relationName = inclusionFilter.relation;
if (!relationName) {
debug('isInclusionAllowed for %j? No: missing relation name', inclusion);
debug(
'isInclusionAllowed for %j? No: missing relation name',
inclusionFilter,
);
return false;
}
const allowed = targetRepository.inclusionResolvers.has(relationName);
debug('isInclusionAllowed for %j (relation %s)? %s', inclusion, allowed);
debug(
'isInclusionAllowed for %j (relation %s)? %s',
inclusionFilter,
allowed,
);
return allowed;
}

0 comments on commit f455a65

Please sign in to comment.