undeexcepturi
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.
1,159 lines (1,013 loc) • 101 kB
text/typescript
/* eslint-disable dot-notation */
import { ObjectId } from 'bson';
import type { EntityProperty } from '@mikro-orm/core';
import {
Collection,
Configuration,
QueryOrder,
Reference,
wrap,
UniqueConstraintViolationException,
IdentityMap,
EntitySchema,
NullHighlighter,
FlushMode,
ref,
} from '@mikro-orm/core';
import { EntityManager, MongoConnection, MongoDriver, MongoPlatform, MikroORM } from '@mikro-orm/mongodb';
import { MongoHighlighter } from '@mikro-orm/mongo-highlighter';
import { Author, Book, BookTag, Publisher, PublisherType, Test } from './entities';
import { AuthorRepository } from './repositories/AuthorRepository';
import { closeReplSets, initORMMongo, mockLogger } from './bootstrap';
import FooBar from './entities/FooBar';
import { FooBaz } from './entities/FooBaz';
describe('EntityManagerMongo', () => {
let orm: MikroORM;
beforeAll(async () => orm = await initORMMongo());
beforeEach(async () => orm.schema.clearDatabase());
afterAll(async () => {
await orm.close();
await closeReplSets();
});
test('should load entities', async () => {
expect(orm.em).toBeInstanceOf(EntityManager);
const god = new Author('God', 'hello@heaven.god');
const bible = new Book('Bible', god);
const bible2 = new Book('Bible of wall life', god);
orm.em.persist(bible2);
await orm.em.persistAndFlush(bible);
const author = new Author('Jon Snow', 'snow@wall.st');
author.born = '2000-01-01';
author.favouriteBook = bible;
const publisher = new Publisher('7K publisher', PublisherType.GLOBAL);
const publisherRef = Reference.create(publisher);
const book1 = new Book('My Life on The Wall, part 1', author);
book1.publisher = publisherRef;
const book2 = new Book('My Life on The Wall, part 2', author);
book2.publisher = publisherRef;
const book3 = new Book('My Life on The Wall, part 3', author);
book3.publisher = publisherRef;
await orm.em.persist([book1, book2, book3]).flush();
orm.em.clear();
const publisher7k = (await orm.em.getRepository(Publisher).findOne({ name: '7K publisher' }))!;
expect(publisher7k).not.toBeNull();
expect(publisher7k.tests).toBeInstanceOf(Collection);
expect(publisher7k.tests.isInitialized()).toBe(true);
expect(publisher7k.tests.isInitialized(true)).toBe(true); // tests are eager loaded
orm.em.clear();
const authorRepository = orm.em.getRepository(Author);
const booksRepository = orm.em.getRepository(Book);
const books = await booksRepository.findAll({ populate: ['author'] });
expect(wrap(books[0].author).isInitialized()).toBe(true);
expect(await authorRepository.findOne({ favouriteBook: bible._id })).not.toBe(null);
orm.em.clear();
const noBooks = await booksRepository.find({ title: 'not existing' }, { populate: ['author'] });
expect(noBooks.length).toBe(0);
orm.em.clear();
const jon = (await authorRepository.findOne({ name: 'Jon Snow' }, { populate: ['books', 'favouriteBook'] }))!;
const authors = await authorRepository.findAll({ populate: ['books', 'favouriteBook'] });
expect(await authorRepository.findOne({ email: 'not existing' })).toBeNull();
// full text search test
const fullTextBooks2 = (await booksRepository.find({ author: god.id, $fulltext: 'life wall' }))!;
expect(fullTextBooks2.length).toBe(1);
const fullTextBooks = (await booksRepository.find({ $fulltext: 'life wall' }))!;
expect(fullTextBooks.length).toBe(4);
await expect(booksRepository.find({ title: { $fulltext: 'life wall' } })).rejects.toThrow('Full text search is only supported on the top level of the query object.');
await expect(booksRepository.find({ author: { name: { $fulltext: 'god' } } })).rejects.toThrow('Full text search is only supported on the top level of the query object.');
// count test
const count = await authorRepository.count();
expect(count).toBe(authors.length);
// identity map test
authors.shift(); // shift the god away, as that entity is detached from IM
expect(jon).toBe(authors[0]);
expect(jon).toBe(await authorRepository.findOne(jon.id));
expect(jon).toBe(await authorRepository.findOne(jon._id));
// serialization test
const o = jon.toJSON(false);
expect(o).toMatchObject({
id: jon.id,
createdAt: jon.createdAt,
updatedAt: jon.updatedAt,
books: [
{ author: jon.id, publisher: publisher.id, title: 'My Life on The Wall, part 1' },
{ author: jon.id, publisher: publisher.id, title: 'My Life on The Wall, part 2' },
{ author: jon.id, publisher: publisher.id, title: 'My Life on The Wall, part 3' },
],
favouriteBook: { author: god.id, title: 'Bible' },
born: '2000-01-01',
name: 'Jon Snow',
foo: 'bar',
});
expect(jon.toJSON(false)).toEqual(o);
expect(jon.books.getIdentifiers('_id')).toBeInstanceOf(Array);
expect(jon.books.getIdentifiers('_id')[0]).toBeInstanceOf(ObjectId);
expect(jon.books.getIdentifiers()).toBeInstanceOf(Array);
expect(typeof jon.books.getIdentifiers('id')[0]).toBe('string');
for (const author of authors) {
expect(author.books).toBeInstanceOf(Collection);
expect(author.books.isInitialized()).toBe(true);
// iterator test
for (const book of author.books) {
expect(book.title).toMatch(/My Life on The Wall, part \d/);
expect(book.author).toBeInstanceOf(Author);
expect(wrap(book.author).isInitialized()).toBe(true);
expect(book.publisher!.isInitialized()).toBe(false);
expect(typeof book.publisher!.id).toBe('string');
expect(book.publisher!._id).toBeInstanceOf(ObjectId);
expect(book.publisher!.unwrap()).toBeInstanceOf(Publisher);
expect(wrap(book.publisher!.unwrap()).isInitialized()).toBe(false);
}
}
const booksByTitleAsc = await booksRepository.find({ author: jon._id }, { orderBy: { title: QueryOrder.ASC } });
expect(booksByTitleAsc[0].title).toBe('My Life on The Wall, part 1');
expect(booksByTitleAsc[1].title).toBe('My Life on The Wall, part 2');
expect(booksByTitleAsc[2].title).toBe('My Life on The Wall, part 3');
const booksByTitleDesc = await booksRepository.find({ author: jon.id }, { orderBy: { title: 'desc' } });
expect(booksByTitleDesc[0].title).toBe('My Life on The Wall, part 3');
expect(booksByTitleDesc[1].title).toBe('My Life on The Wall, part 2');
expect(booksByTitleDesc[2].title).toBe('My Life on The Wall, part 1');
const twoBooks = await booksRepository.find({ author: jon._id }, { orderBy: { title: 'DESC' }, limit: 2 });
expect(twoBooks.length).toBe(2);
expect(twoBooks[0].title).toBe('My Life on The Wall, part 3');
expect(twoBooks[1].title).toBe('My Life on The Wall, part 2');
const lastBook = await booksRepository.find({ author: jon.id }, { populate: ['author'], orderBy: { title: -1 }, limit: 2, offset: 2 });
expect(lastBook.length).toBe(1);
expect(lastBook[0].title).toBe('My Life on The Wall, part 1');
expect(lastBook[0].author).toBeInstanceOf(Author);
expect(wrap(lastBook[0].author).isInitialized()).toBe(true);
const lastBook2 = await booksRepository.find({ author: jon.id }, {
populate: ['author'],
orderBy: { title: QueryOrder.DESC },
limit: 2,
offset: 2,
});
expect(lastBook2.length).toBe(1);
expect(lastBook[0]).toBe(lastBook2[0]);
const lastBook3 = await orm.em.find(Book, { author: jon.id }, {
populate: ['author'],
orderBy: { title: QueryOrder.DESC },
limit: 2,
offset: 2,
});
expect(lastBook3.length).toBe(1);
expect(lastBook[0]).toBe(lastBook3[0]);
await orm.em.remove(lastBook[0]).flush();
});
test('should provide custom repository', async () => {
const repo = orm.em.getRepository(Author);
expect(repo).toBeInstanceOf(AuthorRepository);
expect(repo.magic).toBeInstanceOf(Function);
expect(repo.magic('test')).toBe('111 test 222');
});
test('eager loading', async () => {
const bar = FooBar.create('fb');
bar.baz = FooBaz.create('fz');
bar.baz.book = ref(new Book('FooBar vs FooBaz'));
bar.baz.book.unwrap().author = new Author('a', 'b');
await orm.em.persistAndFlush(bar);
orm.em.clear();
const repo = orm.em.getRepository(FooBar);
const a = await repo.findOne(bar.id, { populate: ['baz.bar'] });
expect(wrap(a!.baz!).isInitialized()).toBe(true);
expect(wrap(a!.baz!.book!).isInitialized()).toBe(true);
// `baz.book` is not part of the loaded hint, but inferred via `EagerProps` symbol
expect(a!.baz!.book!.$.title).toBe('FooBar vs FooBaz');
});
test('property serializer', async () => {
const bar = FooBar.create('fb');
bar.baz = FooBaz.create('fz');
await orm.em.persistAndFlush(bar);
orm.em.clear();
const a = await orm.em.findOneOrFail(FooBar, bar.id, { populate: ['baz'] });
expect(wrap(a).toJSON()).toMatchObject({
name: 'fb',
fooBaz: 'FooBaz id: ' + bar.baz.id,
});
expect(wrap(a).toJSON().baz).toBeUndefined();
});
test(`persisting 1:1 from owning side with cycle`, async () => {
const bar = FooBar.create('fb');
const baz = FooBaz.create('fz');
bar.baz = baz;
baz.bar = ref(bar);
await orm.em.persistAndFlush(bar);
orm.em.clear();
const a = await orm.em.findOneOrFail(FooBar, bar.id);
const b = await orm.em.findOneOrFail(FooBaz, baz.id);
expect(a.baz).toBe(b);
expect(a.name).toBe('fb');
expect(b.bar.$).toBe(a);
expect(b.name).toBe('fz');
});
test(`persisting 1:1 from inverse side with cycle`, async () => {
const bar = FooBar.create('fb');
const baz = FooBaz.create('fz');
bar.baz = baz;
baz.bar = ref(bar);
await orm.em.persistAndFlush(baz);
orm.em.clear();
const a = await orm.em.findOneOrFail(FooBar, bar.id);
const b = await orm.em.findOneOrFail(FooBaz, baz.id);
expect(a.baz).toBe(b);
expect(a.name).toBe('fb');
expect(b.bar.$).toBe(a);
expect(b.name).toBe('fz');
});
test(`persisting 1:1 created via assign from owner (gh #210)`, async () => {
const bar = wrap(new FooBar()).assign({
name: 'fb',
baz: { name: 'fz' },
}, { em: orm.em });
await orm.em.persistAndFlush(bar);
orm.em.clear();
const a = await orm.em.findOneOrFail(FooBar, bar.id);
const b = await orm.em.findOneOrFail(FooBaz, bar.baz!.id);
expect(a.baz).toBe(b);
expect(a.name).toBe('fb');
expect(b.bar.$).toBe(a);
expect(b.name).toBe('fz');
});
test('unsetting 1:1 relation (GH #3233)', async () => {
const bars = [FooBar.create('fb1'), FooBar.create('fb2'), FooBar.create('fb3'), FooBar.create('fb4')];
bars[0].fooBar = bars[3];
await orm.em.persist(bars).flush();
bars[1].fooBar = bars[0].fooBar;
bars[0].fooBar = undefined;
await orm.em.flush();
bars[2].fooBar = bars[1].fooBar;
bars[1].fooBar = undefined;
await orm.em.flush();
});
test(`entity.init() and collection.init() works only for managed entities`, async () => {
const author = new Author('a', 'b');
await expect(wrap(author).init()).rejects.toThrow('Entity Author is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager.persist()');
await expect(author.books.init()).rejects.toThrow('Entity Author is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager.persist()');
});
test(`persisting 1:1 created via assign from inverse (gh #210)`, async () => {
expect(() => wrap(new FooBaz()).assign({
name: 'fz',
bar: { name: 'fb' },
})).toThrow('To use assign() on not managed entities, explicitly provide EM instance: wrap(entity).assign(data, { em: orm.em })');
const baz = wrap(new FooBaz()).assign({
name: 'fz',
bar: { name: 'fb' },
}, { em: orm.em });
await orm.em.persistAndFlush(baz);
orm.em.clear();
const a = await orm.em.findOneOrFail(FooBar, baz.bar.id);
const b = await orm.em.findOneOrFail(FooBaz, baz.id);
expect(a.baz).toBe(b);
expect(a.name).toBe('fb');
expect(b.bar.$).toBe(a);
expect(b.name).toBe('fz');
});
test('findOne should work with options parameter', async () => {
const repo = orm.em.getRepository(Author);
const author = new Author('name 1', 'email1');
const author2 = new Author('name 2', 'email2');
await orm.em.persistAndFlush([author, author2]);
orm.em.clear();
const a2 = await repo.findOne({ name: /^name/ }, {
populate: ['books'],
orderBy: { name: QueryOrder.DESC },
});
expect(a2).not.toBeNull();
expect(a2!.id).toBe(author2.id);
expect(a2!.books.isInitialized()).toBe(true);
const a1 = await repo.findOne({ name: /^name/ }, {
orderBy: { name: QueryOrder.ASC },
});
expect(a1).not.toBeNull();
expect(a1!.id).toBe(author.id);
expect(a1!.books.isInitialized()).toBe(false);
const a3 = await repo.findOne({ name: /^name/ }, { orderBy: { name: QueryOrder.ASC } });
expect(a3).toBe(a1);
});
test('should convert entity to PK when trying to search by entity', async () => {
const repo = orm.em.getRepository(Author);
const author = new Author('name', 'email');
author.favouriteAuthor = author;
await orm.em.persistAndFlush(author);
const a = await repo.findOne(author);
const authors = await repo.find({ favouriteAuthor: author });
expect(a).toBe(author);
expect(authors[0]).toBe(author);
});
test('removing not yet persisted entity will not make db call', async () => {
const author = new Author('name', 'email');
const author2 = new Author('name2', 'email2');
const author3 = new Author('name3', 'email3');
const repo = orm.em.getRepository(Author);
orm.em.persist(author);
orm.em.persist(author2);
await orm.em.removeAndFlush(author);
expect([...orm.em.getUnitOfWork().getIdentityMap().keys()]).toEqual([`Author-${author2.id}`]);
author2.name = 'lol';
orm.em.persist(author2);
orm.em.remove(author3);
await orm.em.flush();
});
test('removing persisted entity will remove it from persist stack first', async () => {
const author = new Author('name', 'email');
const repo = orm.em.getRepository(Author);
await orm.em.persistAndFlush(author);
expect(orm.em.getUnitOfWork().getById<Author>(Author.name, author.id)).toBeDefined();
author.name = 'new name';
orm.em.persist(author);
orm.em.remove(author);
expect(orm.em.getUnitOfWork().getById<Author>(Author.name, author.id)).toBeDefined();
await orm.em.flush();
expect(orm.em.getUnitOfWork().getById<Author>(Author.name, author.id)).toBeUndefined();
expect(orm.em.getUnitOfWork().getIdentityMap()).toEqual({
registry: new Map([
[Author, new Map<string, Author>()],
[Book, new Map<string, Book>()],
] as any[]),
});
});
test('removing entity will remove its FK from relations', async () => {
const author = new Author('auth', 'email');
const publisher = new Publisher('pub');
author.books.add(new Book('b1'));
author.books[0].publisher = Reference.create(publisher);
author.books.add(new Book('b2'));
author.books[1].publisher = Reference.create(publisher);
author.books.add(new Book('b3'));
author.books[2].publisher = Reference.create(publisher);
await orm.em.fork().persistAndFlush(author);
const p1 = await orm.em.findOneOrFail(Publisher, publisher, { populate: ['books'] });
orm.em.remove(p1);
await orm.em.flush();
const books = await orm.em.fork().find(Book, {});
expect(books).toHaveLength(3);
expect(books.map(b => b.publisher)).toEqual([undefined, undefined, undefined]);
});
test('removing persisted entity via PK', async () => {
const author = new Author('name', 'email');
const repo = orm.em.getRepository(Author);
await orm.em.persistAndFlush(author);
orm.em.clear();
const mock = mockLogger(orm);
await orm.em.nativeDelete(Author, author.id);
expect(mock.mock.calls[0][0]).toMatch(/db\.getCollection\('author'\)\.deleteMany\({ _id: ObjectId\('\w+'\) }, {}\)/);
});
test('should throw when trying to merge entity without id', async () => {
const author = new Author('test', 'test');
expect(() => orm.em.merge(author)).toThrow(`You cannot merge entity 'Author' without identifier!`);
});
test('fork', async () => {
const god = new Author('God', 'hello@heaven.god');
const bible = new Book('Bible', god);
await orm.em.persistAndFlush(bible);
const fork = orm.em.fork();
expect(fork).not.toBe(orm.em);
expect(fork.getMetadata()).toBe(orm.em.getMetadata());
expect(fork.getUnitOfWork().getIdentityMap()).toEqual(new IdentityMap());
// request context is not started, so we can use UoW and EF getters
expect(fork.getUnitOfWork().getIdentityMap()).not.toBe(orm.em.getUnitOfWork().getIdentityMap());
expect(fork.getEntityFactory()).not.toBe(orm.em.getEntityFactory());
const spy = jest.spyOn(EntityManager.prototype, 'getContext');
const fork2 = orm.em.fork({ disableContextResolution: true });
expect(spy).toHaveBeenCalledTimes(2);
const fork3 = orm.em.fork({ disableContextResolution: false });
expect(spy).toHaveBeenCalledTimes(5);
});
test('findOne with empty where will throw', async () => {
await expect(orm.em.findOne(Author, {})).rejects.toThrow(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`);
await expect(orm.em.findOne(Author, undefined!)).rejects.toThrow(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`);
await expect(orm.em.findOne(Author, null!)).rejects.toThrow(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`);
});
test('findOne should initialize entity that is already in IM', async () => {
const god = new Author('God', 'hello@heaven.god');
const bible = new Book('Bible', god);
await orm.em.persistAndFlush(bible);
orm.em.clear();
const ref = orm.em.getReference(Author, god.id);
expect(wrap(ref).isInitialized()).toBe(false);
const newGod = await orm.em.findOne(Author, god.id);
expect(ref).toBe(newGod);
expect(wrap(ref).isInitialized()).toBe(true);
});
test('findOne supports regexps', async () => {
const author1 = new Author('Author 1', 'a1@example.com');
const author2 = new Author('Author 2', 'a2@example.com');
const author3 = new Author('Author 3', 'a3@example.com');
await orm.em.persistAndFlush([author1, author2, author3]);
orm.em.clear();
const authors = await orm.em.find(Author, { email: /example\.com$/ });
expect(authors.length).toBe(3);
expect(authors[0].name).toBe('Author 1');
expect(authors[1].name).toBe('Author 2');
expect(authors[2].name).toBe('Author 3');
const authors2 = await orm.em.find(Author, { email: { $re: 'example.com$' } });
expect(authors2.length).toBe(3);
expect(authors2[0].name).toBe('Author 1');
expect(authors2[1].name).toBe('Author 2');
expect(authors2[2].name).toBe('Author 3');
});
test('stable results of serialization', async () => {
const god = new Author('God', 'hello@heaven.god');
const bible = new Book('Bible', god);
const bible2 = new Book('Bible pt. 2', god);
const bible3 = new Book('Bible pt. 3', new Author('Lol', 'lol@lol.lol'));
await orm.em.persistAndFlush([bible, bible2, bible3]);
orm.em.clear();
const newGod = (await orm.em.findOne(Author, god.id))!;
const books = await orm.em.find(Book, {});
await wrap(newGod).init();
for (const book of books) {
expect(book.toJSON()).toMatchObject({
author: book.author.id,
});
}
});
test('stable results of serialization (collection)', async () => {
const pub = new Publisher('Publisher');
const publisherRef = Reference.create(pub);
await orm.em.persistAndFlush(pub);
const god = new Author('God', 'hello@heaven.god');
const bible = new Book('Bible', god);
bible.publisher = publisherRef;
const bible2 = new Book('Bible pt. 2', god);
bible2.publisher = publisherRef;
const bible3 = new Book('Bible pt. 3', new Author('Lol', 'lol@lol.lol'));
bible3.publisher = publisherRef;
await orm.em.persistAndFlush([bible, bible2, bible3]);
orm.em.clear();
const newGod = orm.em.getReference(Author, god.id);
const publisher = (await orm.em.findOne(Publisher, pub.id, { populate: ['books'] }))!;
await wrap(newGod).init();
const json = wrap(publisher).toJSON().books;
for (const book of publisher.books) {
expect(json.find(b => b.id === book.id)).toMatchObject({
author: book.author.id,
});
}
});
test('should return mongo driver', async () => {
const driver = orm.em.getDriver();
expect(driver).toBeInstanceOf(MongoDriver);
expect(driver.getDependencies()).toEqual(['mongodb']);
expect(orm.config.getNamingStrategy().joinTableName('a', 'b', 'c')).toEqual('a_c');
expect(await driver.find(BookTag.name, { foo: 'bar', books: 123 }, { orderBy: {} })).toEqual([]);
expect(await driver.findOne(BookTag.name, { foo: 'bar', books: 123 })).toBeNull();
expect(await driver.findOne(BookTag.name, { foo: 'bar', books: 123 }, { orderBy: {} })).toBeNull();
expect(driver.getPlatform().usesPivotTable()).toBe(false);
expect(driver.getPlatform().usesImplicitTransactions()).toBe(false);
await expect(driver.loadFromPivotTable({} as EntityProperty, [])).rejects.toThrow('MongoDriver does not use pivot tables');
await expect(driver.getConnection().execute('')).rejects.toThrow('MongoConnection does not support generic execute method');
await expect(driver.getConnection().execute('')).rejects.toThrow('MongoConnection does not support generic execute method');
expect(driver.getConnection().getCollection(BookTag).collectionName).toBe('book-tag');
expect(orm.em.getCollection(BookTag).collectionName).toBe('book-tag');
expect(orm.em.getRepository(BookTag).getCollection().collectionName).toBe('book-tag');
expect(() => driver.getPlatform().generateCustomOrder('foo', [1, 2, 3])).toThrow();
const first = await driver.nativeInsert<Publisher>(Publisher.name, { name: 'test 123', type: PublisherType.GLOBAL });
await driver.nativeUpdate<Publisher>(Publisher.name, first.insertId, { name: 'test 456' });
await driver.nativeUpdateMany<Publisher>(Publisher.name, [first.insertId], [{ name: 'test 789' }]);
await driver.nativeDelete(Publisher.name, first.insertId);
// multi inserts
const res = await driver.nativeInsertMany(Publisher.name, [
{ name: 'test 1', type: 'GLOBAL' },
{ name: 'test 2', type: 'LOCAL' },
{ name: 'test 3', type: 'GLOBAL' },
]);
// mongo returns the persisted objects
expect(res).toMatchObject({ affectedRows: 3 });
expect(res.insertId).toBeInstanceOf(ObjectId);
expect(res.insertedIds![0]).toBeInstanceOf(ObjectId);
expect(res.insertedIds![1]).toBeInstanceOf(ObjectId);
expect(res.insertedIds![2]).toBeInstanceOf(ObjectId);
const res2 = await driver.find(Publisher.name, {});
expect(res2).toEqual([
{ _id: res.insertedIds![0], name: 'test 1', type: 'GLOBAL' },
{ _id: res.insertedIds![1], name: 'test 2', type: 'LOCAL' },
{ _id: res.insertedIds![2], name: 'test 3', type: 'GLOBAL' },
]);
await driver.nativeDelete(Publisher.name, res.rows?.[0]._id);
const count = await driver.count(Publisher.name, {});
expect(count).toBe(2);
});
test('ensure indexes', async () => {
// await orm.em.getDriver().ensureIndexes(); // executed in the init method
const conn = orm.em.getDriver().getConnection('write');
const authorInfo = await conn.getCollection('author').indexInformation({ full: true, session: undefined as any });
const bookInfo = await conn.getCollection('books-table').indexInformation({ full: true, session: undefined as any });
expect(authorInfo.reduce((o: any, i: any) => { o[i.name] = i; return o; }, {} as any)).toMatchObject({
_id_: { key: { _id: 1 }, name: '_id_' },
born_1: { key: { born: 1 }, name: 'born_1' },
custom_idx_1: { key: { email: 1, name: 1 }, name: 'custom_idx_1' },
age_uniq: { key: { age: 1 }, name: 'age_uniq', unique: true, partialFilterExpression: { age: { $exists: true } } },
email_1: { key: { email: 1 }, name: 'email_1', unique: true },
});
expect(bookInfo.reduce((o: any, i: any) => { o[i.name] = i; return o; }, {} as any)).toMatchObject({
'_id_': { key: { _id: 1 }, name: '_id_' },
'publisher_idx': { key: { publisher: 1 }, name: 'publisher_idx' },
'title_text': { key: { _fts: 'text', _ftsx: 1 }, weights: { title: 1 } },
'title_1_author_1': { key: { title: 1, author: 1 }, name: 'title_1_author_1', unique: true },
'point_2dsphere': { key: { point: '2dsphere' }, name: 'point_2dsphere' },
'point_2dsphere_title_-1': { key: { point: '2dsphere', title: -1 }, name: 'point_2dsphere_title_-1' },
});
});
test('should use user and password as connection options', async () => {
const config = new Configuration({ driver: MongoDriver, user: 'usr', password: 'pw' } as any, false);
const connection = new MongoConnection(config);
await expect(connection.getConnectionOptions()).toEqual({
auth: { username: 'usr', password: 'pw' },
});
});
test('using $exists operator', async () => {
await orm.em.insert(Author, { name: 'n', email: 'e' });
await orm.em.findOneOrFail(Author, { foo: { $exists: false } });
});
test('connection returns correct URL', async () => {
const conn1 = new MongoConnection(new Configuration({
driver: MongoDriver,
clientUrl: 'mongodb://example.host.com:34500',
dbName: 'test-db-name',
user: 'usr',
password: 'pw',
} as any, false));
await expect(conn1.getClientUrl()).toBe('mongodb://usr:*****@example.host.com:34500');
const conn2 = new MongoConnection(new Configuration({ driver: MongoDriver } as any, false));
await expect(conn2.getClientUrl()).toBe('mongodb://127.0.0.1:27017');
const clientUrl = 'mongodb://user:Q#ais@2d-Aa_43:ui!0d.ai6d@mongodb-replicaset-0.cluster.local:27017,mongodb-replicaset-1.cluster.local:27018,...';
const conn3 = new MongoConnection(new Configuration({ driver: MongoDriver, clientUrl } as any, false));
await expect(conn3.getClientUrl()).toBe('mongodb://user:*****@mongodb-replicaset-0.cluster.local:27017,mongodb-replicaset-1.cluster.local:27018,...');
const conn4 = new MongoConnection(new Configuration({ driver: MongoDriver, clientUrl: 'invalid-url-that-was-not-properly-parsed' } as any, false));
await expect(conn4.getClientUrl()).toBe('invalid-url-that-was-not-properly-parsed');
});
test('json properties', async () => {
const god = new Author('God', 'hello@heaven.god');
god.identities = ['fb-123', 'pw-231', 'tw-321'];
const bible = new Book('Bible', god);
bible.metaObject = { category: 'god like', items: 3, valid: true, nested: { foo: '123', bar: 321, deep: { baz: 59, qux: false } } };
await orm.em.persistAndFlush(bible);
orm.em.clear();
const g = await orm.em.findOneOrFail(Author, god.id, { populate: ['books'] });
expect(Array.isArray(g.identities)).toBe(true);
expect(g.identities).toEqual(['fb-123', 'pw-231', 'tw-321']);
expect(typeof g.books[0].metaObject).toBe('object');
expect(g.books[0].metaObject).toEqual({ category: 'god like', items: 3, valid: true, nested: { foo: '123', bar: 321, deep: { baz: 59, qux: false } } });
orm.em.clear();
const b1 = await orm.em.findOneOrFail(Book, { metaObject: { category: 'god like' } });
const b2 = await orm.em.findOneOrFail(Book, { metaObject: { category: 'god like', items: 3 } });
const b3 = await orm.em.findOneOrFail(Book, { metaObject: { nested: { bar: 321 } } });
const b4 = await orm.em.findOneOrFail(Book, { metaObject: { nested: { foo: '123', bar: 321 } } });
const b5 = await orm.em.findOneOrFail(Book, { metaObject: { valid: true, nested: { foo: '123', bar: 321 } } });
const b6 = await orm.em.findOneOrFail(Book, { metaObject: { valid: true, nested: { foo: '123', bar: 321, deep: { baz: 59 } } } });
const b7 = await orm.em.findOneOrFail(Book, { metaObject: { valid: true, nested: { foo: '123', bar: 321, deep: { baz: 59, qux: false } } } });
expect(b1).toBe(b2);
expect(b1).toBe(b3);
expect(b1).toBe(b4);
expect(b1).toBe(b5);
expect(b1).toBe(b6);
expect(b1).toBe(b7);
});
test('findOne by id', async () => {
const authorRepository = orm.em.getRepository(Author);
const jon = new Author('Jon Snow', 'snow@wall.st');
await orm.em.persistAndFlush(jon);
orm.em.clear();
let author = (await authorRepository.findOne(jon._id))!;
expect(author).not.toBeNull();
expect(author.name).toBe('Jon Snow');
orm.em.clear();
author = (await authorRepository.findOne(jon.id))!;
expect(author).not.toBeNull();
expect(author.name).toBe('Jon Snow');
orm.em.clear();
author = (await authorRepository.findOne({ id: jon.id }))!;
expect(author).not.toBeNull();
expect(author.name).toBe('Jon Snow');
orm.em.clear();
author = (await authorRepository.findOne({ _id: jon._id }))!;
expect(author).not.toBeNull();
expect(author.name).toBe('Jon Snow');
});
test('populate ManyToOne relation via init()', async () => {
const authorRepository = orm.em.getRepository(Author);
const god = new Author('God', 'hello@heaven.god');
const bible = new Book('Bible', god);
await orm.em.persistAndFlush(bible);
let jon = new Author('Jon Snow', 'snow@wall.st');
jon.born = '1990-03-23';
jon.favouriteBook = bible;
await orm.em.persistAndFlush(jon);
orm.em.clear();
jon = (await authorRepository.findOne(jon.id))!;
expect(jon).not.toBeNull();
expect(jon.name).toBe('Jon Snow');
expect(jon.born).toEqual('1990-03-23');
expect(jon.favouriteBook).toBeInstanceOf(Book);
expect(wrap(jon.favouriteBook!).isInitialized()).toBe(false);
await wrap(jon.favouriteBook!).init();
expect(jon.favouriteBook).toBeInstanceOf(Book);
expect(wrap(jon.favouriteBook!).isInitialized()).toBe(true);
expect(jon.favouriteBook?.title).toBe('Bible');
});
test('many to many relation', async () => {
const mock = mockLogger(orm);
const author = new Author('Jon Snow', 'snow@wall.st');
const book1 = new Book('My Life on The Wall, part 1', author);
const book2 = new Book('My Life on The Wall, part 2', author);
const book3 = new Book('My Life on The Wall, part 3', author);
const tag1 = new BookTag('silly');
const tag2 = new BookTag('funny');
const tag3 = new BookTag('sick');
const tag4 = new BookTag('strange');
const tag5 = new BookTag('sexy');
book1.tags.add(tag1, tag3, tag3);
book2.tags.add(tag1, tag2, tag5);
book3.tags.add(tag2, tag4, tag5);
orm.em.persist(book1);
orm.em.persist(book2);
await orm.em.persistAndFlush(book3);
expect(tag1._id).toBeDefined();
expect(tag2._id).toBeDefined();
expect(tag3._id).toBeDefined();
expect(tag4._id).toBeDefined();
expect(tag5._id).toBeDefined();
expect(book1.tags.toArray()).toEqual([wrap(tag1).toJSON(), wrap(tag3).toJSON()]);
expect(book1.tags.toJSON()).toEqual([wrap(tag1).toJSON(), wrap(tag3).toJSON()]);
// ensure we don't have separate update queries for collection sync
expect(mock.mock.calls).toHaveLength(3);
expect(mock.mock.calls[0][0]).toMatch(`db.getCollection('book-tag').insertMany(`);
expect(mock.mock.calls[1][0]).toMatch(`db.getCollection('author').insertMany(`);
expect(mock.mock.calls[2][0]).toMatch(`db.getCollection('books-table').insertMany(`);
orm.em.clear();
// test inverse side
const tagRepository = orm.em.getRepository(BookTag);
{
const tags = await tagRepository.findAll({ populate: ['books'] });
expect(tags).toBeInstanceOf(Array);
expect(tags.length).toBe(5);
expect(tags[0]).toBeInstanceOf(BookTag);
expect(tags[0].name).toBe('silly');
expect(tags[0].books).toBeInstanceOf(Collection);
expect(tags[0].books.isInitialized()).toBe(true);
expect(tags[0].books.isDirty()).toBe(false);
expect(tags[0].books.count()).toBe(2);
expect(tags[0].books.length).toBe(2);
}
orm.em.clear();
let tags = await orm.em.find(BookTag, {});
expect(tags[0].books.isInitialized()).toBe(false);
expect(tags[0].books.isDirty()).toBe(false);
expect(() => tags[0].books.getItems()).toThrow(/Collection<Book> of entity BookTag\[\w{24}] not initialized/);
expect(() => tags[0].books.remove(book1, book2)).toThrow(/Collection<Book> of entity BookTag\[\w{24}] not initialized/);
expect(() => tags[0].books.contains(book1)).toThrow(/Collection<Book> of entity BookTag\[\w{24}] not initialized/);
// test M:N lazy load
orm.em.clear();
tags = await orm.em.find(BookTag, {});
await tags[0].books.init();
expect(tags[0].books.count()).toBe(2);
expect(tags[0].books.getItems()[0]).toBeInstanceOf(Book);
expect(tags[0].books.getItems()[0]._id).toBeDefined();
expect(wrap(tags[0].books.getItems()[0]).isInitialized()).toBe(true);
// test M:N lazy load
orm.em.clear();
let book = await orm.em.findOneOrFail(Book, { tags: tag1._id });
expect(book.tags.isInitialized()).toBe(true); // owning side is always initialized
expect(book.tags.count()).toBe(2);
expect(book.tags.getItems()[0]).toBeInstanceOf(BookTag);
expect(book.tags.getItems()[0]._id).toBeDefined();
expect(wrap(book.tags.getItems()[0]).isInitialized()).toBe(false);
const initSpy = jest.spyOn(Collection.prototype, 'init');
const items2 = await book.tags.loadItems();
expect(initSpy).toHaveBeenCalledTimes(1);
expect(book.tags.getItems()).toEqual(items2);
const items3 = await book.tags.loadItems();
expect(initSpy).toHaveBeenCalledTimes(1);
expect(items3).toEqual(items2);
// test collection CRUD
// remove
expect(book.tags.count()).toBe(2);
book.tags.remove(tagRepository.getReference(tag1.id), tagRepository.getReference(tag5.id)); // tag5 will be ignored as it is not part of collection
await orm.em.persistAndFlush(book);
orm.em.clear();
book = (await orm.em.findOne(Book, book._id))!;
expect(book.tags.count()).toBe(1);
// add
book.tags.add(tagRepository.getReference(tag1.id)); // we need to get reference as tag1 is detached from current EM
await orm.em.persistAndFlush(book);
orm.em.clear();
book = (await orm.em.findOne(Book, book._id))!;
expect(book.tags.count()).toBe(2);
// set
const items = book.tags.getIdentifiers().map(t => tagRepository.getReference(t));
book.tags.set(items);
await orm.em.persistAndFlush(book);
orm.em.clear();
book = (await orm.em.findOne(Book, book._id))!;
expect(book.tags.count()).toBe(2);
// slice
expect(book.tags.slice()).toEqual(items);
expect(book.tags.slice(0, 2)).toEqual(items);
expect(book.tags.slice(1)).toEqual([items[1], items[2]]);
expect(book.tags.slice(0, 1)).toEqual([items[0]]);
// contains
expect(book.tags.contains(tagRepository.getReference(tag1.id))).toBe(true);
expect(book.tags.contains(tagRepository.getReference(tag2.id))).toBe(false);
expect(book.tags.contains(tagRepository.getReference(tag3.id))).toBe(true);
expect(book.tags.contains(tagRepository.getReference(tag4.id))).toBe(false);
expect(book.tags.contains(tagRepository.getReference(tag5.id))).toBe(false);
// removeAll
book.tags.removeAll();
await orm.em.persistAndFlush(book);
orm.em.clear();
book = (await orm.em.findOne(Book, book._id))!;
expect(book.tags.count()).toBe(0);
expect(book.tags.isEmpty()).toBe(true);
});
test('populating many to many relation', async () => {
const p1 = new Publisher('foo');
expect(p1.tests).toBeInstanceOf(Collection);
expect(p1.tests.isInitialized()).toBe(true);
expect(p1.tests.isDirty()).toBe(false);
expect(p1.tests.count()).toBe(0);
const p2 = new Publisher('bar');
p2.tests.add(new Test({ name: 't1' }), new Test({ name: 't2' }));
await orm.em.persistAndFlush([p1, p2]);
const repo = orm.em.getRepository(Publisher);
orm.em.clear();
const publishers = await repo.findAll({ populate: ['tests'], orderBy: { id: 1 } });
expect(publishers).toBeInstanceOf(Array);
expect(publishers.length).toBe(2);
expect(publishers[0]).toBeInstanceOf(Publisher);
expect(publishers[0].tests).toBeInstanceOf(Collection);
expect(publishers[0].tests.isInitialized(true)).toBe(true);
expect(publishers[0].tests.isDirty()).toBe(false);
expect(publishers[0].tests.count()).toBe(0);
await publishers[0].tests.init(); // empty many to many on owning side should not make db calls
expect(wrap(publishers[1].tests.getItems()[0]).isInitialized()).toBe(true);
orm.em.clear();
const publishers2 = await repo.findAll({ populate: ['tests:ref'], orderBy: { id: 1 } });
expect(publishers2).toBeInstanceOf(Array);
expect(publishers2.length).toBe(2);
expect(publishers2[0]).toBeInstanceOf(Publisher);
expect(publishers2[0].tests).toBeInstanceOf(Collection);
expect(publishers2[0].tests.isInitialized()).toBe(true);
expect(publishers2[0].tests.isInitialized(true)).toBe(true); // empty collection
expect(publishers2[0].tests.isDirty()).toBe(false);
expect(publishers2[0].tests.count()).toBe(0);
expect(publishers2[1].tests.isInitialized(true)).toBe(false); // collection with references only
expect(wrap(publishers2[1].tests[0]).isInitialized()).toBe(false);
orm.em.clear();
const publishers3 = await repo.findAll({ populate: ['tests:ref'], strategy: 'joined', orderBy: { id: 1 } });
expect(publishers3).toBeInstanceOf(Array);
expect(publishers3.length).toBe(2);
expect(publishers3[0]).toBeInstanceOf(Publisher);
expect(publishers3[0].tests).toBeInstanceOf(Collection);
expect(publishers3[0].tests.isInitialized()).toBe(true);
expect(publishers3[0].tests.isInitialized(true)).toBe(true); // empty collection
expect(publishers3[0].tests.isDirty()).toBe(false);
expect(publishers3[0].tests.count()).toBe(0);
expect(publishers3[1].tests.isInitialized(true)).toBe(false); // collection with references only
expect(wrap(publishers3[1].tests[0]).isInitialized()).toBe(false);
orm.em.clear();
const publishers4 = await repo.findAll({ orderBy: { id: 1 }, populate: false });
await orm.em.populate(publishers4, ['tests:ref']);
expect(publishers4).toBeInstanceOf(Array);
expect(publishers4.length).toBe(2);
expect(publishers4[0]).toBeInstanceOf(Publisher);
expect(publishers4[0].tests).toBeInstanceOf(Collection);
expect(publishers4[0].tests.isInitialized()).toBe(true);
expect(publishers4[0].tests.isInitialized(true)).toBe(true); // empty collection
expect(publishers4[0].tests.isDirty()).toBe(false);
expect(publishers4[0].tests.count()).toBe(0);
expect(publishers4[1].tests.isInitialized(true)).toBe(false); // collection with references only
expect(wrap(publishers4[1].tests[0]).isInitialized()).toBe(false);
orm.em.clear();
const publishers5 = await repo.findAll({ orderBy: { id: 1 } });
await publishers5[0].tests.init({ ref: true });
await publishers5[1].tests.init({ ref: true });
expect(publishers5).toBeInstanceOf(Array);
expect(publishers5.length).toBe(2);
expect(publishers5[0]).toBeInstanceOf(Publisher);
expect(publishers5[0].tests).toBeInstanceOf(Collection);
expect(publishers5[0].tests.isInitialized()).toBe(true);
expect(publishers5[0].tests.isInitialized(true)).toBe(true); // empty collection
expect(publishers5[0].tests.isDirty()).toBe(false);
expect(publishers5[0].tests.count()).toBe(0);
expect(publishers5[1].tests.isInitialized(true)).toBe(false); // collection with references only
expect(wrap(publishers5[1].tests[0]).isInitialized()).toBe(false);
});
test('many to many relation (ref: true)', async () => {
const author = new Author('Jon Snow', 'snow@wall.st');
const book1 = new Book('My Life on The Wall, part 1', author);
const book2 = new Book('My Life on The Wall, part 2', author);
const book3 = new Book('My Life on The Wall, part 3', author);
const tag1 = new BookTag('silly');
const tag2 = new BookTag('funny');
const tag3 = new BookTag('sick');
const tag4 = new BookTag('strange');
const tag5 = new BookTag('sexy');
book1.tags.add(tag1, tag3);
book2.tags.add(tag1, tag2, tag5);
book3.tags.add(tag2, tag4, tag5);
await orm.em.persistAndFlush([book1, book2, book3]);
orm.em.clear();
const bt1 = await orm.em.findOneOrFail(BookTag, tag1.id, { populate: ['books:ref'] });
expect(bt1.books.isInitialized()).toBe(true);
expect(bt1.books.isInitialized(true)).toBe(false);
expect(wrap(bt1.books[0]).isInitialized()).toBe(false);
});
test('populating many to many relation on inverse side', async () => {
const author = new Author('Jon Snow', 'snow@wall.st');
const book1 = new Book('My Life on The Wall, part 1', author);
const book2 = new Book('My Life on The Wall, part 2', author);
const book3 = new Book('My Life on The Wall, part 3', author);
const tag1 = new BookTag('silly');
const tag2 = new BookTag('funny');
const tag3 = new BookTag('sick');
const tag4 = new BookTag('strange');
const tag5 = new BookTag('sexy');
book1.tags.add(tag1, tag3);
book2.tags.add(tag1, tag2, tag5);
book3.tags.add(tag2, tag4, tag5);
await orm.em.persistAndFlush([book1, book2, book3]);
const repo = orm.em.getRepository(BookTag);
orm.em.clear();
await repo.findOne(tag5.id, { populate: ['books'] }); // preload one of collections to test it is not re-loaded
const tags = await repo.findAll({ populate: ['books'] });
expect(tags).toBeInstanceOf(Array);
expect(tags.length).toBe(5);
expect(tags[0]).toBeInstanceOf(BookTag);
expect(tags[0].books).toBeInstanceOf(Collection);
expect(tags[0].books.isInitialized()).toBe(true);
expect(tags[0].books.isDirty()).toBe(false);
expect(tags[0].books.count()).toBe(2);
expect(wrap(tags[0].books.getItems()[0]).isInitialized()).toBe(true);
});
test('serializing empty initialized many to many collection', async () => {
let a = new Author('name', 'email');
await orm.em.persistAndFlush(a);
expect(a.toJSON()).toMatchObject({
books: [],
});
orm.em.clear();
a = await orm.em.findOneOrFail(Author, a.id, { populate: ['books'] });
expect(a.toJSON()).toMatchObject({
books: [],
});
});
test('merging detached entity', async () => {
const author = new Author('Jon Snow', 'snow@wall.st');
const book1 = new Book('My Life on The Wall, part 1', author);
const book2 = new Book('My Life on The Wall, part 2', author);
const book3 = new Book('My Life on The Wall, part 3', author);
author.favouriteBook = book1;
const tag1 = new BookTag('silly');
const tag2 = new BookTag('funny');
const tag3 = new BookTag('sick');
const tag4 = new BookTag('strange');
const tag5 = new BookTag('sexy');
book1.tags.add(tag1, tag3);
book2.tags.add(tag1, tag2, tag5);
book3.tags.add(tag2, tag4, tag5);
await orm.em.persistAndFlush([book1, book2, book3]);
orm.em.clear();
// cache author with favouriteBook and its tags
const jon = await orm.em.findOneOrFail(Author, author.id, { populate: ['favouriteBook.tags'] });
const cache = wrap(jon).toObject();
// merge cached author with his references
orm.em.clear();
const cachedAuthor = orm.em.create(Author, cache, { managed: true });
expect(cachedAuthor).toBe(cachedAuthor.favouriteBook?.author);
expect(orm.em.getUnitOfWork().getIdentityMap().keys()).toEqual([
'Author-' + author.id,
'Book-' + book1.id,
'BookTag-' + tag1.id,
'BookTag-' + tag3.id,
]);
expect(author).not.toBe(cachedAuthor);
expect(author.id).toBe(cachedAuthor.id);
const book4 = new Book('My Life on The Wall, part 4', cachedAuthor);
await orm.em.persistAndFlush(book4);
// merge detached author
orm.em.clear();
const cachedAuthor2 = orm.em.merge(author);
expect(cachedAuthor2).toBe(cachedAuthor2.favouriteBook?.author);
expect([...orm.em.getUnitOfWork().getIdentityMap().keys()]).toEqual([
'Author-' + author.id,
'Book-' + book1.id,
'Book-' + book2.id,
'Book-' + book3.id,
'BookTag-' + tag1.id,
'BookTag-' + tag2.id,
'BookTag-' + tag4.id,
'BookTag-' + tag5.id,
'BookTag-' + tag3.id,
]);
expect(author).toBe(cachedAuthor2);
expect(author.id).toBe(cachedAuthor2.id);
const book5 = new Book('My Life on The Wall, part 5', cachedAuthor2);
await orm.em.persistAndFlush(book5);
});
test('one to many collection sets inverse side reference after adding', async () => {
const author = new Author('Jon Snow', 'snow@wall.st');
const book1 = new Book('My Life on The Wall, part 1');
const book2 = new Book('My Life on The Wall, part 2');
const book3 = new Book('My Life on The Wall, part 3');
author.books.add(book1, book2, book3);
expect(book1.author).toBe(author);
expect(book3.author).toBe(author);
expect(book3.author).toBe(author);
});
test('many to many collection sets inverse side reference after adding', async () => {
const book1 = new Book('My Life on The Wall, part 1');
const book2 = new Book('My Life on The Wall, part 2');
const book3 = new Book('My Life on The Wall, part 3');
const tag1 = new BookTag('silly');
const tag2 = new BookTag('funny');
const tag3 = new BookTag('sick');
book1.tags.add(tag1, tag3);
book2.tags.add(tag1, tag2);
book3.tags.add(tag2);
expect(tag1.books[0]).toBe(book1);
expect(tag1.books[1]).toBe(book2);
expect(tag1.books.length).toBe(2);
expect(tag2.books[0]).toBe(book2);
expect(tag2.books[1]).toBe(book3);
expect(tag2.books.length).toBe(2);
expect(tag3.books[0]).toBe(book1);
expect(tag3.books.length).toBe(1);
});
test('many to many collection sets owning side reference after adding', async () => {
const book1 = new Book('My Life on The Wall, part 1');
const book2 = new Book('My Life on The Wall, part 2');
const book3 = new Book('My Life on The Wall, part 3');
const tag1 = new BookTag('silly');
const tag2 = new BookTag('funny');
const tag3 = new BookTag('sick');
tag1.books.add(book1, book2);
tag2.books.add(book2, book3);
tag3.books.add(book1);
expect(tag1.books[0]).toBe(book1);
expect(tag1.books[1]).toBe(book2);
expect(tag1.books.length).toBe(2);
expect(tag2.books[0]).toBe(book2);
expect(tag2.books[1]).toBe(book3);
expect(tag2.books.length).toBe(2);
expect(tag3.books[0]).toBe(book1);
expect(tag3.books.length).toBe(1);
});
test('cascade persist on owning side', async () => {
const author = new Author('Jon Snow', 'snow@wall.st');
const book1 = new Book('My Life on The Wall, part 1');
const book2 = new Book('My Life on The Wall, part 2');
const book3 = new Book('My Life on The Wall, part 3');
author.books.add(book1, book2, book3);
const tag1 = new BookTag('silly');
const tag2 = new BookTag('funny');
const tag3 = new BookTag('sick');
const tag4 = new BookTag('strange');
const tag5 = new BookTag('sexy');
book1.tags.add(tag1, tag3);
book2.tags.add(tag1, tag2, tag5);
book3.tags.add(tag2, tag4, tag5);
await orm.em.persistAndFlush(author);
orm.em.clear();
const repo = orm.em.getRepository(Book);
let book = (await repo.findOne(book1.id, { populate: ['author', 'tags'] }))!;
book.author.name = 'Foo Bar';
book.tags[0].name = 'new name 1';
book.tags[1].name = 'new name 2';
await orm.em.persistAndFlush(book);
orm.em.clear();
book = (await repo.findOne(book1.id, { populate: ['author', 'tags'] }))!;
expect(book.author.name).toBe('Foo Bar');
expect(book.tags[0].name).toBe('new name 1');
expect(book.tags[1].name).toBe('new name 2');
});
test('cascade persist on inverse side', async () => {
const author = new Author('Jon Snow', 'snow@wall.st');
const book1 = new Book('My Life on The Wall, part 1', author);
const book2 = new Book('My Life on The Wall, part 2', author);
const book3 = new Book('My Life on The Wall, part 3', author);
const tag1 = new BookTag('silly');
const tag2 = new BookTag('funny');
const tag3 = new BookTag('sick');
const tag4 = new BookTag('strange');
const tag5 = new BookTag('sexy');
book1.tags.add(tag1, tag3);
book2.tags.add(tag1, tag2, tag5);
book3.tags.add(tag2, tag4, tag5);
await orm.em.persistAndFlush([book1, book2, book3]);
orm.em.clear();
const repo = orm.em.getRepository(BookTag);
let tag = (await repo.findOne(tag5.id, { populate: ['books.author'] }))!;
tag.books[0].title = 'new title 1';
tag.books[1].title = 'new title 2';
tag.books[1].author.name = 'Foo Bar';
await orm.em.persistAndFlush(tag);
orm.em.clear();
tag = (await repo.findOne(tag5.id, { populate: ['books.author'] }))!;
expect(tag.books[0].title).toBe('new title 1');
expect(tag.books[1].title).toBe('new title 2');
expect(tag.books[1].author.name).toBe('Foo Bar');
});
test('cascade remove on 1:m collection', async () => {
const author = new Author('Jon Snow', 'snow@wall.st');
const book1 = new Book('My Life on The Wall, part 1', author);
const book2 =