From f3cbb5a9605a8b0f3fb73f360bd8266da1d81664 Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Tue, 20 Aug 2019 15:02:52 +0800 Subject: [PATCH] use mutable entities add audiability for Book and Serie changed Serie to Book relationship to unidirectional for now --- .gitignore | 3 +- .../kotlin/org/gotson/komga/Application.kt | 2 + .../komga/domain/model/AuditableEntity.kt | 21 ++++ .../org/gotson/komga/domain/model/Book.kt | 17 +-- .../gotson/komga/domain/model/BookMetadata.kt | 24 ++-- .../org/gotson/komga/domain/model/Serie.kt | 31 +++--- .../domain/persistence/BookRepository.kt | 8 +- .../domain/persistence/SerieRepository.kt | 4 +- .../gotson/komga/domain/service/BookParser.kt | 2 +- .../komga/domain/service/FileSystemScanner.kt | 7 +- .../komga/domain/service/LibraryManager.kt | 64 +++++++---- .../komga/interfaces/web/SerieController.kt | 14 ++- .../V20190819161603__First_Version.sql | 43 +++++--- .../org/gotson/komga/domain/model/Utils.kt | 12 +- .../domain/persistence/AuditableEntityTest.kt | 103 ++++++++++++++++++ .../domain/persistence/PersistenceTest.kt | 2 +- .../domain/service/LibraryManagerTest.kt | 16 ++- .../gotson/komga/infrastructure/FlywayTest.kt | 19 ++++ komga/src/test/resources/application-test.yml | 4 +- 19 files changed, 290 insertions(+), 106 deletions(-) create mode 100644 komga/src/main/kotlin/org/gotson/komga/domain/model/AuditableEntity.kt create mode 100644 komga/src/test/kotlin/org/gotson/komga/domain/persistence/AuditableEntityTest.kt create mode 100644 komga/src/test/kotlin/org/gotson/komga/infrastructure/FlywayTest.kt diff --git a/.gitignore b/.gitignore index 894b5eef5..0322f595a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,8 @@ .springBeans ### IntelliJ IDEA ### -.idea +!.idea/ +.idea/* !/.idea/runConfigurations/ *.iws *.iml diff --git a/komga/src/main/kotlin/org/gotson/komga/Application.kt b/komga/src/main/kotlin/org/gotson/komga/Application.kt index ac5342699..c22fcc716 100644 --- a/komga/src/main/kotlin/org/gotson/komga/Application.kt +++ b/komga/src/main/kotlin/org/gotson/komga/Application.kt @@ -4,6 +4,7 @@ import org.gotson.komga.infrastructure.configuration.KomgaProperties import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.runApplication +import org.springframework.data.jpa.repository.config.EnableJpaAuditing import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication @@ -11,6 +12,7 @@ import org.springframework.scheduling.annotation.EnableScheduling KomgaProperties::class ) @EnableScheduling +@EnableJpaAuditing class Application fun main(args: Array) { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/AuditableEntity.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/AuditableEntity.kt new file mode 100644 index 000000000..129bb534f --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/AuditableEntity.kt @@ -0,0 +1,21 @@ +package org.gotson.komga.domain.model + +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.LocalDateTime +import javax.persistence.Column +import javax.persistence.EntityListeners +import javax.persistence.MappedSuperclass + +@MappedSuperclass +@EntityListeners(AuditingEntityListener::class) +abstract class AuditableEntity { + @CreatedDate + @Column(name = "created_date", updatable = false, nullable = false) + var createdDate: LocalDateTime? = null + + @LastModifiedDate + @Column(name = "last_modified_date", nullable = false) + var lastModifiedDate: LocalDateTime? = null +} \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt index 5e6060b82..763d2e1fd 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt @@ -11,37 +11,30 @@ import javax.persistence.FetchType import javax.persistence.GeneratedValue import javax.persistence.Id import javax.persistence.JoinColumn -import javax.persistence.ManyToOne import javax.persistence.OneToOne import javax.persistence.PrimaryKeyJoinColumn import javax.persistence.Table import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull @Entity @Table(name = "book") class Book( @NotBlank @Column(name = "name", nullable = false) - val name: String, + var name: String, @Column(name = "url", nullable = false) - val url: URL, + var url: URL, - @Column(name = "updated", nullable = false) - val updated: LocalDateTime -) { + @Column(name = "file_last_modified", nullable = false) + var fileLastModified: LocalDateTime +) : AuditableEntity() { @Id @GeneratedValue @Column(name = "id", nullable = false) @PrimaryKeyJoinColumn var id: Long = 0 - @NotNull - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "serie_id", nullable = false) - lateinit var serie: Serie - @OneToOne(optional = false, orphanRemoval = true, cascade = [CascadeType.ALL], fetch = FetchType.LAZY) @JoinColumn(name = "book_metadata_id", nullable = false) var metadata: BookMetadata = BookMetadata().also { it.book = this } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt index 254d2375f..ae4a16262 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt @@ -19,16 +19,18 @@ import javax.persistence.Table class BookMetadata( @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false) - val status: Status = Status.UNKNOWN, + var status: Status = Status.UNKNOWN, @Column(name = "media_type") - val mediaType: String? = null, + var mediaType: String? = null, @Column(name = "thumbnail") @Lob - val thumbnail: ByteArray? = null, + var thumbnail: ByteArray? = null, - pages: List = emptyList() + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "book_metadata_page", joinColumns = [JoinColumn(name = "book_metadata_id")]) + var pages: MutableList = mutableListOf() ) { @Id @GeneratedValue @@ -38,15 +40,11 @@ class BookMetadata( @OneToOne(optional = false, fetch = FetchType.LAZY, mappedBy = "metadata") lateinit var book: Book - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "book_metadata_page", joinColumns = [JoinColumn(name = "book_metadata_id")]) - private val _pages: MutableList = mutableListOf() - - val pages: List - get() = _pages.toList() - - init { - _pages.addAll(pages) + fun reset() { + status = Status.UNKNOWN + mediaType = null + thumbnail = null + pages = mutableListOf() } } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Serie.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Serie.kt index d29e2be4e..967a23d0c 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Serie.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Serie.kt @@ -8,6 +8,8 @@ import javax.persistence.Entity import javax.persistence.FetchType import javax.persistence.GeneratedValue import javax.persistence.Id +import javax.persistence.JoinColumn +import javax.persistence.JoinTable import javax.persistence.OneToMany import javax.persistence.Table import javax.validation.constraints.NotBlank @@ -17,30 +19,31 @@ import javax.validation.constraints.NotBlank class Serie( @NotBlank @Column(name = "name", nullable = false) - val name: String, + var name: String, @Column(name = "url", nullable = false) - val url: URL, + var url: URL, - @Column(name = "updated", nullable = false) - val updated: LocalDateTime -) { + @Column(name = "file_last_modified", nullable = false) + var fileLastModified: LocalDateTime, + + books: MutableList + +) : AuditableEntity() { @Id @GeneratedValue @Column(name = "id", nullable = false, unique = true) var id: Long = 0 - @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, mappedBy = "serie", orphanRemoval = true) - private var _books: MutableList = mutableListOf() + @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true) + @JoinTable(name = "serie_books_mapping", joinColumns = [JoinColumn(name = "serie_id")], inverseJoinColumns = [JoinColumn(name = "book_id")]) + var books: MutableList = mutableListOf() set(value) { - value.forEach { it.serie = this } - field = value + books.clear() + books.addAll(value) } - val books: List - get() = _books.toList() - - fun setBooks(books: List) { - _books = books.toMutableList() + init { + this.books = books } } \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt index a04755a8d..05e793b6d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt @@ -2,16 +2,16 @@ package org.gotson.komga.domain.persistence import org.gotson.komga.domain.model.Book import org.gotson.komga.domain.model.Status -import org.springframework.data.domain.Page -import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository import java.net.URL @Repository interface BookRepository : JpaRepository { - fun findAllBySerieId(serieId: Long, pageable: Pageable): Page + // fun findAllBySerieId(serieId: Long, pageable: Pageable): Page +// fun findAllBySerieIdAndUrlNotIn(serieId: Long, urls: Iterable): List fun findByUrl(url: URL): Book? fun findAllByMetadataStatus(status: Status): List - fun findAllByMetadataStatusAndSerieId(status: Status, serieId: Long, pageable: Pageable): Page +// fun findAllByMetadataStatusAndSerieId(status: Status, serieId: Long, pageable: Pageable): Page +// fun deleteAllBySerieId(serieId: Long) } \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SerieRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SerieRepository.kt index 8e28d4fe9..cad1bdfcc 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SerieRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SerieRepository.kt @@ -8,7 +8,7 @@ import java.net.URL @Repository interface SerieRepository : JpaRepository, JpaSpecificationExecutor { - fun deleteAllByUrlNotIn(urls: Iterable) - fun countByUrlNotIn(urls: Iterable): Long + // fun deleteAllByUrlNotIn(urls: Iterable) + fun findByUrlNotIn(urls: Iterable): List fun findByUrl(url: URL): Serie? } \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt index 2bfe4c583..dc94bda54 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt @@ -54,7 +54,7 @@ class BookParser( null } - return BookMetadata(mediaType = mediaType, status = Status.READY, pages = pages, thumbnail = thumbnail) + return BookMetadata(mediaType = mediaType, status = Status.READY, pages = pages.toMutableList(), thumbnail = thumbnail) } fun getPageStream(book: Book, number: Int): InputStream { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt index 4fe971278..e85233e6b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt @@ -44,7 +44,7 @@ class FileSystemScanner { Book( name = FilenameUtils.getBaseName(it.fileName.toString()), url = it.toUri().toURL(), - updated = it.getUpdatedTime() + fileLastModified = it.getUpdatedTime() ) }.toList() .sortedWith( @@ -54,8 +54,9 @@ class FileSystemScanner { Serie( name = dir.fileName.toString(), url = dir.toUri().toURL(), - updated = dir.getUpdatedTime() - ).also { it.setBooks(books) } + fileLastModified = dir.getUpdatedTime(), + books = books.toMutableList() + ) }.toList() }.also { val countOfBooks = scannedSeries.sumBy { it.books.size } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt index d63fc41fd..7c5ef6f93 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt @@ -5,6 +5,7 @@ import org.gotson.komga.domain.model.Library import org.gotson.komga.domain.model.Status import org.gotson.komga.domain.persistence.BookRepository import org.gotson.komga.domain.persistence.SerieRepository +import org.springframework.data.auditing.AuditingHandler import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import kotlin.system.measureTimeMillis @@ -16,7 +17,8 @@ class LibraryManager( private val fileSystemScanner: FileSystemScanner, private val serieRepository: SerieRepository, private val bookRepository: BookRepository, - private val bookManager: BookManager + private val bookManager: BookManager, + private val auditingHandler: AuditingHandler ) { @Transactional @@ -30,33 +32,49 @@ class LibraryManager( logger.info { "Scan returned no series, deleting all existing series" } serieRepository.deleteAll() } else { - val urls = series.map { it.url } - val countOfSeriesToDelete = serieRepository.countByUrlNotIn(urls) - if (countOfSeriesToDelete > 0) { - logger.info { "Deleting $countOfSeriesToDelete series not on disk anymore" } - serieRepository.deleteAllByUrlNotIn(urls) - } - } - - // match IDs for existing entities - series.forEach { newSerie -> - serieRepository.findByUrl(newSerie.url)?.let { existingSerie -> - newSerie.id = existingSerie.id - newSerie.books.forEach { newBook -> - bookRepository.findByUrl(newBook.url)?.let { existingBook -> - newBook.id = existingBook.id - // conserve metadata if book has not changed - if (newBook.updated == existingBook.updated) - newBook.metadata = existingBook.metadata - else - logger.info { "Book changed on disk, reset metadata status: ${newBook.url}" } - } + series.map { it.url }.let { urls -> + serieRepository.findByUrlNotIn(urls).forEach { + urls.forEach { logger.info { "Deleting serie not on disk anymore: $it" } } + serieRepository.delete(it) } } } - serieRepository.saveAll(series) + series.forEach { newSerie -> + val existingSerie = serieRepository.findByUrl(newSerie.url) + // if serie does not exist, save it + if (existingSerie == null) { + serieRepository.save(newSerie) + } else { + // if serie already exists, update it + if (newSerie.fileLastModified != existingSerie.fileLastModified) { + logger.info { "Serie changed on disk, updating: ${newSerie.url}" } + existingSerie.name = newSerie.name + + var anyBookchanged = false + // update list of books with existing entities if they exist + existingSerie.books = newSerie.books.map { newBook -> + val existingBook = bookRepository.findByUrl(newBook.url) ?: newBook + + if (newBook.fileLastModified != existingBook.fileLastModified) { + logger.info { "Book changed on disk, update and reset metadata status: ${newBook.url}" } + existingBook.fileLastModified = newBook.fileLastModified + existingBook.name = newBook.name + existingBook.metadata.reset() + anyBookchanged = true + } + existingBook + }.toMutableList() + + // propagate modification of any of the books to the serie, so that LastModifiedDate is updated + if (anyBookchanged) + auditingHandler.markModified(existingSerie) + + serieRepository.save(existingSerie) + } + } + } }.also { logger.info { "Library update finished in $it ms" } } } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/SerieController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/SerieController.kt index b8f47ed09..cc6a19bf0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/SerieController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/SerieController.kt @@ -53,14 +53,16 @@ class SerieController( @GetMapping("{id}/books") fun getAllBooksBySerie( @PathVariable id: Long, - @RequestParam(value = "readyonly", defaultValue = "true") readyFilter: Boolean, - page: Pageable - ): Page { - if (!serieRepository.existsById(id)) throw ResponseStatusException(HttpStatus.NOT_FOUND) + @RequestParam(value = "readyonly", defaultValue = "true") readyFilter: Boolean +// page: Pageable + ): List { + val serie = serieRepository.findByIdOrNull(id) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) return if (readyFilter) { - bookRepository.findAllByMetadataStatusAndSerieId(Status.READY, id, page) +// bookRepository.findAllByMetadataStatusAndSerieId(Status.READY, id, page) + serie.books.filter { it.metadata.status == Status.READY } } else { - bookRepository.findAllBySerieId(id, page) +// bookRepository.findAllBySerieId(id, page) + serie.books }.map { it.toDto() } } diff --git a/komga/src/main/resources/db/migration/V20190819161603__First_Version.sql b/komga/src/main/resources/db/migration/V20190819161603__First_Version.sql index 32eaf4989..c3cd03205 100644 --- a/komga/src/main/resources/db/migration/V20190819161603__First_Version.sql +++ b/komga/src/main/resources/db/migration/V20190819161603__First_Version.sql @@ -2,18 +2,19 @@ create sequence hibernate_sequence start with 1 increment by 1; create table book ( - id bigint generated by default as identity, - name varchar not null, - updated timestamp not null, - url varchar not null, - book_metadata_id bigint not null, - serie_id bigint not null, + id bigint not null, + created_date timestamp not null, + last_modified_date timestamp not null, + file_last_modified timestamp not null, + name varchar not null, + url varchar not null, + book_metadata_id bigint not null, primary key (id) ); create table book_metadata ( - id bigint generated by default as identity, + id bigint not null, media_type varchar, status varchar not null, thumbnail blob, @@ -29,21 +30,35 @@ create table book_metadata_page create table serie ( - id bigint generated by default as identity, - name varchar not null, - updated timestamp not null, - url varchar not null, + id bigint not null, + created_date timestamp not null, + last_modified_date timestamp not null, + file_last_modified timestamp not null, + name varchar not null, + url varchar not null, primary key (id) ); +create table serie_books_mapping +( + serie_id bigint not null, + book_id bigint not null +); + alter table book add constraint uk_book_book_metadata_id unique (book_metadata_id); +alter table serie_books_mapping + add constraint UK_ya6l4fjcs4rlhwx1fwqwsmm unique (book_id); + alter table book add constraint fk_book_book_metadata_book_metadata_id foreign key (book_metadata_id) references book_metadata (id); -alter table book - add constraint fk_book_serie_serie_id foreign key (serie_id) references serie (id); - alter table book_metadata_page add constraint fk_book_metadata_page_book_metadata_book_metadata_id foreign key (book_metadata_id) references book_metadata (id); + +alter table serie_books_mapping + add constraint fk_serie_books_mapping_book_book_id foreign key (book_id) references book (id); + +alter table serie_books_mapping + add constraint fk_serie_books_mapping_serie_serie_id foreign key (serie_id) references serie (id); diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt b/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt index e3cbe8716..096cdba7d 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt @@ -3,11 +3,15 @@ package org.gotson.komga.domain.model import java.net.URL import java.time.LocalDateTime -fun makeBook(name: String, url: String = "file:/$name") = - Book(name = name, url = URL(url), updated = LocalDateTime.now()) +fun makeBook(name: String, url: String = "file:/$name", fileLastModified: LocalDateTime = LocalDateTime.now()): Book { + Thread.sleep(5) + return Book(name = name, url = URL(url), fileLastModified = fileLastModified) +} -fun makeSerie(name: String, url: String = "file:/$name", books: List = listOf()) = - Serie(name = name, url = URL(url), updated = LocalDateTime.now()).also { it.setBooks(books) } +fun makeSerie(name: String, url: String = "file:/$name", books: List = listOf()): Serie { + Thread.sleep(5) + return Serie(name = name, url = URL(url), fileLastModified = LocalDateTime.now(), books = books.toMutableList()) +} fun makeBookPage(name: String) = BookPage(name, "image/png") \ No newline at end of file diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/persistence/AuditableEntityTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/persistence/AuditableEntityTest.kt new file mode 100644 index 000000000..d694dc17b --- /dev/null +++ b/komga/src/test/kotlin/org/gotson/komga/domain/persistence/AuditableEntityTest.kt @@ -0,0 +1,103 @@ +package org.gotson.komga.domain.persistence + +import org.assertj.core.api.Assertions.assertThat +import org.gotson.komga.domain.model.makeBook +import org.gotson.komga.domain.model.makeSerie +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDateTime + +@ExtendWith(SpringExtension::class) +@DataJpaTest +@Transactional +class AuditableEntityTest( + @Autowired private val serieRepository: SerieRepository, + @Autowired private val entityManager: TestEntityManager +) { + + @AfterEach + fun `clear repository`() { + entityManager.clear() + } + + @Test + fun `given serie with book when saving then created and modified date is also saved`() { + // given + val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))) + + // when + serieRepository.save(serie) + + // then + assertThat(serie.createdDate).isBefore(LocalDateTime.now()) + assertThat(serie.lastModifiedDate).isBefore(LocalDateTime.now()) + assertThat(serie.books.first().createdDate).isBefore(LocalDateTime.now()) + assertThat(serie.books.first().lastModifiedDate).isBefore(LocalDateTime.now()) + } + + @Test + fun `given existing serie with book when updating serie only then created date is kept and modified date is changed for serie only`() { + // given + val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))) + + serieRepository.save(serie) + + val creationTimeApprox = LocalDateTime.now() + + Thread.sleep(1000) + + // when + serie.name = "serieUpdated" + serieRepository.saveAndFlush(serie) + + val modificationTimeApprox = LocalDateTime.now() + + // then + assertThat(serie.createdDate) + .isBefore(creationTimeApprox) + .isNotEqualTo(serie.lastModifiedDate) + assertThat(serie.lastModifiedDate) + .isAfter(creationTimeApprox) + .isBefore(modificationTimeApprox) + + assertThat(serie.books.first().createdDate) + .isBefore(creationTimeApprox) + .isEqualTo(serie.books.first().lastModifiedDate) + } + + @Test + fun `given existing serie with book when updating book only then created date is kept and modified date is changed for book only`() { + // given + val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))) + + serieRepository.save(serie) + + val creationTimeApprox = LocalDateTime.now() + + Thread.sleep(1000) + + // when + serie.books.first().name = "bookUpdated" + serieRepository.saveAndFlush(serie) + + val modificationTimeApprox = LocalDateTime.now() + + // then + assertThat(serie.createdDate) + .isBefore(creationTimeApprox) + .isEqualTo(serie.lastModifiedDate) + + assertThat(serie.books.first().createdDate) + .isBefore(creationTimeApprox) + .isNotEqualTo(serie.books.first().lastModifiedDate) + assertThat(serie.books.first().lastModifiedDate) + .isAfter(creationTimeApprox) + .isBefore(modificationTimeApprox) + } +} \ No newline at end of file diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/persistence/PersistenceTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/persistence/PersistenceTest.kt index b461cc682..3280d707b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/persistence/PersistenceTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/persistence/PersistenceTest.kt @@ -52,7 +52,7 @@ class PersistenceTest( // when val book = bookRepository.findAll().first() - book.metadata = BookMetadata(status = Status.READY, mediaType = "test", pages = listOf(makeBookPage("page1"))) + book.metadata = BookMetadata(status = Status.READY, mediaType = "test", pages = mutableListOf(makeBookPage("page1"))) bookRepository.save(book) diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryManagerTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryManagerTest.kt index fad01f294..35747af23 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryManagerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryManagerTest.kt @@ -69,7 +69,7 @@ class LibraryManagerTest( assertThat(series).hasSize(1) assertThat(series.first().books).hasSize(2) - assertThat(series.first().books.map { it.name }).containsExactlyInAnyOrder("book1", "book2") + assertThat(series.first().books.map { it.name }).containsExactly("book1", "book2") } @Test @@ -95,7 +95,7 @@ class LibraryManagerTest( assertThat(series).hasSize(1) assertThat(series.first().books).hasSize(1) - assertThat(series.first().books.map { it.name }).containsExactlyInAnyOrder("book1") + assertThat(series.first().books.map { it.name }).containsExactly("book1") assertThat(bookRepository.count()).describedAs("Orphan book has been removed").isEqualTo(1) } @@ -121,8 +121,10 @@ class LibraryManagerTest( verify(exactly = 2) { mockScanner.scanRootFolder(any()) } assertThat(series).hasSize(1) + assertThat(series.first().lastModifiedDate).isNotEqualTo(series.first().createdDate) assertThat(series.first().books).hasSize(1) - assertThat(series.first().books.map { it.name }).containsExactlyInAnyOrder("book1updated") + assertThat(series.first().books.map { it.name }).containsExactly("book1updated") + assertThat(series.first().books.first().lastModifiedDate).isNotEqualTo(series.first().books.first().createdDate) } @Test @@ -150,14 +152,15 @@ class LibraryManagerTest( @Test fun `given existing Book with metadata when rescanning then metadata is kept intact`() { //given + val book1 = makeBook("book1") every { mockScanner.scanRootFolder(any()) } .returnsMany( - listOf(makeSerie(name = "serie", books = listOf(makeBook("book1")))), - listOf(makeSerie(name = "serie", books = listOf(makeBook("book1")))) + listOf(makeSerie(name = "serie", books = listOf(book1))), + listOf(makeSerie(name = "serie", books = listOf(makeBook(name = "book1", fileLastModified = book1.fileLastModified)))) ) libraryManager.scanRootFolder(library) - every { mockParser.parse(any()) } returns BookMetadata(status = Status.READY, mediaType = "application/zip", pages = listOf(makeBookPage("1.jpg"), makeBookPage("2.jpg"))) + every { mockParser.parse(any()) } returns BookMetadata(status = Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg"))) bookRepository.findAll().forEach { bookManager.parseAndPersist(it) } // when @@ -172,5 +175,6 @@ class LibraryManagerTest( assertThat(book.metadata.mediaType).isEqualTo("application/zip") assertThat(book.metadata.pages).hasSize(2) assertThat(book.metadata.pages.map { it.fileName }).containsExactly("1.jpg", "2.jpg") + assertThat(book.lastModifiedDate).isNotEqualTo(book.createdDate) } } \ No newline at end of file diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/FlywayTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/FlywayTest.kt new file mode 100644 index 000000000..a52bd625e --- /dev/null +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/FlywayTest.kt @@ -0,0 +1,19 @@ +package org.gotson.komga.infrastructure + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension + +@ExtendWith(SpringExtension::class) +@SpringBootTest +@AutoConfigureTestDatabase +@ActiveProfiles("flyway") +class FlywayTest { + + @Test + fun `Application loads properly with flyway profile`() { + } +} \ No newline at end of file diff --git a/komga/src/test/resources/application-test.yml b/komga/src/test/resources/application-test.yml index f2d5d18e6..7b3531cec 100644 --- a/komga/src/test/resources/application-test.yml +++ b/komga/src/test/resources/application-test.yml @@ -2,10 +2,10 @@ application.version: TESTING spring: flyway: - enabled: true + enabled: false jpa: hibernate: - ddl-auto: validate + ddl-auto: none jpa: properties: hibernate: