Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(repository-tests): adding acceptance tests for hasManyThrough relation #5765

Merged
merged 1 commit into from
Jun 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/repository-tests
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {expect, toJSON} from '@loopback/testlab';
import {
CrudFeatures,
CrudRepositoryCtor,
CrudTestContext,
DataSourceOptions,
} from '../../..';
import {
deleteAllModelsInDefaultDataSource,
MixedIdType,
withCrudCtx,
} from '../../../helpers.repository-tests';
import {
CartItem,
CartItemRepository,
Customer,
CustomerCartItemLink,
CustomerCartItemLinkRepository,
CustomerRepository,
} from '../fixtures/models';
import {givenBoundCrudRepositories} from '../helpers';

export function hasManyThroughRelationAcceptance(
dataSourceOptions: DataSourceOptions,
repositoryClass: CrudRepositoryCtor,
features: CrudFeatures,
) {
describe('HasManyThrough relation (acceptance)', () => {
before(deleteAllModelsInDefaultDataSource);
let customerRepo: CustomerRepository;
let cartItemRepo: CartItemRepository;
let customerCartItemLinkRepo: CustomerCartItemLinkRepository;
let existingCustomerId: MixedIdType;

before(
withCrudCtx(async function setupRepository(ctx: CrudTestContext) {
({
customerRepo,
cartItemRepo,
customerCartItemLinkRepo,
} = givenBoundCrudRepositories(
ctx.dataSource,
repositoryClass,
features,
));
await ctx.dataSource.automigrate([
Customer.name,
CartItem.name,
CustomerCartItemLink.name,
]);
}),
);

beforeEach(async () => {
await customerRepo.deleteAll();
await cartItemRepo.deleteAll();
await customerCartItemLinkRepo.deleteAll();
});

beforeEach(async () => {
existingCustomerId = (await givenPersistedCustomerInstance()).id;
});

it('can create an instance of the related model alone with a through model', async () => {
const item = await customerRepo
.cartItems(existingCustomerId)
.create(
{description: 'an item'},
{throughData: {description: 'a through model'}},
);

expect(toJSON(item)).containDeep(
toJSON({
id: item.id,
description: 'an item',
}),
);

const persistedItem = await cartItemRepo.findById(item.id);
expect(toJSON(persistedItem)).to.deepEqual(toJSON(item));
const persistedLink = await customerCartItemLinkRepo.find();
expect(toJSON(persistedLink[0])).to.containDeep(
toJSON({
customerId: existingCustomerId,
cartItemId: item.id,
description: 'a through model',
}),
);
});

it('can find instances of the related model', async () => {
const item = await customerRepo
.cartItems(existingCustomerId)
.create(
{description: 'an item'},
{throughData: {description: 'a through model'}},
);
const notMyItem = await cartItemRepo.create({
description: "someone else's item",
});
const result = await customerRepo.cartItems(existingCustomerId).find();
expect(toJSON(result)).to.containDeep(toJSON([item]));
expect(toJSON(result[0])).to.not.containEql(toJSON(notMyItem));
});

it('can patch instances', async () => {
const item1 = await customerRepo
.cartItems(existingCustomerId)
.create({description: 'group 1'});
const item2 = await customerRepo
.cartItems(existingCustomerId)
.create({description: 'group 1'});
const count = await customerRepo
.cartItems(existingCustomerId)
.patch({description: 'updated'});

expect(count.count).to.equal(2);
const result = await customerRepo.cartItems(existingCustomerId).find();
expect(toJSON(result)).to.containDeep(
toJSON([
{id: item1.id, description: 'updated'},
{id: item2.id, description: 'updated'},
]),
);
});

it('can patch an instance based on the filter', async () => {
const item1 = await customerRepo
.cartItems(existingCustomerId)
.create({description: 'group 1'});
const item2 = await customerRepo
.cartItems(existingCustomerId)
.create({description: 'group 1'});
const count = await customerRepo
.cartItems(existingCustomerId)
.patch({description: 'group 2'}, {id: item2.id});
agnes512 marked this conversation as resolved.
Show resolved Hide resolved

expect(count.count).to.equal(1);
const result = await customerRepo.cartItems(existingCustomerId).find();
expect(toJSON(result)).to.containDeep(
toJSON([
{id: item1.id, description: 'group 1'},
{id: item2.id, description: 'group 2'},
]),
);
});

it('throws error when query tries to change the target id', async () => {
// a diff id for CartItem instance
const anotherId = (await givenPersistedCustomerInstance()).id;
await customerRepo
.cartItems(existingCustomerId)
.create({description: 'group 1'});

await expect(
customerRepo.cartItems(existingCustomerId).patch({id: anotherId}),
).to.be.rejectedWith(/Property "id" cannot be changed!/);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

});

it('can delete many instances and their through models', async () => {
await customerRepo
.cartItems(existingCustomerId)
.create({description: 'group 1'});
await customerRepo
.cartItems(existingCustomerId)
.create({description: 'group 1'});

let links = await customerCartItemLinkRepo.find();
let cartItems = await cartItemRepo.find();
expect(links).have.length(2);
expect(cartItems).have.length(2);

await customerRepo.cartItems(existingCustomerId).delete();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: does delete takes in a filter? Maybe add another test for delete with filter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized that the delete method has a bug. It is able to delete targets based on the filter but it deletes all through instance that has the souceKey. Will open up an issue to fix it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is being tracked in #5852

links = await customerCartItemLinkRepo.find();
cartItems = await cartItemRepo.find();
expect(links).have.length(0);
expect(cartItems).have.length(0);
});

it('can delete corresponding through models when the target gets deleted', async () => {
const item = await customerRepo
.cartItems(existingCustomerId)
.create({description: 'group 1'});
const anotherId = (await givenPersistedCustomerInstance()).id;
// another through model that links to the same item
await customerCartItemLinkRepo.create({
customerId: anotherId,
cartItemId: item.id,
});

let links = await customerCartItemLinkRepo.find();
let cartItems = await cartItemRepo.find();
expect(links).have.length(2);
expect(cartItems).have.length(1);

await customerRepo.cartItems(existingCustomerId).delete();
links = await customerCartItemLinkRepo.find();
cartItems = await cartItemRepo.find();
expect(links).have.length(0);
expect(cartItems).have.length(0);
});

//FIXME(Agnes): should be able to do deletion based on filters

it('can link a target model to a source model', async () => {
const item = await cartItemRepo.create({description: 'an item'});

let targets = await customerRepo.cartItems(existingCustomerId).find();
expect(targets).to.be.empty();

await customerRepo.cartItems(existingCustomerId).link(item.id);

targets = await customerRepo.cartItems(existingCustomerId).find();
expect(targets).to.deepEqual([item]);

const link = await customerCartItemLinkRepo.find();
expect(toJSON(link[0])).to.containEql(
toJSON({customerId: existingCustomerId, cartItemId: item.id}),
);
});

it('can unlink a target model from a source model', async () => {
const item = await customerRepo
.cartItems(existingCustomerId)
.create({description: 'an item'});

let targets = await customerRepo.cartItems(existingCustomerId).find();
expect(targets).to.deepEqual([item]);

await customerRepo.cartItems(existingCustomerId).unlink(item.id);

targets = await customerRepo.cartItems(existingCustomerId).find();
expect(targets).to.be.empty();

const link = await customerCartItemLinkRepo.find();
expect(link).to.be.empty();
});

async function givenPersistedCustomerInstance() {
return customerRepo.create({name: 'a customer'});
}
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/repository-tests
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {
Entity,
EntityCrudRepository,
model,
property,
} from '@loopback/repository';
import {MixedIdType} from '../../../../helpers.repository-tests';

@model()
export class CartItem extends Entity {
@property({
id: true,
generated: true,
useDefaultIdType: true,
})
id: MixedIdType;

@property({
type: 'string',
})
description: string;
}

export interface CartItemRelations {}

export type CartItemWithRelations = CartItem & CartItemRelations;

export interface CartItemRepository
extends EntityCrudRepository<CartItem, typeof CartItem.prototype.id> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/repository-tests
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {
Entity,
EntityCrudRepository,
model,
property,
} from '@loopback/repository';
import {MixedIdType} from '../../../../helpers.repository-tests';

@model()
export class CustomerCartItemLink extends Entity {
@property({
id: true,
generated: true,
useDefaultIdType: true,
})
id: MixedIdType;

@property()
customerId: MixedIdType;

@property()
cartItemId: MixedIdType;

@property({
type: 'string',
})
description?: string;
}

export interface CustomerCartItemLinkRelations {}

export type CustomerCartItemLinkWithRelations = CustomerCartItemLink &
CustomerCartItemLinkRelations;

export interface CustomerCartItemLinkRepository
extends EntityCrudRepository<
CustomerCartItemLink,
typeof CustomerCartItemLink.prototype.id
> {}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
EntityCrudRepository,
hasMany,
HasManyRepositoryFactory,
HasManyThroughRepositoryFactory,
hasOne,
HasOneRepositoryFactory,
model,
Expand All @@ -17,6 +18,8 @@ import {
import {BelongsToAccessor} from '@loopback/repository/src';
import {MixedIdType} from '../../../../helpers.repository-tests';
import {Address, AddressWithRelations} from './address.model';
import {CartItem, CartItemWithRelations} from './cart-item.model';
import {CustomerCartItemLink} from './customer-cart-item-link.model';
import {Order, OrderWithRelations} from './order.model';

@model()
Expand Down Expand Up @@ -44,13 +47,17 @@ export class Customer extends Entity {

@belongsTo(() => Customer)
parentId?: MixedIdType;

@hasMany(() => CartItem, {through: {model: () => CustomerCartItemLink}})
cartItems: CartItem[];
}

export interface CustomerRelations {
address?: AddressWithRelations;
orders?: OrderWithRelations[];
customers?: CustomerWithRelations[];
parentCustomer?: CustomerWithRelations;
cartItems?: CartItemWithRelations[];
}

export type CustomerWithRelations = Customer & CustomerRelations;
Expand All @@ -62,4 +69,10 @@ export interface CustomerRepository
orders: HasManyRepositoryFactory<Order, MixedIdType>;
customers: HasManyRepositoryFactory<Customer, MixedIdType>;
parent: BelongsToAccessor<Customer, MixedIdType>;
cartItems: HasManyThroughRepositoryFactory<
CartItem,
MixedIdType,
CustomerCartItemLink,
MixedIdType
>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// License text available at https://opensource.org/licenses/MIT

export * from './address.model';
export * from './cart-item.model';
export * from './customer-cart-item-link.model';
export * from './customer.model';
export * from './order.model';
export * from './shipment.model';
Loading