use mutable entities

add audiability for Book and Serie
changed Serie to Book relationship to unidirectional for now
This commit is contained in:
Gauthier Roebroeck 2019-08-20 15:02:52 +08:00
parent 1c674916ce
commit f3cbb5a960
19 changed files with 290 additions and 106 deletions

3
.gitignore vendored
View file

@ -10,7 +10,8 @@
.springBeans .springBeans
### IntelliJ IDEA ### ### IntelliJ IDEA ###
.idea !.idea/
.idea/*
!/.idea/runConfigurations/ !/.idea/runConfigurations/
*.iws *.iws
*.iml *.iml

View file

@ -4,6 +4,7 @@ import org.gotson.komga.infrastructure.configuration.KomgaProperties
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
import org.springframework.data.jpa.repository.config.EnableJpaAuditing
import org.springframework.scheduling.annotation.EnableScheduling import org.springframework.scheduling.annotation.EnableScheduling
@SpringBootApplication @SpringBootApplication
@ -11,6 +12,7 @@ import org.springframework.scheduling.annotation.EnableScheduling
KomgaProperties::class KomgaProperties::class
) )
@EnableScheduling @EnableScheduling
@EnableJpaAuditing
class Application class Application
fun main(args: Array<String>) { fun main(args: Array<String>) {

View file

@ -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
}

View file

@ -11,37 +11,30 @@ import javax.persistence.FetchType
import javax.persistence.GeneratedValue import javax.persistence.GeneratedValue
import javax.persistence.Id import javax.persistence.Id
import javax.persistence.JoinColumn import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
import javax.persistence.OneToOne import javax.persistence.OneToOne
import javax.persistence.PrimaryKeyJoinColumn import javax.persistence.PrimaryKeyJoinColumn
import javax.persistence.Table import javax.persistence.Table
import javax.validation.constraints.NotBlank import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
@Entity @Entity
@Table(name = "book") @Table(name = "book")
class Book( class Book(
@NotBlank @NotBlank
@Column(name = "name", nullable = false) @Column(name = "name", nullable = false)
val name: String, var name: String,
@Column(name = "url", nullable = false) @Column(name = "url", nullable = false)
val url: URL, var url: URL,
@Column(name = "updated", nullable = false) @Column(name = "file_last_modified", nullable = false)
val updated: LocalDateTime var fileLastModified: LocalDateTime
) { ) : AuditableEntity() {
@Id @Id
@GeneratedValue @GeneratedValue
@Column(name = "id", nullable = false) @Column(name = "id", nullable = false)
@PrimaryKeyJoinColumn @PrimaryKeyJoinColumn
var id: Long = 0 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) @OneToOne(optional = false, orphanRemoval = true, cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
@JoinColumn(name = "book_metadata_id", nullable = false) @JoinColumn(name = "book_metadata_id", nullable = false)
var metadata: BookMetadata = BookMetadata().also { it.book = this } var metadata: BookMetadata = BookMetadata().also { it.book = this }

View file

@ -19,16 +19,18 @@ import javax.persistence.Table
class BookMetadata( class BookMetadata(
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false) @Column(name = "status", nullable = false)
val status: Status = Status.UNKNOWN, var status: Status = Status.UNKNOWN,
@Column(name = "media_type") @Column(name = "media_type")
val mediaType: String? = null, var mediaType: String? = null,
@Column(name = "thumbnail") @Column(name = "thumbnail")
@Lob @Lob
val thumbnail: ByteArray? = null, var thumbnail: ByteArray? = null,
pages: List<BookPage> = emptyList() @ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "book_metadata_page", joinColumns = [JoinColumn(name = "book_metadata_id")])
var pages: MutableList<BookPage> = mutableListOf()
) { ) {
@Id @Id
@GeneratedValue @GeneratedValue
@ -38,15 +40,11 @@ class BookMetadata(
@OneToOne(optional = false, fetch = FetchType.LAZY, mappedBy = "metadata") @OneToOne(optional = false, fetch = FetchType.LAZY, mappedBy = "metadata")
lateinit var book: Book lateinit var book: Book
@ElementCollection(fetch = FetchType.EAGER) fun reset() {
@CollectionTable(name = "book_metadata_page", joinColumns = [JoinColumn(name = "book_metadata_id")]) status = Status.UNKNOWN
private val _pages: MutableList<BookPage> = mutableListOf() mediaType = null
thumbnail = null
val pages: List<BookPage> pages = mutableListOf()
get() = _pages.toList()
init {
_pages.addAll(pages)
} }
} }

View file

@ -8,6 +8,8 @@ import javax.persistence.Entity
import javax.persistence.FetchType import javax.persistence.FetchType
import javax.persistence.GeneratedValue import javax.persistence.GeneratedValue
import javax.persistence.Id import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.JoinTable
import javax.persistence.OneToMany import javax.persistence.OneToMany
import javax.persistence.Table import javax.persistence.Table
import javax.validation.constraints.NotBlank import javax.validation.constraints.NotBlank
@ -17,30 +19,31 @@ import javax.validation.constraints.NotBlank
class Serie( class Serie(
@NotBlank @NotBlank
@Column(name = "name", nullable = false) @Column(name = "name", nullable = false)
val name: String, var name: String,
@Column(name = "url", nullable = false) @Column(name = "url", nullable = false)
val url: URL, var url: URL,
@Column(name = "updated", nullable = false) @Column(name = "file_last_modified", nullable = false)
val updated: LocalDateTime var fileLastModified: LocalDateTime,
) {
books: MutableList<Book>
) : AuditableEntity() {
@Id @Id
@GeneratedValue @GeneratedValue
@Column(name = "id", nullable = false, unique = true) @Column(name = "id", nullable = false, unique = true)
var id: Long = 0 var id: Long = 0
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, mappedBy = "serie", orphanRemoval = true) @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
private var _books: MutableList<Book> = mutableListOf() @JoinTable(name = "serie_books_mapping", joinColumns = [JoinColumn(name = "serie_id")], inverseJoinColumns = [JoinColumn(name = "book_id")])
var books: MutableList<Book> = mutableListOf()
set(value) { set(value) {
value.forEach { it.serie = this } books.clear()
field = value books.addAll(value)
} }
val books: List<Book> init {
get() = _books.toList() this.books = books
fun setBooks(books: List<Book>) {
_books = books.toMutableList()
} }
} }

View file

@ -2,16 +2,16 @@ package org.gotson.komga.domain.persistence
import org.gotson.komga.domain.model.Book import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.Status 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.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.net.URL import java.net.URL
@Repository @Repository
interface BookRepository : JpaRepository<Book, Long> { interface BookRepository : JpaRepository<Book, Long> {
fun findAllBySerieId(serieId: Long, pageable: Pageable): Page<Book> // fun findAllBySerieId(serieId: Long, pageable: Pageable): Page<Book>
// fun findAllBySerieIdAndUrlNotIn(serieId: Long, urls: Iterable<URL>): List<Book>
fun findByUrl(url: URL): Book? fun findByUrl(url: URL): Book?
fun findAllByMetadataStatus(status: Status): List<Book> fun findAllByMetadataStatus(status: Status): List<Book>
fun findAllByMetadataStatusAndSerieId(status: Status, serieId: Long, pageable: Pageable): Page<Book> // fun findAllByMetadataStatusAndSerieId(status: Status, serieId: Long, pageable: Pageable): Page<Book>
// fun deleteAllBySerieId(serieId: Long)
} }

View file

@ -8,7 +8,7 @@ import java.net.URL
@Repository @Repository
interface SerieRepository : JpaRepository<Serie, Long>, JpaSpecificationExecutor<Serie> { interface SerieRepository : JpaRepository<Serie, Long>, JpaSpecificationExecutor<Serie> {
fun deleteAllByUrlNotIn(urls: Iterable<URL>) // fun deleteAllByUrlNotIn(urls: Iterable<URL>)
fun countByUrlNotIn(urls: Iterable<URL>): Long fun findByUrlNotIn(urls: Iterable<URL>): List<Serie>
fun findByUrl(url: URL): Serie? fun findByUrl(url: URL): Serie?
} }

View file

@ -54,7 +54,7 @@ class BookParser(
null 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 { fun getPageStream(book: Book, number: Int): InputStream {

View file

@ -44,7 +44,7 @@ class FileSystemScanner {
Book( Book(
name = FilenameUtils.getBaseName(it.fileName.toString()), name = FilenameUtils.getBaseName(it.fileName.toString()),
url = it.toUri().toURL(), url = it.toUri().toURL(),
updated = it.getUpdatedTime() fileLastModified = it.getUpdatedTime()
) )
}.toList() }.toList()
.sortedWith( .sortedWith(
@ -54,8 +54,9 @@ class FileSystemScanner {
Serie( Serie(
name = dir.fileName.toString(), name = dir.fileName.toString(),
url = dir.toUri().toURL(), url = dir.toUri().toURL(),
updated = dir.getUpdatedTime() fileLastModified = dir.getUpdatedTime(),
).also { it.setBooks(books) } books = books.toMutableList()
)
}.toList() }.toList()
}.also { }.also {
val countOfBooks = scannedSeries.sumBy { it.books.size } val countOfBooks = scannedSeries.sumBy { it.books.size }

View file

@ -5,6 +5,7 @@ import org.gotson.komga.domain.model.Library
import org.gotson.komga.domain.model.Status import org.gotson.komga.domain.model.Status
import org.gotson.komga.domain.persistence.BookRepository import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.domain.persistence.SerieRepository import org.gotson.komga.domain.persistence.SerieRepository
import org.springframework.data.auditing.AuditingHandler
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -16,7 +17,8 @@ class LibraryManager(
private val fileSystemScanner: FileSystemScanner, private val fileSystemScanner: FileSystemScanner,
private val serieRepository: SerieRepository, private val serieRepository: SerieRepository,
private val bookRepository: BookRepository, private val bookRepository: BookRepository,
private val bookManager: BookManager private val bookManager: BookManager,
private val auditingHandler: AuditingHandler
) { ) {
@Transactional @Transactional
@ -30,33 +32,49 @@ class LibraryManager(
logger.info { "Scan returned no series, deleting all existing series" } logger.info { "Scan returned no series, deleting all existing series" }
serieRepository.deleteAll() serieRepository.deleteAll()
} else { } else {
val urls = series.map { it.url } series.map { it.url }.let { urls ->
val countOfSeriesToDelete = serieRepository.countByUrlNotIn(urls) serieRepository.findByUrlNotIn(urls).forEach {
if (countOfSeriesToDelete > 0) { urls.forEach { logger.info { "Deleting serie not on disk anymore: $it" } }
logger.info { "Deleting $countOfSeriesToDelete series not on disk anymore" } serieRepository.delete(it)
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}" }
}
} }
} }
} }
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" } } }.also { logger.info { "Library update finished in $it ms" } }
} }

View file

@ -53,14 +53,16 @@ class SerieController(
@GetMapping("{id}/books") @GetMapping("{id}/books")
fun getAllBooksBySerie( fun getAllBooksBySerie(
@PathVariable id: Long, @PathVariable id: Long,
@RequestParam(value = "readyonly", defaultValue = "true") readyFilter: Boolean, @RequestParam(value = "readyonly", defaultValue = "true") readyFilter: Boolean
page: Pageable // page: Pageable
): Page<BookDto> { ): List<BookDto> {
if (!serieRepository.existsById(id)) throw ResponseStatusException(HttpStatus.NOT_FOUND) val serie = serieRepository.findByIdOrNull(id) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
return if (readyFilter) { return if (readyFilter) {
bookRepository.findAllByMetadataStatusAndSerieId(Status.READY, id, page) // bookRepository.findAllByMetadataStatusAndSerieId(Status.READY, id, page)
serie.books.filter { it.metadata.status == Status.READY }
} else { } else {
bookRepository.findAllBySerieId(id, page) // bookRepository.findAllBySerieId(id, page)
serie.books
}.map { it.toDto() } }.map { it.toDto() }
} }

View file

@ -2,18 +2,19 @@ create sequence hibernate_sequence start with 1 increment by 1;
create table book create table book
( (
id bigint generated by default as identity, id bigint not null,
name varchar not null, created_date timestamp not null,
updated timestamp not null, last_modified_date timestamp not null,
url varchar not null, file_last_modified timestamp not null,
book_metadata_id bigint not null, name varchar not null,
serie_id bigint not null, url varchar not null,
book_metadata_id bigint not null,
primary key (id) primary key (id)
); );
create table book_metadata create table book_metadata
( (
id bigint generated by default as identity, id bigint not null,
media_type varchar, media_type varchar,
status varchar not null, status varchar not null,
thumbnail blob, thumbnail blob,
@ -29,21 +30,35 @@ create table book_metadata_page
create table serie create table serie
( (
id bigint generated by default as identity, id bigint not null,
name varchar not null, created_date timestamp not null,
updated timestamp not null, last_modified_date timestamp not null,
url varchar not null, file_last_modified timestamp not null,
name varchar not null,
url varchar not null,
primary key (id) primary key (id)
); );
create table serie_books_mapping
(
serie_id bigint not null,
book_id bigint not null
);
alter table book alter table book
add constraint uk_book_book_metadata_id unique (book_metadata_id); 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 alter table book
add constraint fk_book_book_metadata_book_metadata_id foreign key (book_metadata_id) references book_metadata (id); 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 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); 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);

View file

@ -3,11 +3,15 @@ package org.gotson.komga.domain.model
import java.net.URL import java.net.URL
import java.time.LocalDateTime import java.time.LocalDateTime
fun makeBook(name: String, url: String = "file:/$name") = fun makeBook(name: String, url: String = "file:/$name", fileLastModified: LocalDateTime = LocalDateTime.now()): Book {
Book(name = name, url = URL(url), updated = LocalDateTime.now()) Thread.sleep(5)
return Book(name = name, url = URL(url), fileLastModified = fileLastModified)
}
fun makeSerie(name: String, url: String = "file:/$name", books: List<Book> = listOf()) = fun makeSerie(name: String, url: String = "file:/$name", books: List<Book> = listOf()): Serie {
Serie(name = name, url = URL(url), updated = LocalDateTime.now()).also { it.setBooks(books) } Thread.sleep(5)
return Serie(name = name, url = URL(url), fileLastModified = LocalDateTime.now(), books = books.toMutableList())
}
fun makeBookPage(name: String) = fun makeBookPage(name: String) =
BookPage(name, "image/png") BookPage(name, "image/png")

View file

@ -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)
}
}

View file

@ -52,7 +52,7 @@ class PersistenceTest(
// when // when
val book = bookRepository.findAll().first() 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) bookRepository.save(book)

View file

@ -69,7 +69,7 @@ class LibraryManagerTest(
assertThat(series).hasSize(1) assertThat(series).hasSize(1)
assertThat(series.first().books).hasSize(2) 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 @Test
@ -95,7 +95,7 @@ class LibraryManagerTest(
assertThat(series).hasSize(1) assertThat(series).hasSize(1)
assertThat(series.first().books).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) assertThat(bookRepository.count()).describedAs("Orphan book has been removed").isEqualTo(1)
} }
@ -121,8 +121,10 @@ class LibraryManagerTest(
verify(exactly = 2) { mockScanner.scanRootFolder(any()) } verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
assertThat(series).hasSize(1) assertThat(series).hasSize(1)
assertThat(series.first().lastModifiedDate).isNotEqualTo(series.first().createdDate)
assertThat(series.first().books).hasSize(1) 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 @Test
@ -150,14 +152,15 @@ class LibraryManagerTest(
@Test @Test
fun `given existing Book with metadata when rescanning then metadata is kept intact`() { fun `given existing Book with metadata when rescanning then metadata is kept intact`() {
//given //given
val book1 = makeBook("book1")
every { mockScanner.scanRootFolder(any()) } every { mockScanner.scanRootFolder(any()) }
.returnsMany( .returnsMany(
listOf(makeSerie(name = "serie", books = listOf(makeBook("book1")))), listOf(makeSerie(name = "serie", books = listOf(book1))),
listOf(makeSerie(name = "serie", books = listOf(makeBook("book1")))) listOf(makeSerie(name = "serie", books = listOf(makeBook(name = "book1", fileLastModified = book1.fileLastModified))))
) )
libraryManager.scanRootFolder(library) 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) } bookRepository.findAll().forEach { bookManager.parseAndPersist(it) }
// when // when
@ -172,5 +175,6 @@ class LibraryManagerTest(
assertThat(book.metadata.mediaType).isEqualTo("application/zip") assertThat(book.metadata.mediaType).isEqualTo("application/zip")
assertThat(book.metadata.pages).hasSize(2) assertThat(book.metadata.pages).hasSize(2)
assertThat(book.metadata.pages.map { it.fileName }).containsExactly("1.jpg", "2.jpg") assertThat(book.metadata.pages.map { it.fileName }).containsExactly("1.jpg", "2.jpg")
assertThat(book.lastModifiedDate).isNotEqualTo(book.createdDate)
} }
} }

View file

@ -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`() {
}
}

View file

@ -2,10 +2,10 @@ application.version: TESTING
spring: spring:
flyway: flyway:
enabled: true enabled: false
jpa: jpa:
hibernate: hibernate:
ddl-auto: validate ddl-auto: none
jpa: jpa:
properties: properties:
hibernate: hibernate: