From 6824212514d8586bdb47ac2c38d6e121be8db489 Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Fri, 3 Jul 2020 15:01:21 +0800 Subject: [PATCH] feat(api): metadata import settings per library ability to edit a library fix filepath returned by API for Windows paths series metadata import is now looking at all the files from all books, instead of being imported for each book separately related to #199 --- ...2111235__library_metadata_import_flags.sql | 10 + .../gotson/komga/application/tasks/Task.kt | 4 + .../komga/application/tasks/TaskHandler.kt | 8 + .../komga/application/tasks/TaskReceiver.kt | 4 + .../komga/domain/model/BookMetadataPatch.kt | 3 +- .../org/gotson/komga/domain/model/Library.kt | 9 +- .../domain/persistence/LibraryRepository.kt | 4 +- .../komga/domain/service/LibraryLifecycle.kt | 28 +- .../komga/domain/service/MetadataLifecycle.kt | 76 +++++- .../komga/infrastructure/jooq/BookDtoDao.kt | 3 +- .../komga/infrastructure/jooq/LibraryDao.kt | 42 ++- .../komga/infrastructure/jooq/SeriesDtoDao.kt | 3 +- .../metadata/SeriesMetadataProvider.kt | 9 + .../metadata/comicinfo/ComicInfoProvider.kt | 21 +- .../metadata/epub/EpubMetadataProvider.kt | 22 +- .../gotson/komga/infrastructure/web/Utils.kt | 8 + .../interfaces/rest/LibraryController.kt | 68 ++++- .../application/tasks/TaskHandlerTest.kt | 9 +- .../domain/service/LibraryLifecycleTest.kt | 255 ++++++++++++++---- .../infrastructure/jooq/LibraryDaoTest.kt | 55 ++-- .../komga/infrastructure/jooq/MediaDaoTest.kt | 24 +- .../comicinfo/ComicInfoProviderTest.kt | 4 +- .../interfaces/rest/BookControllerTest.kt | 6 +- .../interfaces/rest/LibraryControllerTest.kt | 5 +- .../interfaces/rest/SeriesControllerTest.kt | 5 +- 25 files changed, 537 insertions(+), 148 deletions(-) create mode 100644 komga/src/flyway/resources/db/migration/V20200702111235__library_metadata_import_flags.sql create mode 100644 komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/SeriesMetadataProvider.kt create mode 100644 komga/src/main/kotlin/org/gotson/komga/infrastructure/web/Utils.kt diff --git a/komga/src/flyway/resources/db/migration/V20200702111235__library_metadata_import_flags.sql b/komga/src/flyway/resources/db/migration/V20200702111235__library_metadata_import_flags.sql new file mode 100644 index 00000000..57603ecd --- /dev/null +++ b/komga/src/flyway/resources/db/migration/V20200702111235__library_metadata_import_flags.sql @@ -0,0 +1,10 @@ +alter table library + add column import_comicinfo_book boolean default true; +alter table library + add column import_comicinfo_series boolean default true; +alter table library + add column import_comicinfo_collection boolean default true; +alter table library + add column import_epub_book boolean default true; +alter table library + add column import_epub_series boolean default true; diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt index 95a1d878..265bdcb4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt @@ -21,6 +21,10 @@ sealed class Task : Serializable { override fun uniqueId() = "REFRESH_BOOK_METADATA_$bookId" } + data class RefreshSeriesMetadata(val seriesId: Long) : Task() { + override fun uniqueId() = "REFRESH_SERIES_METADATA_$seriesId" + } + object BackupDatabase : Task() { override fun uniqueId(): String = "BACKUP_DATABASE" override fun toString(): String = "BackupDatabase" diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt index 51106902..b6868d1c 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt @@ -3,6 +3,7 @@ package org.gotson.komga.application.tasks import mu.KotlinLogging import org.gotson.komga.domain.persistence.BookRepository import org.gotson.komga.domain.persistence.LibraryRepository +import org.gotson.komga.domain.persistence.SeriesRepository import org.gotson.komga.domain.service.BookLifecycle import org.gotson.komga.domain.service.LibraryScanner import org.gotson.komga.domain.service.MetadataLifecycle @@ -20,6 +21,7 @@ class TaskHandler( private val taskReceiver: TaskReceiver, private val libraryRepository: LibraryRepository, private val bookRepository: BookRepository, + private val seriesRepository: SeriesRepository, private val libraryScanner: LibraryScanner, private val bookLifecycle: BookLifecycle, private val metadataLifecycle: MetadataLifecycle, @@ -53,8 +55,14 @@ class TaskHandler( is Task.RefreshBookMetadata -> bookRepository.findByIdOrNull(task.bookId)?.let { metadataLifecycle.refreshMetadata(it) + taskReceiver.refreshSeriesMetadata(it.seriesId) } ?: logger.warn { "Cannot execute task $task: Book does not exist" } + is Task.RefreshSeriesMetadata -> + seriesRepository.findByIdOrNull(task.seriesId)?.let { + metadataLifecycle.refreshMetadata(it) + } ?: logger.warn { "Cannot execute task $task: Series does not exist" } + is Task.BackupDatabase -> { databaseBackuper.backupDatabase() } diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt index 9141e57f..49bdeaa3 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt @@ -60,6 +60,10 @@ class TaskReceiver( submitTask(Task.RefreshBookMetadata(book.id)) } + fun refreshSeriesMetadata(seriesId: Long) { + submitTask(Task.RefreshSeriesMetadata(seriesId)) + } + fun databaseBackup() { submitTask(Task.BackupDatabase) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataPatch.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataPatch.kt index b2934092..c01d7d05 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataPatch.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataPatch.kt @@ -11,6 +11,5 @@ data class BookMetadataPatch( val publisher: String?, val ageRating: Int?, val releaseDate: LocalDate?, - val authors: List?, - val series: SeriesMetadataPatch? + val authors: List? ) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt index 623b5841..b7ca555b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt @@ -8,12 +8,17 @@ import java.time.LocalDateTime data class Library( val name: String, val root: URL, + val importComicInfoBook: Boolean = true, + val importComicInfoSeries: Boolean = true, + val importComicInfoCollection: Boolean = true, + val importEpubBook: Boolean = true, + val importEpubSeries: Boolean = true, + val id: Long = 0, + override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = LocalDateTime.now() ) : Auditable() { - constructor(name: String, root: String) : this(name, Paths.get(root).toUri().toURL()) - fun path(): Path = Paths.get(this.root.toURI()) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt index 0772187f..0ebe8a8e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt @@ -4,15 +4,15 @@ import org.gotson.komga.domain.model.Library interface LibraryRepository { fun findByIdOrNull(libraryId: Long): Library? + fun findById(libraryId: Long): Library fun findAll(): Collection fun findAllById(libraryIds: Collection): Collection - fun existsByName(name: String): Boolean - fun delete(libraryId: Long) fun deleteAll() fun insert(library: Library): Library + fun update(library: Library) fun count(): Long } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt index 164b2b35..48341234 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt @@ -31,26 +31,40 @@ class LibraryLifecycle( fun addLibrary(library: Library): Library { logger.info { "Adding new library: ${library.name} with root folder: ${library.root}" } + val existing = libraryRepository.findAll() + checkLibraryValidity(library, existing) + + return libraryRepository.insert(library).also { + taskReceiver.scanLibrary(it.id) + } + } + + fun updateLibrary(toUpdate: Library) { + logger.info { "Updating library: ${toUpdate.id}" } + + val existing = libraryRepository.findAll().filter { it.id != toUpdate.id } + checkLibraryValidity(toUpdate, existing) + + libraryRepository.update(toUpdate) + taskReceiver.scanLibrary(toUpdate.id) + } + + private fun checkLibraryValidity(library: Library, existing: Collection) { if (!Files.exists(library.path())) throw FileNotFoundException("Library root folder does not exist: ${library.root}") if (!Files.isDirectory(library.path())) throw DirectoryNotFoundException("Library root folder is not a folder: ${library.root}") - if (libraryRepository.existsByName(library.name)) + if (existing.map { it.name }.contains(library.name)) throw DuplicateNameException("Library name already exists") - libraryRepository.findAll().forEach { + existing.forEach { if (library.path().startsWith(it.path())) throw PathContainedInPath("Library path ${library.path()} is a child of existing library ${it.name}: ${it.path()}") if (it.path().startsWith(library.path())) throw PathContainedInPath("Library path ${library.path()} is a parent of existing library ${it.name}: ${it.path()}") } - - return libraryRepository.insert(library).let { - taskReceiver.scanLibrary(it.id) - it - } } fun deleteLibrary(library: Library) { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataLifecycle.kt index 68afaf3b..3442de95 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataLifecycle.kt @@ -2,10 +2,17 @@ package org.gotson.komga.domain.service import mu.KotlinLogging import org.gotson.komga.domain.model.Book +import org.gotson.komga.domain.model.Series +import org.gotson.komga.domain.model.SeriesMetadataPatch import org.gotson.komga.domain.persistence.BookMetadataRepository +import org.gotson.komga.domain.persistence.BookRepository +import org.gotson.komga.domain.persistence.LibraryRepository import org.gotson.komga.domain.persistence.MediaRepository import org.gotson.komga.domain.persistence.SeriesMetadataRepository import org.gotson.komga.infrastructure.metadata.BookMetadataProvider +import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider +import org.gotson.komga.infrastructure.metadata.comicinfo.ComicInfoProvider +import org.gotson.komga.infrastructure.metadata.epub.EpubMetadataProvider import org.springframework.stereotype.Service private val logger = KotlinLogging.logger {} @@ -13,33 +20,71 @@ private val logger = KotlinLogging.logger {} @Service class MetadataLifecycle( private val bookMetadataProviders: List, + private val seriesMetadataProviders: List, private val metadataApplier: MetadataApplier, private val mediaRepository: MediaRepository, private val bookMetadataRepository: BookMetadataRepository, - private val seriesMetadataRepository: SeriesMetadataRepository + private val seriesMetadataRepository: SeriesMetadataRepository, + private val libraryRepository: LibraryRepository, + private val bookRepository: BookRepository ) { fun refreshMetadata(book: Book) { logger.info { "Refresh metadata for book: $book" } val media = mediaRepository.findById(book.id) + val library = libraryRepository.findById(book.libraryId) + bookMetadataProviders.forEach { provider -> - provider.getBookMetadataFromBook(book, media)?.let { bPatch -> + when { + provider is ComicInfoProvider && !library.importComicInfoBook -> logger.info { "Library is not set to import book metadata from ComicInfo, skipping" } + provider is EpubMetadataProvider && !library.importEpubBook -> logger.info { "Library is not set to import book metadata from Epub, skipping" } + else -> { + logger.debug { "Provider: $provider" } + provider.getBookMetadataFromBook(book, media)?.let { bPatch -> - bookMetadataRepository.findById(book.id).let { - logger.debug { "Original metadata: $it" } - val patched = metadataApplier.apply(bPatch, it) - logger.debug { "Patched metadata: $patched" } + bookMetadataRepository.findById(book.id).let { + logger.debug { "Original metadata: $it" } + val patched = metadataApplier.apply(bPatch, it) + logger.debug { "Patched metadata: $patched" } - bookMetadataRepository.update(patched) + bookMetadataRepository.update(patched) + } + } } + } + } + } - bPatch.series?.let { sPatch -> - seriesMetadataRepository.findById(book.seriesId).let { - logger.debug { "Apply metadata for series: ${book.seriesId}" } + fun refreshMetadata(series: Series) { + logger.info { "Refresh metadata for series: $series" } + + val library = libraryRepository.findById(series.libraryId) + + seriesMetadataProviders.forEach { provider -> + when { + provider is ComicInfoProvider && !library.importComicInfoSeries -> logger.info { "Library is not set to import series metadata from ComicInfo, skipping" } + provider is EpubMetadataProvider && !library.importEpubSeries -> logger.info { "Library is not set to import series metadata from Epub, skipping" } + else -> { + logger.debug { "Provider: $provider" } + val patches = bookRepository.findBySeriesId(series.id) + .mapNotNull { provider.getSeriesMetadataFromBook(it, mediaRepository.findById(it.id)) } + + val title = patches.uniqueOrNull { it.title } + val titleSort = patches.uniqueOrNull { it.titleSort } + val status = patches.uniqueOrNull { it.status } + + if (title == null) logger.debug { "Ignoring title, values are not unique within series books" } + if (titleSort == null) logger.debug { "Ignoring sort title, values are not unique within series books" } + if (status == null) logger.debug { "Ignoring status, values are not unique within series books" } + + val aggregatedPatch = SeriesMetadataPatch(title, titleSort, status) + + seriesMetadataRepository.findById(series.id).let { + logger.debug { "Apply metadata for series: $series" } logger.debug { "Original metadata: $it" } - val patched = metadataApplier.apply(sPatch, it) + val patched = metadataApplier.apply(aggregatedPatch, it) logger.debug { "Patched metadata: $patched" } seriesMetadataRepository.update(patched) @@ -49,4 +94,13 @@ class MetadataLifecycle( } } + private fun Iterable.uniqueOrNull(transform: (T) -> R?): R? { + return this + .mapNotNull(transform) + .distinct() + .let { + if (it.size == 1) it.first() else null + } + } } + diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDao.kt index b98aaa85..fa5e352f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDao.kt @@ -23,6 +23,7 @@ import org.springframework.data.domain.PageImpl import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.stereotype.Component +import toFilePath import java.net.URL @Component @@ -207,7 +208,7 @@ class BookDtoDao( seriesId = seriesId, libraryId = libraryId, name = name, - url = URL(url).toURI().path, + url = URL(url).toFilePath(), number = number, created = createdDate.toUTC(), lastModified = lastModifiedDate.toUTC(), diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt index a1bf3881..de1696b5 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt @@ -8,6 +8,7 @@ import org.gotson.komga.jooq.tables.records.LibraryRecord import org.jooq.DSLContext import org.springframework.stereotype.Component import java.net.URL +import java.time.LocalDateTime @Component class LibraryDao( @@ -18,10 +19,17 @@ class LibraryDao( private val ul = Tables.USER_LIBRARY_SHARING override fun findByIdOrNull(libraryId: Long): Library? = + findOne(libraryId) + ?.toDomain() + + override fun findById(libraryId: Long): Library = + findOne(libraryId) + .toDomain() + + private fun findOne(libraryId: Long) = dsl.selectFrom(l) .where(l.ID.eq(libraryId)) .fetchOneInto(l) - ?.toDomain() override fun findAll(): Collection = dsl.selectFrom(l) @@ -34,12 +42,6 @@ class LibraryDao( .fetchInto(l) .map { it.toDomain() } - override fun existsByName(name: String): Boolean = - dsl.fetchExists( - dsl.selectFrom(l) - .where(l.NAME.equalIgnoreCase(name)) - ) - override fun delete(libraryId: Long) { dsl.transaction { config -> with(config.dsl()) @@ -67,9 +69,28 @@ class LibraryDao( .set(l.ID, id) .set(l.NAME, library.name) .set(l.ROOT, library.root.toString()) + .set(l.IMPORT_COMICINFO_BOOK, library.importComicInfoBook) + .set(l.IMPORT_COMICINFO_SERIES, library.importComicInfoSeries) + .set(l.IMPORT_COMICINFO_COLLECTION, library.importComicInfoCollection) + .set(l.IMPORT_EPUB_BOOK, library.importEpubBook) + .set(l.IMPORT_EPUB_SERIES, library.importEpubSeries) .execute() - return findByIdOrNull(id)!! + return findById(id) + } + + override fun update(library: Library) { + dsl.update(l) + .set(l.NAME, library.name) + .set(l.ROOT, library.root.toString()) + .set(l.IMPORT_COMICINFO_BOOK, library.importComicInfoBook) + .set(l.IMPORT_COMICINFO_SERIES, library.importComicInfoSeries) + .set(l.IMPORT_COMICINFO_COLLECTION, library.importComicInfoCollection) + .set(l.IMPORT_EPUB_BOOK, library.importEpubBook) + .set(l.IMPORT_EPUB_SERIES, library.importEpubSeries) + .set(l.LAST_MODIFIED_DATE, LocalDateTime.now()) + .where(l.ID.eq(library.id)) + .execute() } override fun count(): Long = dsl.fetchCount(l).toLong() @@ -79,6 +100,11 @@ class LibraryDao( Library( name = name, root = URL(root), + importComicInfoBook = importComicinfoBook, + importComicInfoSeries = importComicinfoSeries, + importComicInfoCollection = importComicinfoCollection, + importEpubBook = importEpubBook, + importEpubSeries = importEpubSeries, id = id, createdDate = createdDate, lastModifiedDate = lastModifiedDate diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt index 7bade109..8f230f11 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt @@ -22,6 +22,7 @@ import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort import org.springframework.stereotype.Component +import toFilePath import java.math.BigDecimal import java.net.URL @@ -188,7 +189,7 @@ class SeriesDtoDao( id = id, libraryId = libraryId, name = name, - url = URL(url).toURI().path, + url = URL(url).toFilePath(), created = createdDate.toUTC(), lastModified = lastModifiedDate.toUTC(), fileLastModified = fileLastModified.toUTC(), diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/SeriesMetadataProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/SeriesMetadataProvider.kt new file mode 100644 index 00000000..4abbda68 --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/SeriesMetadataProvider.kt @@ -0,0 +1,9 @@ +package org.gotson.komga.infrastructure.metadata + +import org.gotson.komga.domain.model.Book +import org.gotson.komga.domain.model.Media +import org.gotson.komga.domain.model.SeriesMetadataPatch + +interface SeriesMetadataProvider { + fun getSeriesMetadataFromBook(book: Book, media: Media): SeriesMetadataPatch? +} diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicinfo/ComicInfoProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicinfo/ComicInfoProvider.kt index 1403a050..fb5fb5ba 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicinfo/ComicInfoProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicinfo/ComicInfoProvider.kt @@ -10,6 +10,7 @@ import org.gotson.komga.domain.model.Media import org.gotson.komga.domain.model.SeriesMetadataPatch import org.gotson.komga.domain.service.BookAnalyzer import org.gotson.komga.infrastructure.metadata.BookMetadataProvider +import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider import org.gotson.komga.infrastructure.metadata.comicinfo.dto.ComicInfo import org.gotson.komga.infrastructure.metadata.comicinfo.dto.Manga import org.springframework.beans.factory.annotation.Autowired @@ -24,7 +25,7 @@ private const val COMIC_INFO = "ComicInfo.xml" class ComicInfoProvider( @Autowired(required = false) private val mapper: XmlMapper = XmlMapper(), private val bookAnalyzer: BookAnalyzer -) : BookMetadataProvider { +) : BookMetadataProvider, SeriesMetadataProvider { override fun getBookMetadataFromBook(book: Book, media: Media): BookMetadataPatch? { getComicInfo(book, media)?.let { comicInfo -> @@ -56,12 +57,18 @@ class ComicInfoProvider( comicInfo.publisher, comicInfo.ageRating?.ageRating, releaseDate, - authors.ifEmpty { null }, - SeriesMetadataPatch( - comicInfo.series, - comicInfo.series, - null - ) + authors.ifEmpty { null } + ) + } + return null + } + + override fun getSeriesMetadataFromBook(book: Book, media: Media): SeriesMetadataPatch? { + getComicInfo(book, media)?.let { comicInfo -> + return SeriesMetadataPatch( + comicInfo.series, + comicInfo.series, + null ) } return null diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt index a595b843..0522f4e7 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt @@ -8,6 +8,7 @@ import org.gotson.komga.domain.model.Media import org.gotson.komga.domain.model.SeriesMetadataPatch import org.gotson.komga.infrastructure.mediacontainer.EpubExtractor import org.gotson.komga.infrastructure.metadata.BookMetadataProvider +import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider import org.jsoup.Jsoup import org.springframework.stereotype.Service import java.time.LocalDate @@ -16,7 +17,7 @@ import java.time.format.DateTimeFormatter @Service class EpubMetadataProvider( private val epubExtractor: EpubExtractor -) : BookMetadataProvider { +) : BookMetadataProvider, SeriesMetadataProvider { private val relators = mapOf( "aut" to "writer", @@ -30,7 +31,7 @@ class EpubMetadataProvider( override fun getBookMetadataFromBook(book: Book, media: Media): BookMetadataPatch? { if (media.mediaType != "application/epub+zip") return null epubExtractor.getPackageFile(book.path())?.let { packageFile -> - val opf = Jsoup.parse(packageFile.toString()) + val opf = Jsoup.parse(packageFile) val title = opf.selectFirst("metadata > dc|title")?.text() val publisher = opf.selectFirst("metadata > dc|publisher")?.text() @@ -57,8 +58,6 @@ class EpubMetadataProvider( Author(name, relators[role] ?: "writer") } - val series = opf.selectFirst("metadata > meta[property=belongs-to-collection]")?.text() - return BookMetadataPatch( title = title, summary = description, @@ -68,13 +67,24 @@ class EpubMetadataProvider( publisher = publisher, ageRating = null, releaseDate = date, - authors = authors, - series = SeriesMetadataPatch(series, series, null) + authors = authors ) } return null } + override fun getSeriesMetadataFromBook(book: Book, media: Media): SeriesMetadataPatch? { + if (media.mediaType != "application/epub+zip") return null + epubExtractor.getPackageFile(book.path())?.let { packageFile -> + val opf = Jsoup.parse(packageFile) + + val series = opf.selectFirst("metadata > meta[property=belongs-to-collection]")?.text() + + return SeriesMetadataPatch(series, series, null) + } + return null + } + private fun parseDate(date: String): LocalDate? = try { LocalDate.parse(date, DateTimeFormatter.ISO_DATE) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/Utils.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/Utils.kt new file mode 100644 index 00000000..95376b31 --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/Utils.kt @@ -0,0 +1,8 @@ +import java.net.URL +import java.nio.file.Paths + +fun URL.toFilePath(): String = + Paths.get(this.toURI()).toString() + +fun filePathToUrl(filePath: String): URL = + Paths.get(filePath).toUri().toURL() diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/LibraryController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/LibraryController.kt index 017d0fc6..ee7b3665 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/LibraryController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/LibraryController.kt @@ -1,5 +1,6 @@ package org.gotson.komga.interfaces.rest +import filePathToUrl import mu.KotlinLogging import org.gotson.komga.application.tasks.TaskReceiver import org.gotson.komga.domain.model.DirectoryNotFoundException @@ -19,11 +20,13 @@ import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController import org.springframework.web.server.ResponseStatusException +import toFilePath import java.io.FileNotFoundException import javax.validation.Valid import javax.validation.constraints.NotBlank @@ -66,7 +69,17 @@ class LibraryController( @Valid @RequestBody library: LibraryCreationDto ): LibraryDto = try { - libraryLifecycle.addLibrary(Library(library.name, library.root)).toDto(includeRoot = principal.user.roleAdmin) + libraryLifecycle.addLibrary( + Library( + name = library.name, + root = filePathToUrl(library.root), + importComicInfoBook = library.importComicInfoBook, + importComicInfoSeries = library.importComicInfoSeries, + importComicInfoCollection = library.importComicInfoCollection, + importEpubBook = library.importEpubBook, + importEpubSeries = library.importEpubSeries + ) + ).toDto(includeRoot = principal.user.roleAdmin) } catch (e: Exception) { when (e) { is FileNotFoundException, @@ -78,6 +91,28 @@ class LibraryController( } } + @PutMapping("/{id}") + @PreAuthorize("hasRole('$ROLE_ADMIN')") + @ResponseStatus(HttpStatus.NO_CONTENT) + fun updateOne( + @PathVariable id: Long, + @Valid @RequestBody library: LibraryUpdateDto + ) { + libraryRepository.findByIdOrNull(id)?.let { + val toUpdate = Library( + id = id, + name = library.name, + root = filePathToUrl(library.root), + importComicInfoBook = library.importComicInfoBook, + importComicInfoSeries = library.importComicInfoSeries, + importComicInfoCollection = library.importComicInfoCollection, + importEpubBook = library.importEpubBook, + importEpubSeries = library.importEpubSeries + ) + libraryLifecycle.updateLibrary(toUpdate) + } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) + } + @DeleteMapping("/{id}") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.NO_CONTENT) @@ -117,17 +152,42 @@ class LibraryController( data class LibraryCreationDto( @get:NotBlank val name: String, - @get:NotBlank val root: String + @get:NotBlank val root: String, + val importComicInfoBook: Boolean = true, + val importComicInfoSeries: Boolean = true, + val importComicInfoCollection: Boolean = true, + val importEpubBook: Boolean = true, + val importEpubSeries: Boolean = true ) data class LibraryDto( val id: Long, val name: String, - val root: String + val root: String, + val importComicInfoBook: Boolean, + val importComicInfoSeries: Boolean, + val importComicInfoCollection: Boolean, + val importEpubBook: Boolean, + val importEpubSeries: Boolean +) + +data class LibraryUpdateDto( + @get:NotBlank val name: String, + @get:NotBlank val root: String, + val importComicInfoBook: Boolean, + val importComicInfoSeries: Boolean, + val importComicInfoCollection: Boolean, + val importEpubBook: Boolean, + val importEpubSeries: Boolean ) fun Library.toDto(includeRoot: Boolean) = LibraryDto( id = id, name = name, - root = if (includeRoot) root.toURI().path else "" + root = if (includeRoot) this.root.toFilePath() else "", + importComicInfoBook = importComicInfoBook, + importComicInfoSeries = importComicInfoSeries, + importComicInfoCollection = importComicInfoCollection, + importEpubBook = importEpubBook, + importEpubSeries = importEpubSeries ) diff --git a/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt b/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt index 35d6e93a..99ba7879 100644 --- a/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt @@ -2,8 +2,12 @@ package org.gotson.komga.application.tasks import com.ninjasquad.springmockk.MockkBean import io.mockk.every +import io.mockk.just +import io.mockk.runs import io.mockk.verify import mu.KotlinLogging +import org.gotson.komga.domain.model.Book +import org.gotson.komga.domain.model.Series import org.gotson.komga.domain.model.makeBook import org.gotson.komga.domain.model.makeLibrary import org.gotson.komga.domain.model.makeSeries @@ -78,7 +82,8 @@ class TaskHandlerTest( seriesLifecycle.addBooks(it, listOf(book)) } - every { mockMetadataLifecycle.refreshMetadata(any()) } answers { Thread.sleep(1_000) } + every { mockMetadataLifecycle.refreshMetadata(any()) } answers { Thread.sleep(1_000) } + every { mockMetadataLifecycle.refreshMetadata(any()) } just runs val createdBook = bookRepository.findAll().first() @@ -88,6 +93,6 @@ class TaskHandlerTest( Thread.sleep(5_000) - verify(atLeast = 1, atMost = 3) { mockMetadataLifecycle.refreshMetadata(any()) } + verify(atLeast = 1, atMost = 3) { mockMetadataLifecycle.refreshMetadata(any()) } } } diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt index 3438b876..c3307825 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt @@ -8,6 +8,7 @@ import org.gotson.komga.domain.model.Library import org.gotson.komga.domain.model.PathContainedInPath import org.gotson.komga.domain.persistence.LibraryRepository import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired @@ -15,6 +16,7 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabas import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.io.FileNotFoundException +import java.net.URL import java.nio.file.Files @ExtendWith(SpringExtension::class) @@ -30,74 +32,213 @@ class LibraryLifecycleTest( libraryRepository.deleteAll() } - @Test - fun `when adding library with non-existent root folder then exception is thrown`() { - // when - val thrown = catchThrowable { libraryLifecycle.addLibrary(Library("test", "/non-existent")) } + @Nested + inner class Add { + @Test + fun `when adding library with non-existent root folder then exception is thrown`() { + // when + val thrown = catchThrowable { libraryLifecycle.addLibrary(Library("test", URL("file:/non-existent"))) } - // then - assertThat(thrown).isInstanceOf(FileNotFoundException::class.java) - } - - @Test - fun `when adding library with non-directory root folder then exception is thrown`() { - // when - val thrown = catchThrowable { - libraryLifecycle.addLibrary(Library("test", Files.createTempFile(null, null).toUri().toURL())) + // then + assertThat(thrown).isInstanceOf(FileNotFoundException::class.java) } - // then - assertThat(thrown).isInstanceOf(DirectoryNotFoundException::class.java) - } + @Test + fun `when adding library with non-directory root folder then exception is thrown`() { + // when + val thrown = catchThrowable { + libraryLifecycle.addLibrary(Library("test", Files.createTempFile(null, null).toUri().toURL())) + } - @Test - fun `given existing library when adding library with same name then exception is thrown`() { - // given - libraryLifecycle.addLibrary(Library("test", Files.createTempDirectory(null).toUri().toURL())) + // then + assertThat(thrown).isInstanceOf(DirectoryNotFoundException::class.java) + } - // when - val thrown = catchThrowable { + @Test + fun `given existing library when adding library with same name then exception is thrown`() { + // given libraryLifecycle.addLibrary(Library("test", Files.createTempDirectory(null).toUri().toURL())) + + // when + val thrown = catchThrowable { + libraryLifecycle.addLibrary(Library("test", Files.createTempDirectory(null).toUri().toURL())) + } + + // then + assertThat(thrown).isInstanceOf(DuplicateNameException::class.java) } - // then - assertThat(thrown).isInstanceOf(DuplicateNameException::class.java) - } - - @Test - fun `given existing library when adding library with root folder as child of existing library then exception is thrown`() { - // given - val parent = Files.createTempDirectory(null) - libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL())) - - // when - val child = Files.createTempDirectory(parent, "") - val thrown = catchThrowable { - libraryLifecycle.addLibrary(Library("child", child.toUri().toURL())) - } - - // then - assertThat(thrown) - .isInstanceOf(PathContainedInPath::class.java) - .hasMessageContaining("child") - } - - @Test - fun `given existing library when adding library with root folder as parent of existing library then exception is thrown`() { - // given - val parent = Files.createTempDirectory(null) - val child = Files.createTempDirectory(parent, null) - libraryLifecycle.addLibrary(Library("child", child.toUri().toURL())) - - // when - val thrown = catchThrowable { + @Test + fun `given existing library when adding library with root folder as child of existing library then exception is thrown`() { + // given + val parent = Files.createTempDirectory(null) libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL())) + + // when + val child = Files.createTempDirectory(parent, "") + val thrown = catchThrowable { + libraryLifecycle.addLibrary(Library("child", child.toUri().toURL())) + } + + // then + assertThat(thrown) + .isInstanceOf(PathContainedInPath::class.java) + .hasMessageContaining("child") } - // then - assertThat(thrown) - .isInstanceOf(PathContainedInPath::class.java) - .hasMessageContaining("parent") + @Test + fun `given existing library when adding library with root folder as parent of existing library then exception is thrown`() { + // given + val parent = Files.createTempDirectory(null) + val child = Files.createTempDirectory(parent, null) + libraryLifecycle.addLibrary(Library("child", child.toUri().toURL())) + + // when + val thrown = catchThrowable { + libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL())) + } + + // then + assertThat(thrown) + .isInstanceOf(PathContainedInPath::class.java) + .hasMessageContaining("parent") + } } + @Nested + inner class Update { + private val rootFolder = Files.createTempDirectory(null) + private val library = Library("Existing", rootFolder.toUri().toURL()) + + @Test + fun `given existing library when updating with non-existent root folder then exception is thrown`() { + // given + val existing = libraryLifecycle.addLibrary(library) + + // when + val toUpdate = existing.copy(name = "test", root = URL("file:/non-existent")) + val thrown = catchThrowable { libraryLifecycle.updateLibrary(toUpdate) } + + // then + assertThat(thrown).isInstanceOf(FileNotFoundException::class.java) + } + + @Test + fun `given existing library when updating with non-directory root folder then exception is thrown`() { + // given + val existing = libraryLifecycle.addLibrary(library) + + // when + val toUpdate = existing.copy(name = "test", root = Files.createTempFile(null, null).toUri().toURL()) + val thrown = catchThrowable { + libraryLifecycle.updateLibrary(toUpdate) + } + + // then + assertThat(thrown).isInstanceOf(DirectoryNotFoundException::class.java) + } + + @Test + fun `given single existing library when updating library with same name then it is updated`() { + // given + val existing = libraryLifecycle.addLibrary(library) + + // when + val thrown = catchThrowable { + libraryLifecycle.updateLibrary(existing) + } + + // then + assertThat(thrown).doesNotThrowAnyException() + } + + @Test + fun `given existing library when updating library with same name then exception is thrown`() { + // given + libraryLifecycle.addLibrary(Library("test", Files.createTempDirectory(null).toUri().toURL())) + val existing = libraryLifecycle.addLibrary(library) + + // when + val toUpdate = existing.copy(name = "test", root = Files.createTempDirectory(null).toUri().toURL()) + val thrown = catchThrowable { + libraryLifecycle.updateLibrary(toUpdate) + } + + // then + assertThat(thrown).isInstanceOf(DuplicateNameException::class.java) + } + + @Test + fun `given single existing library when updating library with root folder as child of existing library then no exception is thrown`() { + // given + val existing = libraryLifecycle.addLibrary(library) + + // when + val child = Files.createTempDirectory(rootFolder, "") + val toUpdate = existing.copy(root = child.toUri().toURL()) + val thrown = catchThrowable { + libraryLifecycle.updateLibrary(toUpdate) + } + + // then + assertThat(thrown).doesNotThrowAnyException() + } + + @Test + fun `given existing library when updating library with root folder as child of existing library then exception is thrown`() { + // given + val parent = Files.createTempDirectory(null) + libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL())) + val existing = libraryLifecycle.addLibrary(library) + + // when + val child = Files.createTempDirectory(parent, "") + val toUpdate = existing.copy(root = child.toUri().toURL()) + val thrown = catchThrowable { + libraryLifecycle.updateLibrary(toUpdate) + } + + // then + assertThat(thrown) + .isInstanceOf(PathContainedInPath::class.java) + .hasMessageContaining("child") + } + + @Test + fun `given single existing library when updating library with root folder as parent of existing library then no exception is thrown`() { + // given + val parent = Files.createTempDirectory(null) + val child = Files.createTempDirectory(parent, null) + val existing = libraryLifecycle.addLibrary(Library("child", child.toUri().toURL())) + + // when + val toUpdate = existing.copy(root = parent.toUri().toURL()) + val thrown = catchThrowable { + libraryLifecycle.updateLibrary(toUpdate) + } + + // then + assertThat(thrown).doesNotThrowAnyException() + } + + @Test + fun `given existing library when updating library with root folder as parent of existing library then exception is thrown`() { + // given + val parent = Files.createTempDirectory(null) + val child = Files.createTempDirectory(parent, null) + libraryLifecycle.addLibrary(Library("child", child.toUri().toURL())) + val existing = libraryLifecycle.addLibrary(library) + + // when + val toUpdate = existing.copy(root = parent.toUri().toURL()) + val thrown = catchThrowable { + libraryLifecycle.updateLibrary(toUpdate) + } + + // then + assertThat(thrown) + .isInstanceOf(PathContainedInPath::class.java) + .hasMessageContaining("parent") + } + } } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt index 35029b65..c5b7342b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt @@ -45,6 +45,46 @@ class LibraryDaoTest( assertThat(created.root).isEqualTo(library.root) } + @Test + fun `given existing library when updating then it is persisted`() { + val library = Library( + name = "Library", + root = URL("file://library") + ) + val created = libraryDao.insert(library) + + Thread.sleep(5) + + val modificationDate = LocalDateTime.now() + + val updated = created.copy( + name = "LibraryUpdated", + root = URL("file://library2"), + importEpubSeries = false, + importEpubBook = false, + importComicInfoCollection = false, + importComicInfoSeries = false, + importComicInfoBook = false + ) + + libraryDao.update(updated) + val modified = libraryDao.findById(updated.id) + + assertThat(modified.id).isEqualTo(updated.id) + assertThat(modified.createdDate).isEqualTo(updated.createdDate) + assertThat(modified.lastModifiedDate) + .isAfterOrEqualTo(modificationDate) + .isNotEqualTo(updated.lastModifiedDate) + + assertThat(modified.name).isEqualTo(updated.name) + assertThat(modified.root).isEqualTo(updated.root) + assertThat(modified.importEpubSeries).isEqualTo(updated.importEpubSeries) + assertThat(modified.importEpubBook).isEqualTo(updated.importEpubBook) + assertThat(modified.importComicInfoCollection).isEqualTo(updated.importComicInfoCollection) + assertThat(modified.importComicInfoSeries).isEqualTo(updated.importComicInfoSeries) + assertThat(modified.importComicInfoBook).isEqualTo(updated.importComicInfoBook) + } + @Test fun `given a library when deleting then it is deleted`() { val library = Library( @@ -141,19 +181,4 @@ class LibraryDaoTest( assertThat(found).isNull() } - - @Test - fun `given libraries when checking if exists by name then returns true or false`() { - val library = Library( - name = "Library", - root = URL("file://library") - ) - libraryDao.insert(library) - - val exists = libraryDao.existsByName("LIBRARY") - val notExists = libraryDao.existsByName("LIBRARY2") - - assertThat(exists).isTrue() - assertThat(notExists).isFalse() - } } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt index 5d258ca2..18dc56cd 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt @@ -129,19 +129,17 @@ class MediaDaoTest( val modificationDate = LocalDateTime.now() - val updated = with(created) { - copy( - status = Media.Status.ERROR, - mediaType = "application/rar", - thumbnail = Random.nextBytes(1), - pages = listOf(BookPage( - fileName = "2.png", - mediaType = "image/png" - )), - files = listOf("id.txt"), - comment = "comment2" - ) - } + val updated = created.copy( + status = Media.Status.ERROR, + mediaType = "application/rar", + thumbnail = Random.nextBytes(1), + pages = listOf(BookPage( + fileName = "2.png", + mediaType = "image/png" + )), + files = listOf("id.txt"), + comment = "comment2" + ) mediaDao.update(updated) val modified = mediaDao.findById(updated.bookId) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicinfo/ComicInfoProviderTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicinfo/ComicInfoProviderTest.kt index aee16c2b..9cead5cc 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicinfo/ComicInfoProviderTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicinfo/ComicInfoProviderTest.kt @@ -157,9 +157,9 @@ class ComicInfoProviderTest { every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo - val patch = comicInfoProvider.getBookMetadataFromBook(book, media)!!.series + val patch = comicInfoProvider.getSeriesMetadataFromBook(book, media)!! - with(patch!!) { + with(patch) { assertThat(title).isEqualTo("series") assertThat(titleSort).isEqualTo("series") assertThat(status).isNull() diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/BookControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/BookControllerTest.kt index fd9bfb2e..91f44b76 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/BookControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/BookControllerTest.kt @@ -22,6 +22,7 @@ import org.gotson.komga.domain.service.KomgaUserLifecycle import org.gotson.komga.domain.service.LibraryLifecycle import org.gotson.komga.domain.service.SeriesLifecycle import org.gotson.komga.infrastructure.security.KomgaPrincipal +import org.hamcrest.Matchers import org.hamcrest.core.IsNull import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterEach @@ -451,10 +452,9 @@ class BookControllerTest( val book = bookRepository.findAll().first() - val url = "/1.cbr" val validation: MockMvcResultMatchersDsl.() -> Unit = { status { isOk } - jsonPath("$.content[0].url") { value(url) } + jsonPath("$.content[0].url") { value(Matchers.containsString("1.cbr")) } } mockMvc.get("/api/v1/books") @@ -469,7 +469,7 @@ class BookControllerTest( mockMvc.get("/api/v1/books/${book.id}") .andExpect { status { isOk } - jsonPath("$.url") { value(url) } + jsonPath("$.url") { value(Matchers.containsString("1.cbr")) } } } } diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/LibraryControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/LibraryControllerTest.kt index 6be27636..5f280eb6 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/LibraryControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/LibraryControllerTest.kt @@ -4,6 +4,7 @@ import org.gotson.komga.domain.model.ROLE_ADMIN import org.gotson.komga.domain.model.ROLE_USER import org.gotson.komga.domain.model.makeLibrary import org.gotson.komga.domain.persistence.LibraryRepository +import org.hamcrest.Matchers import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Nested @@ -136,13 +137,13 @@ class LibraryControllerTest( mockMvc.get(route) .andExpect { status { isOk } - jsonPath("$[0].root") { value("/library1") } + jsonPath("$[0].root") { value(Matchers.containsString("library1")) } } mockMvc.get("${route}/${library.id}") .andExpect { status { isOk } - jsonPath("$.root") { value("/library1") } + jsonPath("$.root") { value(Matchers.containsString("library1")) } } } } diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesControllerTest.kt index 5663a927..5df4ddbc 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesControllerTest.kt @@ -326,10 +326,9 @@ class SeriesControllerTest( } } - val url = "/series" val validation: MockMvcResultMatchersDsl.() -> Unit = { status { isOk } - jsonPath("$.content[0].url") { value(url) } + jsonPath("$.content[0].url") { value(Matchers.containsString("series")) } } mockMvc.get("/api/v1/series") @@ -344,7 +343,7 @@ class SeriesControllerTest( mockMvc.get("/api/v1/series/${createdSeries.id}") .andExpect { status { isOk } - jsonPath("$.url") { value(url) } + jsonPath("$.url") { value(Matchers.containsString("series")) } } } }