diff --git a/src/main/kotlin/io/github/bayang/jelu/dao/BookRepository.kt b/src/main/kotlin/io/github/bayang/jelu/dao/BookRepository.kt index 5f49a0a7..8341e71e 100644 --- a/src/main/kotlin/io/github/bayang/jelu/dao/BookRepository.kt +++ b/src/main/kotlin/io/github/bayang/jelu/dao/BookRepository.kt @@ -263,24 +263,46 @@ class BookRepository( if (!book.title.isNullOrBlank()) { updated.title = book.title.trim() } - updated.isbn10 = book.isbn10?.trim() - updated.isbn13 = book.isbn13?.trim() - updated.pageCount = book.pageCount - updated.publisher = book.publisher?.trim() + book.isbn10?.let { + updated.isbn10 = book.isbn10.trim() + } + book.isbn13?.let { + updated.isbn13 = book.isbn13.trim() + } + book.pageCount?.let { + updated.pageCount = book.pageCount + } + book.publisher?.let { + updated.publisher = book.publisher.trim() + } if (!book.summary.isNullOrBlank()) { updated.summary = sanitizeHtml(book.summary) - } else { - updated.summary = book.summary } // image must be set when saving file succeeds - updated.publishedDate = book.publishedDate?.trim() - updated.series = book.series?.trim() - updated.numberInSeries = book.numberInSeries - updated.amazonId = book.amazonId?.trim() - updated.goodreadsId = book.goodreadsId?.trim() - updated.googleId = book.googleId?.trim() - updated.librarythingId = book.librarythingId?.trim() - updated.language = book.language?.trim() + book.publishedDate?.let { + updated.publishedDate = book.publishedDate.trim() + } + book.series?.let { + updated.series = book.series.trim() + } + book.numberInSeries?.let { + updated.numberInSeries = book.numberInSeries + } + book.amazonId?.let { + updated.amazonId = book.amazonId.trim() + } + book.goodreadsId?.let { + updated.goodreadsId = book.goodreadsId.trim() + } + book.googleId?.let { + updated.googleId = book.googleId.trim() + } + book.librarythingId?.let { + updated.librarythingId = book.librarythingId.trim() + } + book.language?.let { + updated.language = book.language.trim() + } updated.modificationDate = nowInstant() val authorsList = mutableListOf() book.authors?.forEach { @@ -333,7 +355,9 @@ class BookRepository( if (book.owned != null) { found.owned = book.owned } - found.personalNotes = book.personalNotes?.trim() + book.personalNotes?.let { + found.personalNotes = book.personalNotes.trim() + } if (book.toRead != null) { found.toRead = book.toRead } @@ -361,16 +385,36 @@ class BookRepository( if (!author.name.isNullOrBlank()) { found.name = author.name.trim() } - found.biography = author.biography?.trim() - found.dateOfDeath = author.dateOfDeath?.trim() - found.dateOfBirth = author.dateOfBirth?.trim() - found.notes = author.notes?.trim() - found.officialPage = author.officialPage?.trim() - found.wikipediaPage = author.wikipediaPage?.trim() - found.goodreadsPage = author.goodreadsPage?.trim() - found.twitterPage = author.twitterPage?.trim() - found.facebookPage = author.facebookPage?.trim() - found.instagramPage = author.instagramPage?.trim() + author.biography?.let { + found.biography = author.biography.trim() + } + author.dateOfDeath?.let { + found.dateOfDeath = author.dateOfDeath.trim() + } + author.dateOfBirth?.let { + found.dateOfBirth = author.dateOfBirth.trim() + } + author.notes?.let { + found.notes = author.notes.trim() + } + author.officialPage?.let { + found.officialPage = author.officialPage.trim() + } + author.wikipediaPage?.let { + found.wikipediaPage = author.wikipediaPage.trim() + } + author.goodreadsPage?.let { + found.goodreadsPage = author.goodreadsPage.trim() + } + author.twitterPage?.let { + found.twitterPage = author.twitterPage.trim() + } + author.facebookPage?.let { + found.facebookPage = author.facebookPage.trim() + } + author.instagramPage?.let { + found.instagramPage = author.instagramPage.trim() + } // found.image = author.image?.trim() found.modificationDate = nowInstant() return found @@ -537,6 +581,10 @@ class BookRepository( Tag[tagId].delete() } + /** + * Removes an author from a book without deleting the author from the database. + * Used to clean the composite table + */ fun deleteAuthorFromBook(bookId: UUID, authorId: UUID) { BookAuthors.deleteWhere { BookAuthors.book eq bookId and(BookAuthors.author eq authorId) diff --git a/src/main/kotlin/io/github/bayang/jelu/dao/ReadingEventRepository.kt b/src/main/kotlin/io/github/bayang/jelu/dao/ReadingEventRepository.kt index 05760b6e..e50d89a4 100644 --- a/src/main/kotlin/io/github/bayang/jelu/dao/ReadingEventRepository.kt +++ b/src/main/kotlin/io/github/bayang/jelu/dao/ReadingEventRepository.kt @@ -74,11 +74,15 @@ class ReadingEventRepository { val alreadyReadingEvent: ReadingEvent? = userBook.readingEvents.find { it.eventType == ReadingEventType.CURRENTLY_READING } val instant: Instant = nowInstant() - if (userBook.lastReadingEvent != null) { - if (createReadingEventDto.eventDate != null && createReadingEventDto.eventDate.isAfter(userBook.lastReadingEventDate)) { + + // we have a previous event, + // only update lastReadingEvent if the new one has a date and is effectively after the one already existing + if (userBook.lastReadingEventDate != null && createReadingEventDto.eventDate != null) { + if (createReadingEventDto.eventDate.isAfter(userBook.lastReadingEventDate)) { userBook.lastReadingEvent = createReadingEventDto.eventType userBook.lastReadingEventDate = createReadingEventDto.eventDate } + // no previous event, or the new one does not have a date set lastReadingEvent in any case } else { userBook.lastReadingEvent = createReadingEventDto.eventType userBook.lastReadingEventDate = createReadingEventDto.eventDate ?: instant @@ -90,7 +94,7 @@ class ReadingEventRepository { alreadyReadingEvent.modificationDate = createReadingEventDto.eventDate ?: instant return alreadyReadingEvent } - // else : alreadyReadingEvent is not null and createReadingEventDto.eventDate is before an already existing CURRENTLY_READING EVENT + // FIXME else : alreadyReadingEvent is not null and createReadingEventDto.eventDate is before an already existing CURRENTLY_READING EVENT // this could create CURRENTLY_READING events in the past // should we allow this ? Let's wait and see for the moment, user can edit events in bookdetail page } diff --git a/src/main/kotlin/io/github/bayang/jelu/service/BookService.kt b/src/main/kotlin/io/github/bayang/jelu/service/BookService.kt index c7498c0d..fabf0243 100644 --- a/src/main/kotlin/io/github/bayang/jelu/service/BookService.kt +++ b/src/main/kotlin/io/github/bayang/jelu/service/BookService.kt @@ -74,10 +74,16 @@ class BookService( @Transactional fun findAuthorsById(authorId: UUID): AuthorDto = bookRepository.findAuthorsById(authorId).toAuthorDto() + /** + * Image not updated, to add or update an image call the variant which accepts a MultiPartFile + */ @Transactional fun update(bookId: UUID, book: BookUpdateDto): BookDto = bookRepository.update(bookId, book).toBookDto() - // call saveImages in case image url is set + // call saveImages in case image url is set ? + /** + * Image not updated, to add or update an image call the variant which accepts a MultiPartFile + */ @Transactional fun update(userBookId: UUID, book: UserBookUpdateDto): UserBookLightDto = bookRepository.update(userBookId, book).toUserBookLightDto() @@ -129,8 +135,29 @@ class BookService( ) ) } + var backup: File? = null + var currentImage: File? = null if (file != null || userBook.book.image != null) { + // existing book used on UserBook already had an image, backup it + if (!book.image.isNullOrBlank()) { + currentImage = File(properties.files.images, book.image) + backup = File(properties.files.images, "${book.image}.bak") + Files.move(currentImage.toPath(), backup.toPath()) + } book.image = saveImages(file, book.title, book.id.toString(), userBook.book.image, properties.files.images) + // we had a previous image and we saved a new one : delete the old one + if (backup != null && backup.exists()) { + // successfully saved new image, delete backup + if (book.image != null && book.image!!.isNotBlank()) { + Files.deleteIfExists(backup.toPath()) + } else { + // saving new file failed ? Restore backup + if (currentImage != null) { + Files.move(backup.toPath(), currentImage.toPath()) + book.image = currentImage.name + } + } + } } return created.toUserBookLightDto() } @@ -145,7 +172,6 @@ class BookService( fun saveImages(file: MultipartFile?, title: String, id: String, dtoImage: String?, targetDir: String): String? { var importedFile = false var savedImage: String? = null - // FIXME resize image when saving (protect with a flag) if (file != null) { try { val destFileName: String = imageName(slugify(title), id, FilenameUtils.getExtension(file.originalFilename)) @@ -273,6 +299,10 @@ class BookService( bookRepository.deleteTagById(tagId) } + /** + * Removes an author from a book without deleting the author from the database. + * The author is removed only from that book. + */ @Transactional fun deleteAuthorFromBook(bookId: UUID, authorId: UUID) { bookRepository.deleteAuthorFromBook(bookId, authorId) diff --git a/src/main/kotlin/io/github/bayang/jelu/service/import/CsvImportService.kt b/src/main/kotlin/io/github/bayang/jelu/service/import/CsvImportService.kt index 09567dfd..7ac3bede 100644 --- a/src/main/kotlin/io/github/bayang/jelu/service/import/CsvImportService.kt +++ b/src/main/kotlin/io/github/bayang/jelu/service/import/CsvImportService.kt @@ -22,6 +22,7 @@ import io.github.bayang.jelu.service.ImportService import io.github.bayang.jelu.service.ReadingEventService import io.github.bayang.jelu.service.UserService import io.github.bayang.jelu.service.metadata.FetchMetadataService +import io.github.bayang.jelu.utils.toInstant import mu.KotlinLogging import org.apache.commons.csv.CSVFormat import org.apache.commons.csv.CSVParser @@ -32,7 +33,6 @@ import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.io.File -import java.time.Instant import java.time.LocalDate import java.time.ZoneId import java.time.format.DateTimeFormatter @@ -269,8 +269,6 @@ class CsvImportService( .count().toInt() > 0 } - private fun toInstant(date: LocalDate): Instant = date.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant() - private fun readingStatus(readStatusFromShelves: String): ReadingEventType? { return if (readStatusFromShelves.equals(CURRENTLY_READING, true)) { ReadingEventType.CURRENTLY_READING diff --git a/src/main/kotlin/io/github/bayang/jelu/utils/DateUtils.kt b/src/main/kotlin/io/github/bayang/jelu/utils/DateUtils.kt index 68e43ce0..d6db5907 100644 --- a/src/main/kotlin/io/github/bayang/jelu/utils/DateUtils.kt +++ b/src/main/kotlin/io/github/bayang/jelu/utils/DateUtils.kt @@ -1,7 +1,10 @@ package io.github.bayang.jelu.utils import java.time.Instant +import java.time.LocalDate import java.time.OffsetDateTime import java.time.ZoneId fun nowInstant(): Instant = OffsetDateTime.now(ZoneId.systemDefault()).toInstant() + +fun toInstant(date: LocalDate): Instant = date.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant() diff --git a/src/test/kotlin/io/github/bayang/jelu/TestHelpers.kt b/src/test/kotlin/io/github/bayang/jelu/TestHelpers.kt new file mode 100644 index 00000000..6ff15081 --- /dev/null +++ b/src/test/kotlin/io/github/bayang/jelu/TestHelpers.kt @@ -0,0 +1,58 @@ +package io.github.bayang.jelu + +import io.github.bayang.jelu.dao.ReadingEventType +import io.github.bayang.jelu.dto.AuthorDto +import io.github.bayang.jelu.dto.BookCreateDto +import io.github.bayang.jelu.dto.CreateUserBookDto +import io.github.bayang.jelu.dto.TagDto +import java.time.Instant + +fun createUserBookDto(bookDto: BookCreateDto, lastReadingEvent: ReadingEventType? = null, lastreadingEventDate: Instant? = null): CreateUserBookDto { + return CreateUserBookDto( + personalNotes = "test personal notes\nwith a newline", + lastReadingEvent = lastReadingEvent, + lastReadingEventDate = lastreadingEventDate, + owned = true, + toRead = false, + percentRead = null, + book = bookDto + ) +} + +fun bookDto(title: String = "title1", withTags: Boolean = false): BookCreateDto { + return BookCreateDto( + id = null, + title = title, + isbn10 = "1566199093", + isbn13 = "9781566199094 ", + summary = "This is a test summary\nwith a newline", + image = "", + publisher = "test-publisher", + pageCount = 50, + publishedDate = "", + series = "", + authors = mutableListOf(authorDto()), + numberInSeries = null, + tags = if (withTags) tags() else emptyList(), + goodreadsId = "4321abc", + googleId = "1234", + librarythingId = "", + language = "", + amazonId = "" + ) +} + +fun authorDto(name: String = "test author"): AuthorDto { + return AuthorDto(id = null, creationDate = null, modificationDate = null, name = name, image = "", dateOfBirth = "", dateOfDeath = "", biography = "author bio", facebookPage = null, goodreadsPage = null, instagramPage = null, notes = null, officialPage = null, twitterPage = null, wikipediaPage = "https://wikipedia.org") +} + +fun tags(): List { + val tags = mutableListOf() + tags.add(tagDto()) + tags.add(tagDto("fantasy")) + return tags +} + +fun tagDto(name: String = "science fiction"): TagDto { + return TagDto(id = null, creationDate = null, modificationDate = null, name) +} diff --git a/src/test/kotlin/io/github/bayang/jelu/dao/BookDaoTests.kt b/src/test/kotlin/io/github/bayang/jelu/dao/BookDaoTests.kt deleted file mode 100644 index 43b58470..00000000 --- a/src/test/kotlin/io/github/bayang/jelu/dao/BookDaoTests.kt +++ /dev/null @@ -1,83 +0,0 @@ -package io.github.bayang.jelu.dao - -import io.github.bayang.jelu.dto.AuthorDto -import io.github.bayang.jelu.dto.BookCreateDto -import io.github.bayang.jelu.dto.BookDto -import io.github.bayang.jelu.service.BookService -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -class BookDaoTests(@Autowired private val bookService: BookService) { - - @Test - fun testInsertBooks() { - val res: BookDto = bookService.save( - BookCreateDto( - id = null, - title = "title1", - isbn10 = "", - isbn13 = "", - summary = "", - image = "", - publisher = "", - pageCount = 50, - publishedDate = "", - series = "", - authors = emptyList(), - numberInSeries = null, - tags = emptyList(), - goodreadsId = "", - googleId = "", - librarythingId = "", - language = "", - amazonId = "" - ), - null - ) - Assertions.assertNotNull(res.id) - val found = bookService.findBookById(res.id!!) - Assertions.assertEquals(found.id, res.id) - Assertions.assertEquals(found.authors, res.authors) - Assertions.assertEquals(found.title, res.title) - Assertions.assertEquals(found.isbn10, res.isbn10) - Assertions.assertEquals(found.pageCount, res.pageCount) - } - - @Test - fun testInsertBooksWithAuthors() { - var author = AuthorDto(id = null, creationDate = null, modificationDate = null, name = "test", image = "", dateOfBirth = "", dateOfDeath = "", biography = "", facebookPage = null, goodreadsPage = null, instagramPage = null, notes = null, officialPage = null, twitterPage = null, wikipediaPage = null) - val res: BookDto = bookService.save( - BookCreateDto( - id = null, - title = "title1", - isbn10 = "", - isbn13 = "", - summary = "", - image = "", - publisher = "", - pageCount = 50, - publishedDate = "", - series = "", - authors = mutableListOf(author), - numberInSeries = null, - tags = emptyList(), - goodreadsId = "", - googleId = "", - librarythingId = "", - language = "", - amazonId = "" - ), - null - ) - Assertions.assertNotNull(res.id) - val found = bookService.findBookById(res.id!!) - Assertions.assertEquals(found.id, res.id) - Assertions.assertEquals(found.authors?.get(0)?.name, res.authors?.get(0)?.name) - Assertions.assertEquals(found.title, res.title) - Assertions.assertEquals(found.isbn10, res.isbn10) - Assertions.assertEquals(found.pageCount, res.pageCount) - } -} diff --git a/src/test/kotlin/io/github/bayang/jelu/service/BookServiceTest.kt b/src/test/kotlin/io/github/bayang/jelu/service/BookServiceTest.kt new file mode 100644 index 00000000..05c82760 --- /dev/null +++ b/src/test/kotlin/io/github/bayang/jelu/service/BookServiceTest.kt @@ -0,0 +1,819 @@ +package io.github.bayang.jelu.service + +import io.github.bayang.jelu.authorDto +import io.github.bayang.jelu.bookDto +import io.github.bayang.jelu.config.JeluProperties +import io.github.bayang.jelu.createUserBookDto +import io.github.bayang.jelu.dao.ReadingEventType +import io.github.bayang.jelu.dao.User +import io.github.bayang.jelu.dto.AuthorDto +import io.github.bayang.jelu.dto.AuthorUpdateDto +import io.github.bayang.jelu.dto.BookCreateDto +import io.github.bayang.jelu.dto.BookDto +import io.github.bayang.jelu.dto.CreateUserDto +import io.github.bayang.jelu.dto.JeluUser +import io.github.bayang.jelu.dto.LibraryFilter +import io.github.bayang.jelu.dto.UserBookLightDto +import io.github.bayang.jelu.dto.UserBookUpdateDto +import io.github.bayang.jelu.tags +import io.github.bayang.jelu.utils.nowInstant +import io.github.bayang.jelu.utils.slugify +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.io.TempDir +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.domain.Pageable +import org.springframework.mock.web.MockMultipartFile +import java.io.File + +@SpringBootTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class BookServiceTest( + @Autowired private val bookService: BookService, + @Autowired private val userService: UserService, + @Autowired private val jeluProperties: JeluProperties, + @Autowired private val readingEventService: ReadingEventService +) { + + companion object { + @TempDir + lateinit var tempDir: File + } + + @BeforeAll + fun setupUser() { + userService.save(CreateUserDto(login = "testuser", password = "1234", isAdmin = true)) + jeluProperties.files.images = tempDir.absolutePath + println(jeluProperties.files.images) + } + + @AfterAll + fun teardDown() { + userService.findAll(null).forEach { userService.deleteUser(it.id!!) } + } + + @AfterEach + fun cleanTest() { + tempDir.listFiles().forEach { + it.deleteRecursively() + } + readingEventService.findAll(null, null, Pageable.ofSize(30)).content.forEach { + readingEventService.deleteReadingEventById(it.id!!) + } + bookService.findUserBookByCriteria(user().id.value, null, null, Pageable.ofSize(30)) + .forEach { bookService.deleteUserBookById(it.id!!) } + bookService.findAllAuthors(null, Pageable.ofSize(30)).forEach { + bookService.deleteAuthorById(it.id!!) + } + } + + @Test + fun testInsertAuthor() { + val author = authorDto() + val res = bookService.save(author) + Assertions.assertNotNull(res) + Assertions.assertEquals(author.name, res.name) + val found = bookService.findAuthorsById(res.id!!) + Assertions.assertEquals(author.name, found.name) + Assertions.assertEquals(author.biography, found.biography) + Assertions.assertEquals(author.wikipediaPage, found.wikipediaPage) + Assertions.assertNotNull(found.creationDate) + Assertions.assertNotNull(found.modificationDate) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testUpdateAuthorNoImage() { + val author = authorDto() + val res = bookService.save(author) + Assertions.assertNotNull(res) + Assertions.assertEquals(author.name, res.name) + val update = AuthorUpdateDto(biography = null, name = null, dateOfBirth = "2000-12-12", dateOfDeath = null, facebookPage = null, goodreadsPage = null, image = null, instagramPage = null, officialPage = null, twitterPage = null, wikipediaPage = null, notes = null, creationDate = null, id = null, modificationDate = null) + val updated: AuthorDto = bookService.updateAuthor(res.id!!, update) + Assertions.assertEquals(author.name, updated.name) + Assertions.assertEquals(author.biography, updated.biography) + Assertions.assertEquals(author.wikipediaPage, updated.wikipediaPage) + Assertions.assertEquals(update.dateOfBirth, updated.dateOfBirth) + Assertions.assertNotNull(updated.creationDate) + Assertions.assertNotNull(updated.modificationDate) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testUpdateAuthorWithImageAndThenReplaceImage() { + val author = authorDto() + val res = bookService.save(author) + Assertions.assertNotNull(res) + Assertions.assertEquals(author.name, res.name) + Assertions.assertNull(res.image) + val update = AuthorUpdateDto(biography = null, name = null, dateOfBirth = "2000-12-12", dateOfDeath = null, facebookPage = null, goodreadsPage = null, image = null, instagramPage = null, officialPage = null, twitterPage = null, wikipediaPage = null, notes = null, creationDate = null, id = null, modificationDate = null) + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val updated: AuthorDto = bookService.updateAuthor(res.id!!, update, uploadFile) + Assertions.assertEquals(author.name, updated.name) + Assertions.assertEquals(author.biography, updated.biography) + Assertions.assertEquals(author.wikipediaPage, updated.wikipediaPage) + Assertions.assertEquals(update.dateOfBirth, updated.dateOfBirth) + Assertions.assertNotNull(updated.creationDate) + Assertions.assertNotNull(updated.modificationDate) + Assertions.assertNotNull(updated.image) + Assertions.assertTrue(updated.image?.contains(slugify(updated.name), true)!!) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + // update new image make sure previous is replaced + val replacementFile = MockMultipartFile("test-replacement-cover.jpg", "test-replacement-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val replaced: AuthorDto = bookService.updateAuthor(res.id!!, update, replacementFile) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + Assertions.assertTrue(replaced.image?.contains(slugify(updated.name), true)!!) + } + + @Test + fun testInsertBooks() { + val res: BookDto = bookService.save( + BookCreateDto( + id = null, + title = "title1", + isbn10 = "", + isbn13 = "", + summary = "", + image = "", + publisher = "", + pageCount = 50, + publishedDate = "", + series = "", + authors = emptyList(), + numberInSeries = null, + tags = emptyList(), + goodreadsId = "", + googleId = "", + librarythingId = "", + language = "", + amazonId = "" + ), + null + ) + Assertions.assertNotNull(res.id) + val found = bookService.findBookById(res.id!!) + Assertions.assertEquals(found.id, res.id) + Assertions.assertEquals(found.authors, res.authors) + Assertions.assertEquals(found.title, res.title) + Assertions.assertEquals(found.isbn10, res.isbn10) + Assertions.assertEquals(found.pageCount, res.pageCount) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testInsertBooksWithAuthors() { + val res: BookDto = bookService.save(bookDto(), null) + Assertions.assertNotNull(res.id) + val found = bookService.findBookById(res.id!!) + Assertions.assertEquals(found.id, res.id) + Assertions.assertEquals(found.authors?.get(0)?.name, res.authors?.get(0)?.name) + Assertions.assertEquals(found.title, res.title) + Assertions.assertEquals(found.isbn10, res.isbn10) + Assertions.assertEquals(found.pageCount, res.pageCount) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testDeletedAuthorShouldBeRemovedFromBook() { + val res: BookDto = bookService.save(bookDto(), null) + Assertions.assertNotNull(res.id) + val res2: BookDto = bookService.save(bookDto("title2"), null) + Assertions.assertNotNull(res2.id) + val found = bookService.findBookById(res.id!!) + Assertions.assertEquals(found.id, res.id) + Assertions.assertEquals(found.authors?.get(0)?.name, res.authors?.get(0)?.name) + Assertions.assertEquals(found.title, res.title) + Assertions.assertEquals(found.isbn10, res.isbn10) + Assertions.assertEquals(found.pageCount, res.pageCount) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + bookService.deleteAuthorById(found.authors?.get(0)?.id!!) + val foundAfterModification = bookService.findBookById(res.id!!) + Assertions.assertEquals(0, foundAfterModification.authors?.size) + val foundAfterModification2 = bookService.findBookById(res2.id!!) + Assertions.assertEquals(0, foundAfterModification2.authors?.size) + } + + @Test + fun testDeleteAuthorOnlyFromOneBookStillExistsInDb() { + val res: BookDto = bookService.save(bookDto(), null) + Assertions.assertNotNull(res.id) + val res2: BookDto = bookService.save(bookDto("title2"), null) + Assertions.assertNotNull(res2.id) + val found = bookService.findBookById(res.id!!) + Assertions.assertEquals(found.id, res.id) + Assertions.assertEquals(found.authors?.get(0)?.name, res.authors?.get(0)?.name) + Assertions.assertEquals(found.title, res.title) + Assertions.assertEquals(found.isbn10, res.isbn10) + Assertions.assertEquals(found.pageCount, res.pageCount) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + val authorId = res.authors?.get(0)?.id + val authorName = res.authors?.get(0)?.name + bookService.deleteAuthorFromBook(res.id!!, authorId!!) + val foundAfterModification = bookService.findBookById(res.id!!) + Assertions.assertEquals(0, foundAfterModification.authors?.size) + val foundAfterModification2 = bookService.findBookById(res2.id!!) + Assertions.assertEquals(1, foundAfterModification2.authors?.size) + val authorStillInDb = bookService.findAuthorsById(authorId) + Assertions.assertEquals(authorName, authorStillInDb.name) + } + + @Test + fun testInsertUserbookWithNewBookNoImage() { + val createBook = bookDto() + val createUserBookDto = createUserBookDto(createBook) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + Assertions.assertEquals(createBook.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertNull(saved.book.image) + Assertions.assertNull(saved.lastReadingEvent) + Assertions.assertNull(saved.lastReadingEventDate) + Assertions.assertTrue(readingEventService.findAll(null, null, Pageable.ofSize(30)).isEmpty) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testInsertUserbookWithNewBookImageAndEvent() { + val createBook = bookDto() + val createUserBookDto = createUserBookDto(createBook, ReadingEventType.CURRENTLY_READING, nowInstant()) + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), uploadFile) + Assertions.assertEquals(createBook.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertTrue(saved.book.image!!.contains(slugify(saved.book.title), true)) + Assertions.assertEquals(ReadingEventType.CURRENTLY_READING, saved.lastReadingEvent) + Assertions.assertNotNull(saved.lastReadingEventDate) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testInsertUserbookWithExistingBookNoImage() { + val createBook = bookDto() + val savedBook = bookService.save(createBook, null) + Assertions.assertEquals(createBook.title, savedBook.title) + Assertions.assertNotNull(savedBook.id) + val modified = BookCreateDto( + savedBook.id, + title = "modified title", + isbn10 = savedBook.isbn10, + isbn13 = savedBook.isbn13 + ) + val createUserBookDto = createUserBookDto(modified) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + Assertions.assertEquals(modified.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertNull(saved.lastReadingEvent) + Assertions.assertNull(saved.lastReadingEventDate) + Assertions.assertTrue(readingEventService.findAll(null, null, Pageable.ofSize(30)).isEmpty) + Assertions.assertNull(saved.book.image) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testInsertUserbookWithExistingBookAndExistingBookHasImage() { + val createBook = bookDto() + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val savedBook = bookService.save(createBook, uploadFile) + Assertions.assertEquals(createBook.title, savedBook.title) + Assertions.assertNotNull(savedBook.id) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + val modified = BookCreateDto( + savedBook.id, + title = "modified title", + isbn10 = savedBook.isbn10, + isbn13 = savedBook.isbn13 + ) + val createUserBookDto = createUserBookDto(modified) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + Assertions.assertEquals(modified.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertNull(saved.lastReadingEvent) + Assertions.assertNull(saved.lastReadingEventDate) + Assertions.assertTrue(readingEventService.findAll(null, null, Pageable.ofSize(30)).isEmpty) + Assertions.assertTrue(saved.book.image!!.contains(slugify(savedBook.title), true)) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testInsertUserbookWithImageAndExistingBookAndExistingBookHasImage() { + val createBook = bookDto() + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val savedBook = bookService.save(createBook, uploadFile) + Assertions.assertEquals(createBook.title, savedBook.title) + Assertions.assertNotNull(savedBook.id) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + val modified = BookCreateDto( + savedBook.id, + title = "modified title", + isbn10 = savedBook.isbn10, + isbn13 = savedBook.isbn13 + ) + val createUserBookDto = createUserBookDto(modified) + val replacementFile = MockMultipartFile("test-replace-cover.jpg", "test-replace-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), replacementFile) + Assertions.assertEquals(modified.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertNull(saved.lastReadingEvent) + Assertions.assertNull(saved.lastReadingEventDate) + Assertions.assertTrue(readingEventService.findAll(null, null, Pageable.ofSize(30)).isEmpty) + Assertions.assertTrue(saved.book.image!!.contains(slugify(modified.title), true)) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testUpdateUserbookWithImageAndEventNoNewEvent() { + val createBook = bookDto() + val createUserBookDto = createUserBookDto(createBook, ReadingEventType.CURRENTLY_READING, nowInstant()) + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), uploadFile) + Assertions.assertEquals(createBook.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNull(saved.percentRead) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertTrue(saved.book.image!!.contains(slugify(saved.book.title), true)) + Assertions.assertEquals(ReadingEventType.CURRENTLY_READING, saved.lastReadingEvent) + Assertions.assertNotNull(saved.lastReadingEventDate) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + + val updater = UserBookUpdateDto( + ReadingEventType.FINISHED, + personalNotes = "new notes", + owned = false, + book = null, + toRead = null, + percentRead = 50 + ) + val updated = bookService.update(saved.id!!, updater, null) + Assertions.assertEquals(createBook.title, updated.book.title) + Assertions.assertEquals(createBook.isbn10, updated.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), updated.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", updated.book.summary) + Assertions.assertEquals(createBook.publisher, updated.book.publisher) + Assertions.assertEquals(createBook.pageCount, updated.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, updated.book.goodreadsId) + Assertions.assertNull(updated.book.librarythingId) + Assertions.assertEquals(updater.owned, updated.owned) + Assertions.assertEquals(saved.toRead, updated.toRead) + Assertions.assertEquals(updater.percentRead, updated.percentRead) + Assertions.assertEquals(updater.personalNotes, updated.personalNotes) + Assertions.assertNotNull(updated.creationDate) + Assertions.assertNotNull(updated.modificationDate) + Assertions.assertNotNull(updated.book.creationDate) + Assertions.assertNotNull(updated.book.modificationDate) + Assertions.assertTrue(updated.book.image!!.contains(slugify(updated.book.title), true)) + Assertions.assertEquals(ReadingEventType.FINISHED, updated.lastReadingEvent) + Assertions.assertNotNull(updated.lastReadingEventDate) + Assertions.assertEquals(1, updated.readingEvents?.size) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testUpdateUserbookWithImageAndEventNewEventRequired() { + val createBook = bookDto() + val createUserBookDto = createUserBookDto(createBook, ReadingEventType.FINISHED, nowInstant()) + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), uploadFile) + Assertions.assertEquals(createBook.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNull(saved.percentRead) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertTrue(saved.book.image!!.contains(slugify(saved.book.title), true)) + Assertions.assertEquals(ReadingEventType.FINISHED, saved.lastReadingEvent) + Assertions.assertNotNull(saved.lastReadingEventDate) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + + val updater = UserBookUpdateDto( + ReadingEventType.DROPPED, + personalNotes = "new notes", + owned = false, + book = null, + toRead = null, + percentRead = 50 + ) + val updated = bookService.update(saved.id!!, updater, null) + Assertions.assertEquals(createBook.title, updated.book.title) + Assertions.assertEquals(createBook.isbn10, updated.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), updated.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", updated.book.summary) + Assertions.assertEquals(createBook.publisher, updated.book.publisher) + Assertions.assertEquals(createBook.pageCount, updated.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, updated.book.goodreadsId) + Assertions.assertNull(updated.book.librarythingId) + Assertions.assertEquals(updater.owned, updated.owned) + Assertions.assertEquals(saved.toRead, updated.toRead) + Assertions.assertEquals(updater.percentRead, updated.percentRead) + Assertions.assertEquals(updater.personalNotes, updated.personalNotes) + Assertions.assertNotNull(updated.creationDate) + Assertions.assertNotNull(updated.modificationDate) + Assertions.assertNotNull(updated.book.creationDate) + Assertions.assertNotNull(updated.book.modificationDate) + Assertions.assertTrue(updated.book.image!!.contains(slugify(updated.book.title), true)) + Assertions.assertEquals(ReadingEventType.DROPPED, updated.lastReadingEvent) + Assertions.assertNotNull(updated.lastReadingEventDate) + Assertions.assertEquals(2, updated.readingEvents?.size) + Assertions.assertEquals(2, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testUpdateUserbookWithImageAndEventNewEventRequiredAndNewImage() { + val createBook = bookDto() + val createUserBookDto = createUserBookDto(createBook, ReadingEventType.FINISHED, nowInstant()) + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), uploadFile) + Assertions.assertEquals(createBook.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNull(saved.percentRead) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertTrue(saved.book.image!!.contains(slugify(saved.book.title), true)) + Assertions.assertEquals(ReadingEventType.FINISHED, saved.lastReadingEvent) + Assertions.assertNotNull(saved.lastReadingEventDate) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + + val updater = UserBookUpdateDto( + ReadingEventType.DROPPED, + personalNotes = "new notes", + owned = false, + book = null, + toRead = null, + percentRead = 50 + ) + val replacementFile = MockMultipartFile("test-replace-cover.jpg", "test-replace-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val updated = bookService.update(saved.id!!, updater, replacementFile) + Assertions.assertEquals(createBook.title, updated.book.title) + Assertions.assertEquals(createBook.isbn10, updated.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), updated.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", updated.book.summary) + Assertions.assertEquals(createBook.publisher, updated.book.publisher) + Assertions.assertEquals(createBook.pageCount, updated.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, updated.book.goodreadsId) + Assertions.assertNull(updated.book.librarythingId) + Assertions.assertEquals(updater.owned, updated.owned) + Assertions.assertEquals(saved.toRead, updated.toRead) + Assertions.assertEquals(updater.percentRead, updated.percentRead) + Assertions.assertEquals(updater.personalNotes, updated.personalNotes) + Assertions.assertNotNull(updated.creationDate) + Assertions.assertNotNull(updated.modificationDate) + Assertions.assertNotNull(updated.book.creationDate) + Assertions.assertNotNull(updated.book.modificationDate) + Assertions.assertTrue(updated.book.image!!.contains(slugify(updated.book.title), true)) + Assertions.assertEquals(ReadingEventType.DROPPED, updated.lastReadingEvent) + Assertions.assertNotNull(updated.lastReadingEventDate) + Assertions.assertEquals(2, updated.readingEvents?.size) + Assertions.assertEquals(2, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun deleteUserBookRemovesEventsDoesNotRemoveBook() { + val createBook = bookDto() + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val savedBook = bookService.save(createBook, uploadFile) + Assertions.assertEquals(createBook.title, savedBook.title) + Assertions.assertNotNull(savedBook.id) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + val modified = BookCreateDto( + savedBook.id, + title = "modified title", + isbn10 = savedBook.isbn10, + isbn13 = savedBook.isbn13 + ) + val createUserBookDto = createUserBookDto(modified, ReadingEventType.FINISHED) + val saved1: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + val second = BookCreateDto(id = savedBook.id) + val createUserBookDto2 = createUserBookDto(second, ReadingEventType.FINISHED) + val saved2: UserBookLightDto = bookService.save(createUserBookDto2, user(), null) + + Assertions.assertNotNull(saved1) + Assertions.assertNotNull(saved2) + var nb = bookService.findUserBookByCriteria(user().id.value, null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(2, nb) + var eventsNb = readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(2, eventsNb) + bookService.deleteUserBookById(saved1.id!!) + nb = bookService.findUserBookByCriteria(user().id.value, null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(1, nb) + eventsNb = readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(1, eventsNb) + } + + @Test + fun deleteBookCascades() { + val createBook = bookDto(withTags = true) + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val savedBook = bookService.save(createBook, uploadFile) + Assertions.assertEquals(createBook.title, savedBook.title) + Assertions.assertNotNull(savedBook.id) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + val modified = BookCreateDto( + savedBook.id, + title = "modified title", + isbn10 = savedBook.isbn10, + isbn13 = savedBook.isbn13 + ) + val createUserBookDto = createUserBookDto(modified, ReadingEventType.FINISHED) + val saved1: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + val second = BookCreateDto(id = savedBook.id) + val createUserBookDto2 = createUserBookDto(second, ReadingEventType.FINISHED) + val saved2: UserBookLightDto = bookService.save(createUserBookDto2, user(), null) + + Assertions.assertNotNull(saved1) + Assertions.assertNotNull(saved2) + var nb = bookService.findUserBookByCriteria(user().id.value, null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(2, nb) + var eventsNb = readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(2, eventsNb) + var authorsNb = bookService.findAllAuthors(null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(1, authorsNb) + var tagsNb = bookService.findAllTags(null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(2, tagsNb) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + bookService.deleteBookById(savedBook.id!!) + nb = bookService.findUserBookByCriteria(user().id.value, null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(0, nb) + authorsNb = bookService.findAllAuthors(null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(1, authorsNb) + eventsNb = readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(0, eventsNb) + tagsNb = bookService.findAllTags(null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(2, tagsNb) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testRemoveTagFromOneBook() { + val createBook = bookDto(withTags = true) + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val savedBook = bookService.save(createBook, uploadFile) + Assertions.assertEquals(createBook.title, savedBook.title) + Assertions.assertNotNull(savedBook.id) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + val modified = BookCreateDto( + savedBook.id, + title = "modified title", + isbn10 = savedBook.isbn10, + isbn13 = savedBook.isbn13 + ) + val createUserBookDto = createUserBookDto(modified, ReadingEventType.FINISHED) + val saved1: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + val createBook2 = bookDto(withTags = true) + val savedBook2 = bookService.save(createBook2, uploadFile) + Assertions.assertEquals(createBook.title, savedBook.title) + Assertions.assertEquals(2, savedBook2.tags?.size) + Assertions.assertEquals(2, saved1.book.tags?.size) + var tagsNb = bookService.findAllTags(null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(2, tagsNb) + val tagId = bookService.findAllTags("fantasy", Pageable.ofSize(30)).content[0].id + bookService.deleteTagFromBook(savedBook.id!!, tagId!!) + + val retrieved1 = bookService.findUserBookById(saved1.id!!) + val retrieved2 = bookService.findBookById(savedBook2.id!!) + Assertions.assertEquals(2, retrieved2.tags?.size) + Assertions.assertEquals(1, retrieved1.book.tags?.size) + tagsNb = bookService.findAllTags(null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(2, tagsNb) + } + + @Test + fun testRemoveTagFromDb() { + val createBook = bookDto(withTags = true) + val uploadFile = MockMultipartFile("test-cover.jpg", "test-cover.jpg", "image/jpeg", this::class.java.getResourceAsStream("test-cover.jpg")) + val savedBook = bookService.save(createBook, uploadFile) + Assertions.assertEquals(createBook.title, savedBook.title) + Assertions.assertNotNull(savedBook.id) + Assertions.assertEquals(1, File(jeluProperties.files.images).listFiles().size) + val modified = BookCreateDto( + savedBook.id, + title = "modified title", + isbn10 = savedBook.isbn10, + isbn13 = savedBook.isbn13 + ) + val createUserBookDto = createUserBookDto(modified, ReadingEventType.FINISHED) + val saved1: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + val createBook2 = bookDto(withTags = true) + val savedBook2 = bookService.save(createBook2, uploadFile) + Assertions.assertEquals(createBook.title, savedBook.title) + Assertions.assertEquals(2, savedBook2.tags?.size) + Assertions.assertEquals(2, saved1.book.tags?.size) + var tagsNb = bookService.findAllTags(null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(2, tagsNb) + val tagId = bookService.findAllTags("fantasy", Pageable.ofSize(30)).content[0].id + bookService.deleteTagById(tagId!!) + + val retrieved1 = bookService.findUserBookById(saved1.id!!) + val retrieved2 = bookService.findBookById(savedBook2.id!!) + Assertions.assertEquals(1, retrieved2.tags?.size) + Assertions.assertEquals(1, retrieved1.book.tags?.size) + tagsNb = bookService.findAllTags(null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(1, tagsNb) + } + + @Test + fun testMergeAuthors() { + val authorDto1 = authorDto(name = "author1") + val authorDto2 = authorDto(name = "author2") + val book1 = BookCreateDto( + id = null, + title = "book 1", + isbn10 = "1566199093", + isbn13 = "9781566199094 ", + summary = "This is a test summary\nwith a newline", + image = "", + publisher = "test-publisher", + pageCount = 50, + publishedDate = "", + series = "", + authors = mutableListOf(authorDto1), + numberInSeries = null, + tags = tags(), + goodreadsId = "4321abc", + googleId = "1234", + librarythingId = "", + language = "", + amazonId = "" + ) + val book2 = BookCreateDto( + id = null, + title = "book 2", + isbn10 = "1566199093", + isbn13 = "9781566199094 ", + summary = "This is a test summary\nwith a newline", + image = "", + publisher = "test-publisher", + pageCount = 50, + publishedDate = "", + series = "", + authors = mutableListOf(authorDto2), + numberInSeries = null, + tags = tags(), + goodreadsId = "4321abc", + googleId = "1234", + librarythingId = "", + language = "", + amazonId = "" + ) + val book3 = BookCreateDto( + id = null, + title = "book 3", + isbn10 = "1566199093", + isbn13 = "9781566199094 ", + summary = "This is a test summary\nwith a newline", + image = "", + publisher = "test-publisher", + pageCount = 50, + publishedDate = "", + series = "", + authors = mutableListOf(authorDto2, authorDto1), + numberInSeries = null, + tags = tags(), + goodreadsId = "4321abc", + googleId = "1234", + librarythingId = "", + language = "", + amazonId = "" + ) + val savedBook1 = bookService.save(book1, null) + Assertions.assertNotNull(savedBook1) + Assertions.assertEquals(authorDto1.name, savedBook1.authors?.get(0)?.name) + val savedBook2 = bookService.save(book2, null) + Assertions.assertNotNull(savedBook2) + Assertions.assertEquals(authorDto2.name, savedBook2.authors?.get(0)?.name) + val savedBook3 = bookService.save(book3, null) + Assertions.assertNotNull(savedBook3) + Assertions.assertEquals(2, savedBook3.authors?.size) + val authorId1 = savedBook1.authors?.get(0)?.id + val authorId2 = savedBook2.authors?.get(0)?.id + val author1BooksNb = bookService.findAuthorBooksById(authorId1!!, user(), Pageable.ofSize(30), LibraryFilter.ANY).totalElements + val author2BooksNb = bookService.findAuthorBooksById(authorId2!!, user(), Pageable.ofSize(30), LibraryFilter.ANY).totalElements + Assertions.assertEquals(2, author1BooksNb) + Assertions.assertEquals(2, author2BooksNb) + var authorsNb = bookService.findAllAuthors(null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(2, authorsNb) + val update = AuthorUpdateDto(biography = null, name = "author 3", dateOfBirth = "2000-12-12", dateOfDeath = null, facebookPage = null, goodreadsPage = null, image = null, instagramPage = null, officialPage = null, twitterPage = null, wikipediaPage = null, notes = null, creationDate = null, id = null, modificationDate = null) + val merged = bookService.mergeAuthors(authorId1, authorId2, update, user()) + Assertions.assertEquals(update.name, merged.name) + val mergedBooks = bookService.findAuthorBooksById(merged.id!!, user(), Pageable.ofSize(30), LibraryFilter.ANY) + val titles = listOf(book1.title, book2.title, book3.title) + mergedBooks.forEach { + Assertions.assertTrue(titles.contains(it.title)) + } + Assertions.assertEquals(3, mergedBooks.totalElements) + authorsNb = bookService.findAllAuthors(null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(1, authorsNb) + } + + fun user(): User { + val userDetail = userService.loadUserByUsername("testuser") + return (userDetail as JeluUser).user + } +} diff --git a/src/test/kotlin/io/github/bayang/jelu/service/ReadingEventServiceTest.kt b/src/test/kotlin/io/github/bayang/jelu/service/ReadingEventServiceTest.kt new file mode 100644 index 00000000..b752cd89 --- /dev/null +++ b/src/test/kotlin/io/github/bayang/jelu/service/ReadingEventServiceTest.kt @@ -0,0 +1,408 @@ +package io.github.bayang.jelu.service + +import io.github.bayang.jelu.bookDto +import io.github.bayang.jelu.config.JeluProperties +import io.github.bayang.jelu.createUserBookDto +import io.github.bayang.jelu.dao.ReadingEventType +import io.github.bayang.jelu.dao.User +import io.github.bayang.jelu.dto.CreateReadingEventDto +import io.github.bayang.jelu.dto.CreateUserDto +import io.github.bayang.jelu.dto.JeluUser +import io.github.bayang.jelu.dto.LibraryFilter +import io.github.bayang.jelu.dto.UpdateReadingEventDto +import io.github.bayang.jelu.dto.UserBookLightDto +import io.github.bayang.jelu.dto.UserBookUpdateDto +import io.github.bayang.jelu.errors.JeluException +import io.github.bayang.jelu.utils.nowInstant +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.io.TempDir +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.domain.Pageable +import java.io.File +import java.time.temporal.ChronoUnit + +@SpringBootTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ReadingEventServiceTest( + @Autowired private val bookService: BookService, + @Autowired private val userService: UserService, + @Autowired private val jeluProperties: JeluProperties, + @Autowired private val readingEventService: ReadingEventService +) { + + companion object { + @TempDir + lateinit var tempDir: File + } + + @BeforeAll + fun setupUser() { + userService.save(CreateUserDto(login = "testuser", password = "1234", isAdmin = true)) + jeluProperties.files.images = tempDir.absolutePath + println(jeluProperties.files.images) + } + + @AfterAll + fun teardDown() { + userService.findAll(null).forEach { userService.deleteUser(it.id!!) } + } + + @AfterEach + fun cleanTest() { + tempDir.listFiles().forEach { + it.deleteRecursively() + } + readingEventService.findAll(null, null, Pageable.ofSize(30)).content.forEach { + readingEventService.deleteReadingEventById(it.id!!) + } + bookService.findUserBookByCriteria(user().id.value, null, null, Pageable.ofSize(30)) + .forEach { bookService.deleteUserBookById(it.id!!) } + bookService.findAllAuthors(null, Pageable.ofSize(30)).forEach { + bookService.deleteAuthorById(it.id!!) + } + bookService.findAll(null, null, null, null, null, null, Pageable.ofSize(30), user(), LibraryFilter.ANY).forEach { + bookService.deleteBookById(it.id!!) + } + } + + @Test + fun testUpdateUserbookWithImageAndEventNoNewEventRequired() { + val createBook = bookDto() + val createUserBookDto = createUserBookDto(createBook, ReadingEventType.CURRENTLY_READING, nowInstant()) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + Assertions.assertEquals(createBook.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNull(saved.percentRead) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertEquals(ReadingEventType.CURRENTLY_READING, saved.lastReadingEvent) + Assertions.assertNotNull(saved.lastReadingEventDate) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + + val updater = UserBookUpdateDto( + ReadingEventType.DROPPED, + personalNotes = "new notes", + owned = false, + book = null, + toRead = null, + percentRead = 50 + ) + val updated = bookService.update(saved.id!!, updater, null) + Assertions.assertEquals(createBook.title, updated.book.title) + Assertions.assertEquals(createBook.isbn10, updated.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), updated.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", updated.book.summary) + Assertions.assertEquals(createBook.publisher, updated.book.publisher) + Assertions.assertEquals(createBook.pageCount, updated.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, updated.book.goodreadsId) + Assertions.assertNull(updated.book.librarythingId) + Assertions.assertEquals(updater.owned, updated.owned) + Assertions.assertEquals(saved.toRead, updated.toRead) + Assertions.assertEquals(updater.percentRead, updated.percentRead) + Assertions.assertEquals(updater.personalNotes, updated.personalNotes) + Assertions.assertNotNull(updated.creationDate) + Assertions.assertNotNull(updated.modificationDate) + Assertions.assertNotNull(updated.book.creationDate) + Assertions.assertNotNull(updated.book.modificationDate) + Assertions.assertEquals(ReadingEventType.DROPPED, updated.lastReadingEvent) + Assertions.assertNotNull(updated.lastReadingEventDate) + Assertions.assertEquals(1, updated.readingEvents?.size) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testUpdateUserbookWithImageAndEventNewEventRequired() { + val createBook = bookDto() + val createUserBookDto = createUserBookDto(createBook, ReadingEventType.FINISHED, nowInstant()) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + Assertions.assertEquals(createBook.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNull(saved.percentRead) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertEquals(ReadingEventType.FINISHED, saved.lastReadingEvent) + Assertions.assertNotNull(saved.lastReadingEventDate) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + + val updater = UserBookUpdateDto( + ReadingEventType.DROPPED, + personalNotes = "new notes", + owned = false, + book = null, + toRead = null, + percentRead = 50 + ) + val updated = bookService.update(saved.id!!, updater, null) + Assertions.assertEquals(createBook.title, updated.book.title) + Assertions.assertEquals(createBook.isbn10, updated.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), updated.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", updated.book.summary) + Assertions.assertEquals(createBook.publisher, updated.book.publisher) + Assertions.assertEquals(createBook.pageCount, updated.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, updated.book.goodreadsId) + Assertions.assertNull(updated.book.librarythingId) + Assertions.assertEquals(updater.owned, updated.owned) + Assertions.assertEquals(saved.toRead, updated.toRead) + Assertions.assertEquals(updater.percentRead, updated.percentRead) + Assertions.assertEquals(updater.personalNotes, updated.personalNotes) + Assertions.assertNotNull(updated.creationDate) + Assertions.assertNotNull(updated.modificationDate) + Assertions.assertNotNull(updated.book.creationDate) + Assertions.assertNotNull(updated.book.modificationDate) + Assertions.assertEquals(ReadingEventType.DROPPED, updated.lastReadingEvent) + Assertions.assertNotNull(updated.lastReadingEventDate) + Assertions.assertEquals(2, updated.readingEvents?.size) + Assertions.assertEquals(2, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + } + + @Test + fun testUpdateUserbookWithImageAndEventNewEventRequiredWithDateBefore() { + val createBook = bookDto() + val createUserBookDto = createUserBookDto(createBook, ReadingEventType.FINISHED, nowInstant()) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + Assertions.assertEquals(createBook.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNull(saved.percentRead) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertEquals(ReadingEventType.FINISHED, saved.lastReadingEvent) + Assertions.assertNotNull(saved.lastReadingEventDate) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + + val thirtyDaysBefore = nowInstant().minus(30, ChronoUnit.DAYS) + val newEvent = readingEventService.save( + CreateReadingEventDto( + ReadingEventType.CURRENTLY_READING, + saved.book.id, + thirtyDaysBefore + ), + user() + ) + Assertions.assertEquals(ReadingEventType.CURRENTLY_READING, newEvent.eventType) + + var userbook = bookService.findUserBookById(saved.id!!) + Assertions.assertEquals(ReadingEventType.FINISHED, userbook.lastReadingEvent) + Assertions.assertTrue(userbook.lastReadingEventDate?.isAfter(nowInstant().minus(2, ChronoUnit.MINUTES))!!) + + Assertions.assertEquals(2, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + + readingEventService.deleteReadingEventById(newEvent.id!!) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + userbook = bookService.findUserBookById(saved.id!!) + Assertions.assertEquals(ReadingEventType.FINISHED, userbook.lastReadingEvent) + Assertions.assertTrue(userbook.lastReadingEventDate?.isAfter(nowInstant().minus(2, ChronoUnit.MINUTES))!!) + } + + @Test + fun testUpdateUserbookWithImageAndEventNewEventRequiredWithDateAfter() { + val createBook = bookDto() + val createUserBookDto = createUserBookDto(createBook, ReadingEventType.FINISHED, nowInstant()) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + Assertions.assertEquals(createBook.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNull(saved.percentRead) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertEquals(ReadingEventType.FINISHED, saved.lastReadingEvent) + Assertions.assertNotNull(saved.lastReadingEventDate) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + + val thirtyDaysAfter = nowInstant().plus(30, ChronoUnit.DAYS) + val newEvent = readingEventService.save( + CreateReadingEventDto( + ReadingEventType.CURRENTLY_READING, + saved.book.id, + thirtyDaysAfter + ), + user() + ) + Assertions.assertEquals(ReadingEventType.CURRENTLY_READING, newEvent.eventType) + + var userbook = bookService.findUserBookById(saved.id!!) + Assertions.assertEquals(ReadingEventType.CURRENTLY_READING, userbook.lastReadingEvent) + Assertions.assertTrue(userbook.lastReadingEventDate?.isAfter(nowInstant().plus(29, ChronoUnit.DAYS))!!) + + Assertions.assertEquals(2, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + + readingEventService.deleteReadingEventById(newEvent.id!!) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + userbook = bookService.findUserBookById(saved.id!!) + Assertions.assertEquals(ReadingEventType.FINISHED, userbook.lastReadingEvent) + Assertions.assertTrue(userbook.lastReadingEventDate?.isAfter(nowInstant().minus(2, ChronoUnit.MINUTES))!!) + } + + @Test + fun newEventWithoutExistingUserbookShouldCreateUserbook() { + val user = user() + val createBook = bookDto() + val savedBook = bookService.save(createBook, null) + val nbBooks = bookService.findAll(null, null, null, null, null, null, Pageable.ofSize(30), user(), LibraryFilter.ANY).totalElements + Assertions.assertEquals(1, nbBooks) + + var nbUserBooks = bookService.findUserBookByCriteria(user.id.value, null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(0, nbUserBooks) + + Assertions.assertEquals(0, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + + val dateAfter = nowInstant().plus(1, ChronoUnit.DAYS) + val newEvent = readingEventService.save( + CreateReadingEventDto( + ReadingEventType.CURRENTLY_READING, + savedBook.id, + dateAfter + ), + user() + ) + Assertions.assertEquals(ReadingEventType.CURRENTLY_READING, newEvent.eventType) + nbUserBooks = bookService.findUserBookByCriteria(user.id.value, null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(1, nbUserBooks) + + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + } + + @Test + fun newEventWithoutBookIdShouldThrowException() { + val user = user() + val nbBooks = bookService.findAll(null, null, null, null, null, null, Pageable.ofSize(30), user(), LibraryFilter.ANY).totalElements + Assertions.assertEquals(0, nbBooks) + + var nbUserBooks = bookService.findUserBookByCriteria(user.id.value, null, null, Pageable.ofSize(30)).totalElements + Assertions.assertEquals(0, nbUserBooks) + + val dateAfter = nowInstant().plus(1, ChronoUnit.DAYS) + assertThrows("bookId is required") { + readingEventService.save( + CreateReadingEventDto( + ReadingEventType.CURRENTLY_READING, + null, + dateAfter + ), + user() + ) + } + Assertions.assertEquals(0, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + } + + @Test + fun testUpdateEvent() { + val createBook = bookDto() + val createUserBookDto = createUserBookDto(createBook, ReadingEventType.FINISHED, nowInstant()) + val saved: UserBookLightDto = bookService.save(createUserBookDto, user(), null) + Assertions.assertEquals(createBook.title, saved.book.title) + Assertions.assertEquals(createBook.isbn10, saved.book.isbn10) + Assertions.assertEquals(createBook.isbn13?.trim(), saved.book.isbn13) + Assertions.assertEquals("This is a test summary with a newline", saved.book.summary) + Assertions.assertEquals(createBook.publisher, saved.book.publisher) + Assertions.assertEquals(createBook.pageCount, saved.book.pageCount) + Assertions.assertEquals(createBook.goodreadsId, saved.book.goodreadsId) + Assertions.assertNull(saved.book.librarythingId) + Assertions.assertEquals(createUserBookDto.owned, saved.owned) + Assertions.assertEquals(createUserBookDto.toRead, saved.toRead) + Assertions.assertEquals(createUserBookDto.personalNotes, saved.personalNotes) + Assertions.assertNull(saved.percentRead) + Assertions.assertNotNull(saved.creationDate) + Assertions.assertNotNull(saved.modificationDate) + Assertions.assertNotNull(saved.book.creationDate) + Assertions.assertNotNull(saved.book.modificationDate) + Assertions.assertEquals(ReadingEventType.FINISHED, saved.lastReadingEvent) + Assertions.assertNotNull(saved.lastReadingEventDate) + Assertions.assertEquals(1, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + + val thirtyDaysAfter = nowInstant().plus(30, ChronoUnit.DAYS) + val newEvent = readingEventService.save( + CreateReadingEventDto( + ReadingEventType.CURRENTLY_READING, + saved.book.id, + thirtyDaysAfter + ), + user() + ) + Assertions.assertEquals(ReadingEventType.CURRENTLY_READING, newEvent.eventType) + + var userbook = bookService.findUserBookById(saved.id!!) + Assertions.assertEquals(ReadingEventType.CURRENTLY_READING, userbook.lastReadingEvent) + Assertions.assertTrue(userbook.lastReadingEventDate?.isAfter(nowInstant().plus(29, ChronoUnit.DAYS))!!) + + Assertions.assertEquals(2, readingEventService.findAll(null, null, Pageable.ofSize(30)).totalElements) + Assertions.assertEquals(0, File(jeluProperties.files.images).listFiles().size) + + val updated = readingEventService.updateReadingEvent( + newEvent.id!!, + UpdateReadingEventDto( + ReadingEventType.FINISHED, + nowInstant().plus(40, ChronoUnit.DAYS) + ) + ) + Assertions.assertEquals(ReadingEventType.FINISHED, updated.eventType) + Assertions.assertFalse(updated.creationDate?.isAfter(nowInstant().plus(2, ChronoUnit.HOURS))!!) + Assertions.assertTrue(updated.modificationDate?.isAfter(nowInstant().plus(39, ChronoUnit.DAYS))!!) + userbook = bookService.findUserBookById(saved.id!!) + Assertions.assertEquals(ReadingEventType.FINISHED, userbook.lastReadingEvent) + Assertions.assertTrue(userbook.lastReadingEventDate?.isAfter(nowInstant().plus(39, ChronoUnit.DAYS))!!) + } + + fun user(): User { + val userDetail = userService.loadUserByUsername("testuser") + return (userDetail as JeluUser).user + } +} diff --git a/src/test/kotlin/io/github/bayang/jelu/service/UserServiceTest.kt b/src/test/kotlin/io/github/bayang/jelu/service/UserServiceTest.kt index 4c471613..5d36fcf3 100644 --- a/src/test/kotlin/io/github/bayang/jelu/service/UserServiceTest.kt +++ b/src/test/kotlin/io/github/bayang/jelu/service/UserServiceTest.kt @@ -1,6 +1,7 @@ package io.github.bayang.jelu.service import io.github.bayang.jelu.dto.CreateUserDto +import io.github.bayang.jelu.dto.UpdateUserDto import io.github.bayang.jelu.errors.JeluException import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach @@ -30,7 +31,13 @@ class UserServiceTest(@Autowired private val userService: UserService) { assertThrows { userService.save(CreateUserDto(login = "login1", password = "password2", isAdmin = true)) } Assertions.assertFalse(userService.isInitialSetup()) - val found = userService.findByLogin("login1") + var found = userService.findByLogin("login1") Assertions.assertEquals("login1", found[0].login) + + val updated = userService.updateUser(created.id!!, UpdateUserDto(isAdmin = false, password = "newpass")) + Assertions.assertEquals(false, updated.isAdmin) + + found = userService.findByLogin("login1") + Assertions.assertEquals(false, found[0].isAdmin) } } diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 1a71da9f..30287227 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -2,14 +2,17 @@ jelu: database: path: ${java.io.tmpdir} files: - dir: ${java.io.tmpdir} + images: ${java.io.tmpdir} + imports: ${java.io.tmpdir} + resizeImages: false spring: liquibase: enabled: true drop-first: false change-log: classpath:liquibase.xml datasource: -# url: jdbc:sqlite:memory:myDb + url: jdbc:sqlite::memory:?foreign_keys=on; username: test password: test driver-class-name: org.sqlite.JDBC + generate-unique-name: true diff --git a/src/test/resources/test-cover.jpg b/src/test/resources/test-cover.jpg new file mode 100644 index 00000000..e1fc5ec0 Binary files /dev/null and b/src/test/resources/test-cover.jpg differ