Skip to content

Commit

Permalink
feat(repository): transparently ensure foreign key target in inclusio…
Browse files Browse the repository at this point in the history
…n resolvers
  • Loading branch information
InvictusMB committed May 31, 2020
1 parent b9597f3 commit 655c7fe
Show file tree
Hide file tree
Showing 6 changed files with 429 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,152 @@ describe('HasMany relation', () => {
expect(orders).to.deepEqual(persistedOrders);
});

it('can include the related model when the foreign key is omitted in filter', async () => {
const order = await customerOrderRepo.create({
description: 'an order desc',
});

const customer = await customerRepo.findById(existingCustomerId, {
include: [
{
relation: 'orders',
scope: {
fields: {
description: true,
},
},
},
],
});

withProtoCheck(false, () => {
expect(customer.orders).length(1);
expect(customer.orders).to.matchEach((v: Partial<Order>) => {
expect(v).to.deepEqual({
id: undefined,
description: order.description,
customerId: undefined,
});
});
});
});

it('can include the related model when the foreign key is disabled in filter', async () => {
const order = await customerOrderRepo.create({
description: 'an order desc',
});
const customer = await customerRepo.findById(existingCustomerId, {
include: [
{
relation: 'orders',
scope: {
fields: {
customerId: false,
description: true,
},
},
},
],
});

withProtoCheck(false, () => {
expect(customer.orders).length(1);
expect(customer.orders).to.matchEach((v: Partial<Order>) => {
expect(v).to.deepEqual({
id: undefined,
description: order.description,
customerId: undefined,
});
});
});
});

it('can include the related model when only the foreign key is disabled in filter', async () => {
const order = await customerOrderRepo.create({
description: 'an order desc',
});
const customer = await customerRepo.findById(existingCustomerId, {
include: [
{
relation: 'orders',
scope: {
fields: {
customerId: false,
},
},
},
],
});

withProtoCheck(false, () => {
expect(customer.orders).length(1);
expect(customer.orders).to.matchEach((v: Partial<Order>) => {
expect(v).to.deepEqual({
id: order.id,
description: order.description,
customerId: undefined,
});
});
});
});

it('preserves the foreign key value when set in filter', async () => {
const order = await customerOrderRepo.create({
description: 'an order desc',
});
const customer = await customerRepo.findById(existingCustomerId, {
include: [
{
relation: 'orders',
scope: {
fields: {
customerId: true,
description: true,
},
},
},
],
});

withProtoCheck(false, () => {
expect(customer.orders).length(1);
expect(customer.orders).to.matchEach((v: Partial<Order>) => {
expect(v).to.deepEqual({
id: undefined,
description: order.description,
customerId: order.customerId,
});
});
});
});

it('includes only fields set in filter', async () => {
await customerOrderRepo.create({
description: 'an order desc',
});
const customer = await customerRepo.findById(existingCustomerId, {
include: [
{
relation: 'orders',
scope: {
fields: {},
},
},
],
});

withProtoCheck(false, () => {
expect(customer.orders).length(1);
expect(customer.orders).to.matchEach((v: Partial<Order>) => {
expect(v).to.deepEqual({
id: undefined,
description: undefined,
customerId: undefined,
});
});
});
});

it('finds appropriate related model instances for multiple relations', async () => {
// note(shimks): roundabout way of creating reviews with 'approves'
// ideally, the review repository should have a approve function
Expand Down Expand Up @@ -139,6 +285,14 @@ describe('HasMany relation', () => {
);

customerOrderRepo = orderFactoryFn(existingCustomerId);
const customerCrud = customerRepo as DefaultCrudRepository<
Customer,
number
>;
customerCrud.registerInclusionResolver(
'orders',
orderFactoryFn.inclusionResolver,
);
}

function givenRepositoryFactoryFunctions() {
Expand All @@ -154,6 +308,8 @@ describe('HasMany relation', () => {
});

describe('BelongsTo relation', () => {
let customer: Customer;
let order: Order;
let findCustomerOfOrder: BelongsToAccessor<
Customer,
typeof Order.prototype.id
Expand All @@ -168,30 +324,150 @@ describe('BelongsTo relation', () => {
reviewRepo.deleteAll(),
]);
});
beforeEach(givenCustomerAndOrder);

it('finds an instance of the related model', async () => {
const customer = await customerRepo.create({name: 'Order McForder'});
const order = await orderRepo.create({
customerId: customer.id,
description: 'Order from Order McForder, the hoarder of Mordor',
});

const result = await findCustomerOfOrder(order.id);

expect(result).to.deepEqual(customer);
});

it('throws EntityNotFound error when the related model does not exist', async () => {
const order = await orderRepo.create({
const orderToFail = await orderRepo.create({
customerId: 999, // does not exist
description: 'Order of a fictional customer',
});

await expect(findCustomerOfOrder(order.id)).to.be.rejectedWith(
await expect(findCustomerOfOrder(orderToFail.id)).to.be.rejectedWith(
EntityNotFoundError,
);
});

it('can include the related model when the foreign key is omitted in filter', async () => {
const orderWithRelations = (await orderRepo.findById(order.id, {
include: [
{
relation: 'customer',
scope: {
fields: {
name: true,
},
},
},
],
})) as OrderWithRelations;

withProtoCheck(false, () => {
expect(orderWithRelations.customer).to.deepEqual({
id: undefined,
name: customer.name,
orders: undefined,
reviewsApproved: undefined,
reviewsAuthored: undefined,
});
});
});

it('can include the related model when the foreign key is disabled in filter', async () => {
const orderWithRelations = (await orderRepo.findById(order.id, {
include: [
{
relation: 'customer',
scope: {
fields: {
id: false,
name: true,
},
},
},
],
})) as OrderWithRelations;

withProtoCheck(false, () => {
expect(orderWithRelations.customer).to.deepEqual({
id: undefined,
name: customer.name,
orders: undefined,
reviewsApproved: undefined,
reviewsAuthored: undefined,
});
});
});

it('can include the related model when only the foreign key is disabled in filter', async () => {
const orderWithRelations = (await orderRepo.findById(order.id, {
include: [
{
relation: 'customer',
scope: {
fields: {
id: false,
},
},
},
],
})) as OrderWithRelations;

withProtoCheck(false, () => {
expect(orderWithRelations.customer).to.deepEqual({
id: undefined,
name: customer.name,
orders: undefined,
reviewsApproved: undefined,
reviewsAuthored: undefined,
});
});
});

it('preserves the foreign key value when set in filter', async () => {
const orderWithRelations = (await orderRepo.findById(order.id, {
include: [
{
relation: 'customer',
scope: {
fields: {
id: true,
name: true,
},
},
},
],
})) as OrderWithRelations;

withProtoCheck(false, () => {
expect(orderWithRelations.customer).to.deepEqual({
id: customer.id,
name: customer.name,
orders: undefined,
reviewsApproved: undefined,
reviewsAuthored: undefined,
});
});
});

it('includes only fields set in filter', async () => {
const orderWithRelations = (await orderRepo.findById(order.id, {
include: [
{
relation: 'customer',
scope: {
fields: {},
},
},
],
})) as OrderWithRelations;

withProtoCheck(false, () => {
expect(orderWithRelations.customer).to.deepEqual({
id: undefined,
name: undefined,
orders: undefined,
reviewsApproved: undefined,
reviewsAuthored: undefined,
});
});
});

//--- HELPERS ---//

function givenAccessor() {
Expand All @@ -200,6 +476,22 @@ describe('BelongsTo relation', () => {
Getter.fromValue(customerRepo),
orderRepo,
);

const orderCrud = orderRepo as DefaultCrudRepository<Order, number>;
orderCrud.registerInclusionResolver(
'customer',
findCustomerOfOrder.inclusionResolver,
);
}

async function givenCustomerAndOrder() {
customer = await customerRepo.create({
name: 'Order McForder',
});
order = await orderRepo.create({
customerId: customer.id,
description: 'Order from Order McForder, the hoarder of Mordor',
});
}
});

Expand All @@ -225,6 +517,10 @@ class Order extends Entity {
});
}

class OrderWithRelations extends Order {
customer: Customer;
}

class Review extends Entity {
id: number;
description: string;
Expand Down Expand Up @@ -284,3 +580,14 @@ function givenCrudRepositories() {
orderRepo = new DefaultCrudRepository(Order, db);
reviewRepo = new DefaultCrudRepository(Review, db);
}

function withProtoCheck(value: boolean, fn: Function) {
const shouldJs = (expect as unknown) as {config: {checkProtoEql: boolean}};
const oldValue = shouldJs.config.checkProtoEql;
shouldJs.config.checkProtoEql = value;
try {
fn();
} finally {
shouldJs.config.checkProtoEql = oldValue;
}
}
Loading

0 comments on commit 655c7fe

Please sign in to comment.