UNPKG

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.

273 lines (237 loc) 13 kB
import { ObjectId } from 'bson'; import type { MikroORM, EntityFactory } from '@mikro-orm/core'; import { Collection, ReferenceKind, wrap } from '@mikro-orm/core'; import { Book, Author, Publisher, Test, BookTag } from './entities'; import { initORMMongo, mockLogger } from './bootstrap'; import { AuthorRepository } from './repositories/AuthorRepository'; import { BookRepository } from './repositories/BookRepository'; describe('EntityFactory', () => { let orm: MikroORM; let factory: EntityFactory; beforeAll(async () => { orm = await initORMMongo(); factory = orm.em.getEntityFactory(); expect(orm.config.getNamingStrategy().referenceColumnName()).toBe('_id'); }); beforeEach(async () => orm.schema.clearDatabase()); afterAll(async () => orm.close(true)); test('should load entities', async () => { const metadata = orm.getMetadata().getAll(); expect(metadata).toBeInstanceOf(Object); expect(metadata[Author.name]).toBeInstanceOf(Object); expect(metadata[Author.name].path).toBe('./entities/Author.ts'); expect(metadata[Author.name].toJsonParams).toEqual(['strict', 'strip']); expect(metadata[Author.name].properties).toBeInstanceOf(Object); expect(metadata[Author.name].properties.books.type).toBe(Book.name); expect(metadata[Author.name].properties.books.kind).toBe(ReferenceKind.ONE_TO_MANY); expect(metadata[Author.name].properties.foo.type).toBe('string'); expect(metadata[Author.name].properties.age.type).toBe('number'); expect(metadata[Author.name].properties.age.nullable).toBe(true); // nullable is sniffed via ts-morph too expect(metadata[Author.name].repository()).toBe(AuthorRepository); expect(metadata[Book.name].properties.author.type).toBe(Author.name); expect(metadata[Book.name].properties.author.kind).toBe(ReferenceKind.MANY_TO_ONE); expect(metadata[Book.name].repository()).toBe(BookRepository); expect(metadata[Publisher.name].properties.tests.owner).toBe(true); }); test('should return reference', async () => { const ref = factory.createReference(Book, '5b0d19b28b21c648c2c8a600'); expect(ref).toBeInstanceOf(Book); expect(ref._id).toBeInstanceOf(ObjectId); expect(ref.id).toBe('5b0d19b28b21c648c2c8a600'); expect(ref.title).toBeUndefined(); expect(ref.tags).toBeUndefined(); expect(ref.toJSON()).toEqual({ id: '5b0d19b28b21c648c2c8a600' }); }); test('should return entity', async () => { const entity = factory.create(Author, { id: '5b0d19b28b21c648c2c8a600', name: 'test', email: 'mail@test.com', books: { title: 'asd' } }); expect(entity).toBeInstanceOf(Author); expect(entity.id).toBe('5b0d19b28b21c648c2c8a600'); expect(entity.name).toBe('test'); expect(entity.email).toBe('mail@test.com'); expect(entity.books.isInitialized()).toBe(true); expect(entity.books).toHaveLength(1); expect(entity.books[0].title).toBe('asd'); }); test('should return embeddable', async () => { // we are testing this on Author entity as it does not really matter whether it is an embeddable type or not const data = { id: '5b0d19b28b21c648c2c8a600', name: 'test', email: 'mail@test.com', books: { title: 'asd' } }; const managedEntity = factory.createEmbeddable(Author, data); expect(managedEntity).toBeInstanceOf(Author); expect(managedEntity._id).toBeUndefined(); expect(managedEntity.name).toBeUndefined(); expect(managedEntity.email).toBeUndefined(); expect(managedEntity.books).toBeUndefined(); expect(managedEntity.books).toBeUndefined(); const newEntity = factory.createEmbeddable(Author, data, { newEntity: true }); expect(newEntity).toBeInstanceOf(Author); // not available, as we are not hydrating here, just filling constructor parameters expect(newEntity._id).toBeUndefined(); expect(newEntity.name).toBe('test'); expect(newEntity.email).toBe('mail@test.com'); expect(newEntity.books.isInitialized()).toBe(true); // books are not assigned either, again we just care about creating the instance, while filling the constructor parameters expect(newEntity.books).toHaveLength(0); }); test('entity ctor can have different params than props', async () => { const entity = factory.create(Test, { name: 'test' }); expect(entity).toBeInstanceOf(Test); expect(entity._id).toBeUndefined(); expect(entity.name).toBe('test'); }); test('should return entity without id', async () => { const author = factory.create(Author, { name: 'test', favouriteBook: '5b0d19b28b21c648c2c8a600', email: 'mail@test.com' }); expect(author).toBeInstanceOf(Author); expect(author.id).toBeNull(); expect(author.name).toBe('test'); expect(author.email).toBe('mail@test.com'); expect(author.favouriteBook).toBeInstanceOf(Book); expect(author.favouriteBook!.id).toBe('5b0d19b28b21c648c2c8a600'); }); test('should return entity without id [reference as constructor parameter]', async () => { // we need to use normal entity manager to have working identity map const author = orm.em.getReference(Author, '5b0d19b28b21c648c2c8a600'); expect(author.id).toBe('5b0d19b28b21c648c2c8a600'); const book = orm.em.create(Book, { title: 'book title', author: author.id }); expect(book).toBeInstanceOf(Book); expect(book.id).toBeNull(); expect(book.title).toBe('book title'); expect(book.author).toBe(author); // try with id of entity that is not managed const book2 = orm.em.create(Book, { title: 'book title', author: '5b0d19b28b21c648c2c8a601' }); expect(book2).toBeInstanceOf(Book); expect(book2.id).toBeNull(); expect(book2.title).toBe('book title'); expect(book2.author).toBeInstanceOf(Author); expect(book2.author.id).toBe('5b0d19b28b21c648c2c8a601'); }); test('create should create entity without calling constructor', async () => { const p1 = new Publisher(); // calls constructor, so uses default name expect(p1.name).toBe('asd'); expect(p1).toBeInstanceOf(Publisher); expect(p1.books).toBeInstanceOf(Collection); expect(p1.tests).toBeInstanceOf(Collection); const p2 = factory.create(Publisher, { id: '5b0d19b28b21c648c2c8a601' }); expect(p2).toBeInstanceOf(Publisher); expect(p2.name).toBeUndefined(); expect(p2.books).toBeInstanceOf(Collection); expect(p2.tests).toBeInstanceOf(Collection); const p3 = factory.create(Publisher, { id: '5b0d19b28b21c648c2c8a602', name: 'test' }); expect(p3).toBeInstanceOf(Publisher); expect(p3.name).toBe('test'); expect(p3.books).toBeInstanceOf(Collection); expect(p3.tests).toBeInstanceOf(Collection); }); test('create should create entity without calling constructor', async () => { const p1 = new Publisher(); // calls constructor, so uses default name expect(p1.name).toBe('asd'); expect(p1).toBeInstanceOf(Publisher); expect(p1.books).toBeInstanceOf(Collection); expect(p1.tests).toBeInstanceOf(Collection); const p2 = factory.createReference(Publisher, '5b0d19b28b21c648c2c8a600'); expect(p2).toBeInstanceOf(Publisher); expect(p2.name).toBeUndefined(); expect(p1.books).toBeInstanceOf(Collection); expect(p1.tests).toBeInstanceOf(Collection); }); test('create return entity without hydrating it if it is already an entity', async () => { const p1 = new Publisher(); expect(p1.name).toBe('asd'); const p2 = factory.create(Publisher, p1); expect(p2).toBe(p1); }); test('create does not merge entity instances', async () => { const a1 = new Author('n', 'e'); a1.id = '5b0d19b28b21c648c2c8a600'; const t1 = new BookTag('t1'); t1.id = '5b0d19b28b21c648c2c8a601'; // managed entity have an internal __em reference, so that is what we are testing here expect(wrap(a1, true).__em).toBeUndefined(); expect(wrap(t1, true).__em).toBeUndefined(); const b1 = factory.create(Book, { author: a1, tags: [t1] }); expect(wrap(a1, true).__em).toBeUndefined(); expect(wrap(t1, true).__em).toBeUndefined(); expect(wrap(b1, true).__em).toBeUndefined(); }); test('create should ignore invalid reference values', async () => { const a = factory.create(Author, { favouriteAuthor: false } as any); expect(a).toBeInstanceOf(Author); expect(a.name).toBeUndefined(); expect(a.favouriteAuthor).toBeUndefined(); }); test('create works with entity constructor param', async () => { const author = new Author('n', 'e'); const book = factory.create(Book, { title: 't', author }); expect(book.author).toBe(author); }); test('create should flag collections as dirty for new entities', async () => { const a = new Author('n', 'e'); const t1 = new BookTag('t1'); const t2 = new BookTag('t2'); const t3 = new BookTag('t3'); const b1 = factory.create(Book, { title: 'b1', author: a, tags: [t1, t2, t3] }, { newEntity: false }); expect(b1.tags.isDirty()).toBe(false); const b2 = factory.create(Book, { title: 'b2', author: a, tags: [t1, t2, t3] }, { newEntity: true }); expect(b2.tags.isDirty()).toBe(true); }); test('create entity from nested object', async () => { const repo = orm.em.getRepository(Author); const a1 = repo.create({ name: 'Jon', email: 'jon@snow.com', books: [ { title: 'B1', publisher: '5b0d19b28b21c648c2c8a600', tags: [{ name: 't1' }, '5b0d19b28b21c648c2c8a601'] }, ] }); expect(a1.name).toBe('Jon'); expect(a1.email).toBe('jon@snow.com'); expect(a1.books.isInitialized()).toBe(true); expect(a1.books.isDirty()).toBe(true); expect(a1.books[0].title).toBe('B1'); expect(a1.books[0].author).toBe(a1); // propagation to owning side expect(a1.books[0].publisher!.id).toBe('5b0d19b28b21c648c2c8a600'); expect(wrap(a1.books[0].publisher!).isInitialized()).toBe(false); expect(a1.books[0].tags.isInitialized()).toBe(true); expect(a1.books[0].tags.isDirty()).toBe(true); // owning side expect(a1.books[0].tags[0].name).toBe('t1'); expect(a1.books[0].tags[0].id).toBe(null); expect(a1.books[0].tags[1].id).toBe('5b0d19b28b21c648c2c8a601'); const mock = mockLogger(orm); await orm.em.persistAndFlush(a1); expect(mock.mock.calls).toHaveLength(3); expect(mock.mock.calls[0][0]).toMatch(/db\.getCollection\('book-tag'\)\.insertMany\(\[ { name: 't1' } ], {}\);/); expect(mock.mock.calls[1][0]).toMatch(/db\.getCollection\('author'\)\.insertMany\(\[ { createdAt: ISODate\('.*'\), updatedAt: ISODate\('.*'\), foo: 'bar', name: 'Jon', email: 'jon@snow\.com', termsAccepted: false } ], {}\);/); expect(mock.mock.calls[2][0]).toMatch(/db\.getCollection\('books-table'\)\.insertMany\(\[ { createdAt: ISODate\('.*'\), title: 'B1', author: ObjectId\('.*'\), publisher: ObjectId\('5b0d19b28b21c648c2c8a600'\), tags: \[ ObjectId\('.*'\), ObjectId\('5b0d19b28b21c648c2c8a601'\) ] } ], {}\);/); orm.em.clear(); mock.mock.calls.length = 0; const a2 = repo.create({ name: 'Jon', email: 'jon2@snow.com' }); repo.assign(a2, { books: [ { title: 'B1', publisher: '5b0d19b28b21c648c2c8a600', tags: [{ name: 't1' }, '5b0d19b28b21c648c2c8a601'] }, ] }); expect(a2.name).toBe('Jon'); expect(a2.email).toBe('jon2@snow.com'); expect(a2.books.isInitialized()).toBe(true); expect(a2.books.isDirty()).toBe(true); expect(a2.books[0].title).toBe('B1'); expect(a2.books[0].author).toBe(a2); // propagation to owning side expect(a2.books[0].publisher!.id).toBe('5b0d19b28b21c648c2c8a600'); expect(wrap(a2.books[0].publisher!).isInitialized()).toBe(false); expect(a2.books[0].tags.isInitialized()).toBe(true); expect(a2.books[0].tags.isDirty()).toBe(true); // owning side expect(a2.books[0].tags[0].name).toBe('t1'); expect(a2.books[0].tags[0].id).toBe(null); expect(a2.books[0].tags[1].id).toBe('5b0d19b28b21c648c2c8a601'); await orm.em.persistAndFlush(a2); expect(mock.mock.calls.length).toBe(3); expect(mock.mock.calls[0][0]).toMatch(/db\.getCollection\('book-tag'\)\.insertMany\(\[ { name: 't1' } ], {}\);/); expect(mock.mock.calls[1][0]).toMatch(/db\.getCollection\('author'\)\.insertMany\(\[ { createdAt: ISODate\('.*'\), updatedAt: ISODate\('.*'\), foo: 'bar', name: 'Jon', email: 'jon2@snow\.com', termsAccepted: false } ], {}\);/); expect(mock.mock.calls[2][0]).toMatch(/db\.getCollection\('books-table'\)\.insertMany\(\[ { createdAt: ISODate\('.*'\), title: 'B1', author: ObjectId\('.*'\), publisher: ObjectId\('5b0d19b28b21c648c2c8a600'\), tags: \[ ObjectId\('.*'\), ObjectId\('5b0d19b28b21c648c2c8a601'\) ] } ], {}\);/); }); test('em.create() should not mutate the input object (GH issue 1294)', async () => { const data = { name: 'this is my name', email: 'e', age: 21, }; const entity = orm.em.create(Author, data); expect(data.name).toEqual('this is my name'); expect(data.age).toEqual(21); expect(entity.name).toEqual('this is my name'); expect(entity.age).toEqual(21); }); });