expeditavoluptas
Version:
TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.
225 lines (181 loc) • 7.92 kB
text/typescript
import { Entity, PrimaryKey, MikroORM, ManyToOne, PrimaryKeyType, Property, wrap, OneToMany, Collection, ManyToMany } from '@mikro-orm/core';
import { SqliteDriver } from '@mikro-orm/sqlite';
()
export class Order {
()
id!: number;
(() => OrderItem, item => item.order)
items = new Collection<OrderItem>(this);
({ entity: () => Product, pivotEntity: () => OrderItem })
products = new Collection<Product>(this);
()
paid: boolean = false;
()
shipped: boolean = false;
()
created: Date = new Date();
}
()
export class Product {
()
id!: number;
()
name: string;
()
currentPrice: number;
(() => Order, o => o.products)
orders = new Collection<Order>(this);
constructor(name: string, currentPrice: number) {
this.name = name;
this.currentPrice = currentPrice;
}
}
()
export class OrderItem {
({ primary: true })
product: Product;
({ primary: true })
order: Order;
({ default: 1 })
amount!: number;
({ default: 0 })
offeredPrice: number;
[PrimaryKeyType]?: [number, number];
constructor(order: Order, product: Product) {
this.order = order;
this.product = product;
this.offeredPrice = product.currentPrice;
}
}
describe('custom pivot entity for m:n with additional properties (bidirectional, with wrong property order in pivot entity)', () => {
let orm: MikroORM;
beforeAll(async () => {
orm = await MikroORM.init({
entities: [Product, OrderItem, Order],
dbName: ':memory:',
driver: SqliteDriver,
});
await orm.schema.createSchema();
});
afterAll(() => orm.close(true));
beforeEach(() => orm.schema.clearDatabase());
test(`schema`, async () => {
const sql = await orm.schema.getCreateSchemaSQL();
expect(sql).toMatchSnapshot();
});
async function createEntities() {
const order1 = new Order();
const order2 = new Order();
const order3 = new Order();
const product1 = new Product('p1', 111);
const product2 = new Product('p2', 222);
const product3 = new Product('p3', 333);
const product4 = new Product('p4', 444);
const product5 = new Product('p5', 555);
const item11 = new OrderItem(order1, product1);
item11.offeredPrice = 123;
const item12 = new OrderItem(order1, product2);
item12.offeredPrice = 3123;
const item21 = new OrderItem(order2, product1);
item21.offeredPrice = 4123;
const item22 = new OrderItem(order2, product2);
item22.offeredPrice = 1123;
const item23 = new OrderItem(order2, product5);
item23.offeredPrice = 1263;
const item31 = new OrderItem(order3, product3);
item31.offeredPrice = 7123;
const item32 = new OrderItem(order3, product4);
item32.offeredPrice = 9123;
const item33 = new OrderItem(order3, product5);
item33.offeredPrice = 5123;
await orm.em.fork().persistAndFlush([order1, order2, order3]);
return { order1, order2, product1, product2, product3, product4, product5 };
}
test(`should work`, async () => {
const { order1, order2, product1, product2, product3, product4, product5 } = await createEntities();
const orders = await orm.em.find(Order, {}, { populate: true });
expect(orders).toHaveLength(3);
// test inverse side
const productRepository = orm.em.getRepository(Product);
let products = await productRepository.findAll();
expect(products).toBeInstanceOf(Array);
expect(products.length).toBe(5);
expect(products[0]).toBeInstanceOf(Product);
expect(products[0].name).toBe('p1');
expect(products[0].orders).toBeInstanceOf(Collection);
expect(products[0].orders.isInitialized()).toBe(true);
expect(products[0].orders.isDirty()).toBe(false);
expect(products[0].orders.count()).toBe(2);
expect(products[0].orders.length).toBe(2);
orm.em.clear();
products = await orm.em.find(Product, {});
expect(products[0].orders.isInitialized()).toBe(false);
expect(products[0].orders.isDirty()).toBe(false);
expect(() => products[0].orders.getItems()).toThrowError(/Collection<Order> of entity Product\[\d+] not initialized/);
expect(() => products[0].orders.remove(order1, order2)).toThrowError(/Collection<Order> of entity Product\[\d+] not initialized/);
expect(() => products[0].orders.removeAll()).toThrowError(/Collection<Order> of entity Product\[\d+] not initialized/);
expect(() => products[0].orders.contains(order1)).toThrowError(/Collection<Order> of entity Product\[\d+] not initialized/);
// test M:N lazy load
orm.em.clear();
products = await productRepository.findAll();
await products[0].orders.init();
expect(products[0].orders.count()).toBe(2);
expect(products[0].orders.getItems()[0]).toBeInstanceOf(Order);
expect(products[0].orders.getItems()[0].id).toBeDefined();
expect(wrap(products[0].orders.getItems()[0]).isInitialized()).toBe(true);
expect(products[0].orders.isInitialized()).toBe(true);
const old = products[0];
expect(products[1].orders.isInitialized()).toBe(false);
products = await productRepository.findAll({ populate: ['orders'] as const });
expect(products[1].orders.isInitialized()).toBe(true);
expect(products[0].id).toBe(old.id);
expect(products[0]).toBe(old);
expect(products[0].orders).toBe(old.orders);
// test M:N lazy load
orm.em.clear();
let order = (await orm.em.findOne(Order, { products: product1.id }))!;
expect(order.products.isInitialized()).toBe(false);
await order.products.init();
expect(order.products.isInitialized()).toBe(true);
expect(order.products.count()).toBe(2);
expect(order.products.getItems()[0]).toBeInstanceOf(Product);
expect(order.products.getItems()[0].id).toBeDefined();
expect(wrap(order.products.getItems()[0]).isInitialized()).toBe(true);
// test collection CRUD
// remove
expect(order.products.count()).toBe(2);
order.products.remove(t => t.id === product1.id); // we need to get reference as product1 is detached from current EM
await orm.em.persistAndFlush(order);
orm.em.clear();
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(1);
// add
order.products.add(productRepository.getReference(product1.id)); // we need to get reference as product1 is detached from current EM
const product6 = new Product('fresh', 555);
order.products.add(product6);
await orm.em.persistAndFlush(order);
orm.em.clear();
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(3);
// contains
expect(order.products.contains(productRepository.getReference(product1.id))).toBe(true);
expect(order.products.contains(productRepository.getReference(product2.id))).toBe(true);
expect(order.products.contains(productRepository.getReference(product3.id))).toBe(false);
expect(order.products.contains(productRepository.getReference(product4.id))).toBe(false);
expect(order.products.contains(productRepository.getReference(product5.id))).toBe(false);
expect(order.products.contains(productRepository.getReference(product6.id))).toBe(true);
// removeAll
order.products.removeAll();
await orm.em.persistAndFlush(order);
orm.em.clear();
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(0);
});
test(`search by m:n property and loadCount() works`, async () => {
await createEntities();
const res = await orm.em.find(Order, { products: { name: 'p1' } });
expect(res).toHaveLength(2);
const count = await res[0].products.loadCount();
expect(count).toBe(2);
});
});