diff --git a/src/mongoose.repository.ts b/src/mongoose.repository.ts index 3f88585..254c6b2 100644 --- a/src/mongoose.repository.ts +++ b/src/mongoose.repository.ts @@ -44,53 +44,24 @@ export abstract class MongooseRepository> this.entityModel = this.createEntityModel(connection); } - /** @inheritdoc */ - async deleteById(id: string, options?: DeleteByIdOptions): Promise { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); - if (!id) throw new IllegalArgumentException('The given ID must be valid'); - const isDeleted = await this.entityModel.findByIdAndDelete(id, { - session: options?.session, - }); - return !!isDeleted; - } - - /** @inheritdoc */ - async findAll(options?: FindAllOptions): Promise { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); - if (options?.pageable?.pageNumber && options?.pageable?.pageNumber < 0) { - throw new IllegalArgumentException( - 'The given page number must be a positive number', + private createEntityModel(connection?: Connection) { + let entityModel; + const supertypeData = this.typeMap.getSupertypeData(); + if (connection) { + entityModel = connection.model( + supertypeData.type.name, + supertypeData.schema, ); - } - if (options?.pageable?.offset && options?.pageable?.offset < 0) { - throw new IllegalArgumentException( - 'The given page offset must be a positive number', + } else { + entityModel = mongoose.model( + supertypeData.type.name, + supertypeData.schema, ); } - - const offset = options?.pageable?.offset ?? 0; - const pageNumber = options?.pageable?.pageNumber ?? 0; - try { - const documents = await this.entityModel - .find(options?.filters) - .skip(pageNumber > 0 ? (pageNumber - 1) * offset : 0) - .limit(offset) - .sort(options?.sortBy) - .session(options?.session ?? null) - .exec(); - return documents.map((document) => this.instantiateFrom(document) as S); - } catch (error) { - throw new IllegalArgumentException( - 'The given optional parameters must be valid', - error, - ); + for (const subtypeData of this.typeMap.getSubtypesData()) { + entityModel.discriminator(subtypeData.type.name, subtypeData.schema); } + return entityModel; } /** @inheritdoc */ @@ -132,6 +103,42 @@ export abstract class MongooseRepository> return Optional.ofNullable(this.instantiateFrom(document) as S); } + /** @inheritdoc */ + async findAll(options?: FindAllOptions): Promise { + if (options?.connection) + console.warn( + 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', + ); + if (options?.pageable?.pageNumber && options?.pageable?.pageNumber < 0) { + throw new IllegalArgumentException( + 'The given page number must be a positive number', + ); + } + if (options?.pageable?.offset && options?.pageable?.offset < 0) { + throw new IllegalArgumentException( + 'The given page offset must be a positive number', + ); + } + + const offset = options?.pageable?.offset ?? 0; + const pageNumber = options?.pageable?.pageNumber ?? 0; + try { + const documents = await this.entityModel + .find(options?.filters) + .skip(pageNumber > 0 ? (pageNumber - 1) * offset : 0) + .limit(offset) + .sort(options?.sortBy) + .session(options?.session ?? null) + .exec(); + return documents.map((document) => this.instantiateFrom(document) as S); + } catch (error) { + throw new IllegalArgumentException( + 'The given optional parameters must be valid', + error, + ); + } + } + /** @inheritdoc */ async save( entity: S | PartialEntityWithId, @@ -142,7 +149,9 @@ export abstract class MongooseRepository> 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', ); if (!entity) - throw new IllegalArgumentException('The given entity must be valid'); + throw new IllegalArgumentException( + 'The given entity cannot be null or undefined', + ); try { if (!entity.id) { return await this.insert(entity as S, options); @@ -163,49 +172,6 @@ export abstract class MongooseRepository> } } - /** - * Instantiates a persistable domain object from the given Mongoose Document. - * @param {HydratedDocument | null} document the given Mongoose Document. - * @returns {S | null} the resulting persistable domain object instance. - * @throws {UndefinedConstructorException} if there is no constructor available. - */ - protected instantiateFrom( - document: HydratedDocument | null, - ): S | null { - if (!document) return null; - const entityKey = document.get('__t'); - const constructor: Constructor | undefined = entityKey - ? (this.typeMap.getSubtypeData(entityKey)?.type as Constructor) - : (this.typeMap.getSupertypeData().type as Constructor); - if (constructor) { - // safe instantiation as no abstract class instance can be stored in the first place - return new constructor(document.toObject()); - } - throw new UndefinedConstructorException( - `There is no registered instance constructor for the document with ID ${document.id}`, - ); - } - - private createEntityModel(connection?: Connection) { - let entityModel; - const supertypeData = this.typeMap.getSupertypeData(); - if (connection) { - entityModel = connection.model( - supertypeData.type.name, - supertypeData.schema, - ); - } else { - entityModel = mongoose.model( - supertypeData.type.name, - supertypeData.schema, - ); - } - for (const subtypeData of this.typeMap.getSubtypesData()) { - entityModel.discriminator(subtypeData.type.name, subtypeData.schema); - } - return entityModel; - } - /** * Inserts an entity. * @param {S} entity the entity to insert. @@ -222,7 +188,9 @@ export abstract class MongooseRepository> 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', ); if (!entity) - throw new IllegalArgumentException('The given entity must be valid'); + throw new IllegalArgumentException( + 'The given entity cannot be null or undefined', + ); const entityClassName = entity['constructor']['name']; if (!this.typeMap.has(entityClassName)) { throw new IllegalArgumentException( @@ -293,4 +261,40 @@ export abstract class MongooseRepository> `There is no document matching the given ID '${entity.id}'`, ); } + + /** @inheritdoc */ + async deleteById(id: string, options?: DeleteByIdOptions): Promise { + if (options?.connection) + console.warn( + 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', + ); + if (!id) throw new IllegalArgumentException('The given ID must be valid'); + const isDeleted = await this.entityModel.findByIdAndDelete(id, { + session: options?.session, + }); + return !!isDeleted; + } + + /** + * Instantiates a persistable domain object from the given Mongoose Document. + * @param {HydratedDocument | null} document the given Mongoose Document. + * @returns {S | null} the resulting persistable domain object instance. + * @throws {UndefinedConstructorException} if there is no constructor available. + */ + protected instantiateFrom( + document: HydratedDocument | null, + ): S | null { + if (!document) return null; + const entityKey = document.get('__t'); + const constructor: Constructor | undefined = entityKey + ? (this.typeMap.getSubtypeData(entityKey)?.type as Constructor) + : (this.typeMap.getSupertypeData().type as Constructor); + if (constructor) { + // safe instantiation as no abstract class instance can be stored in the first place + return new constructor(document.toObject()); + } + throw new UndefinedConstructorException( + `There is no registered instance constructor for the document with ID ${document.id}`, + ); + } } diff --git a/test/repository/book.repository.test.ts b/test/repository/book.repository.test.ts index 9d81667..27eab56 100644 --- a/test/repository/book.repository.test.ts +++ b/test/repository/book.repository.test.ts @@ -945,7 +945,7 @@ describe('Given an instance of book repository', () => { it('throws an exception', async () => { await expect( bookRepository.save(undefined as unknown as Book), - ).rejects.toThrow('The given entity must be valid'); + ).rejects.toThrow(IllegalArgumentException); }); }); @@ -953,7 +953,7 @@ describe('Given an instance of book repository', () => { it('throws an exception', async () => { await expect( bookRepository.save(null as unknown as Book), - ).rejects.toThrow('The given entity must be valid'); + ).rejects.toThrow(IllegalArgumentException); }); }); @@ -1097,24 +1097,44 @@ describe('Given an instance of book repository', () => { expect(book.id).toBe(storedBook.id); expect(book.title).toBe(storedBook.title); expect(book.description).toBe(bookToUpdate.description); + expect(book.isbn).toBe(storedBook.isbn); }); }); }); + describe('and that specifies all the contents of the supertype', () => { - it('updates the book', async () => { - const bookToUpdate = bookFixture( - { - title: 'Continuous Delivery', - description: - 'Boost your development productivity via automation', - }, - storedBook.id, - ); + describe('and some field values are invalid', () => { + it('throws an exception', async () => { + const bookToUpdate = audioBookFixture( + { + title: undefined, + }, + storedBook.id, + ); + + await expect(bookRepository.save(bookToUpdate)).rejects.toThrow( + ValidationException, + ); + }); + }); + + describe('and all field values are valid', () => { + it('updates the book', async () => { + const bookToUpdate = bookFixture( + { + title: 'Continuous Delivery', + description: + 'Boost your development productivity via automation', + }, + storedBook.id, + ); - const book = await bookRepository.save(bookToUpdate); - expect(book.id).toBe(bookToUpdate.id); - expect(book.title).toBe(bookToUpdate.title); - expect(book.description).toBe(bookToUpdate.description); + const book = await bookRepository.save(bookToUpdate); + expect(book.id).toBe(bookToUpdate.id); + expect(book.title).toBe(bookToUpdate.title); + expect(book.description).toBe(bookToUpdate.description); + expect(book.isbn).toBe(bookToUpdate.isbn); + }); }); }); });