diff --git a/.gitignore b/.gitignore
index 99fd8986..817f8489 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,5 @@ replay_pid*
application-local.yml
target
+index-data
+index-data-test
diff --git a/pom.xml b/pom.xml
index 4f12bbfe..a95f6dbc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,7 @@
1.9.23
1.6.3
2.15.4
+ 9.10.0
@@ -113,6 +114,18 @@
0.2.0
+
+
+ org.apache.lucene
+ lucene-core
+ ${lucene.version}
+
+
+ org.apache.lucene
+ lucene-analysis-common
+ ${lucene.version}
+
+
org.springframework.boot
diff --git a/src/main/kotlin/no/nb/bikube/BikubeApplication.kt b/src/main/kotlin/no/nb/bikube/BikubeApplication.kt
index d5ab281d..546cb56a 100644
--- a/src/main/kotlin/no/nb/bikube/BikubeApplication.kt
+++ b/src/main/kotlin/no/nb/bikube/BikubeApplication.kt
@@ -3,9 +3,11 @@ package no.nb.bikube
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication
+import org.springframework.scheduling.annotation.EnableScheduling
@ConfigurationPropertiesScan
@SpringBootApplication
+@EnableScheduling
class BikubeApplication
fun main(args: Array) {
diff --git a/src/main/kotlin/no/nb/bikube/catalogue/collections/repository/CollectionsRepository.kt b/src/main/kotlin/no/nb/bikube/catalogue/collections/repository/CollectionsRepository.kt
index 4a177f08..0888ab8a 100644
--- a/src/main/kotlin/no/nb/bikube/catalogue/collections/repository/CollectionsRepository.kt
+++ b/src/main/kotlin/no/nb/bikube/catalogue/collections/repository/CollectionsRepository.kt
@@ -33,12 +33,14 @@ class CollectionsRepository(
)
}
- fun getTitleByName(name: String): Mono {
- return searchTexts(
+ fun getAllNewspaperTitles(page: Int = 1): Mono {
+ return getRecordsWebClientRequest(
"record_type=${CollectionsRecordType.WORK} and " +
- "work.description_type=${CollectionsDescriptionType.SERIAL} and " +
- "title=\"${name}\""
- )
+ "work.description_type=${CollectionsDescriptionType.SERIAL}",
+ CollectionsDatabase.TEXTS,
+ limit = 50,
+ from = (page-1) * 50 + 1
+ ).bodyToMono()
}
fun searchPublisher(name: String): Mono {
@@ -81,7 +83,12 @@ class CollectionsRepository(
return getRecordsWebClientRequest(query, db).bodyToMono()
}
- private fun getRecordsWebClientRequest(query: String, db: CollectionsDatabase): WebClient.ResponseSpec {
+ private fun getRecordsWebClientRequest(
+ query: String,
+ db: CollectionsDatabase,
+ limit: Int = 10,
+ from: Int = 1
+ ): WebClient.ResponseSpec {
return webClient()
.get()
.uri {
@@ -89,6 +96,8 @@ class CollectionsRepository(
.queryParam("database", db.value)
.queryParam("output", "json")
.queryParam("search", query)
+ .queryParam("limit", limit)
+ .queryParam("startfrom", from)
.build()
}
.retrieve()
diff --git a/src/main/kotlin/no/nb/bikube/core/controller/CoreController.kt b/src/main/kotlin/no/nb/bikube/core/controller/CoreController.kt
index 27d701bb..99bbd801 100644
--- a/src/main/kotlin/no/nb/bikube/core/controller/CoreController.kt
+++ b/src/main/kotlin/no/nb/bikube/core/controller/CoreController.kt
@@ -19,6 +19,7 @@ import no.nb.bikube.core.model.CatalogueRecord
import no.nb.bikube.core.model.Item
import no.nb.bikube.core.model.Title
import no.nb.bikube.newspaper.service.NewspaperService
+import no.nb.bikube.newspaper.service.TitleIndexService
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
@@ -33,7 +34,8 @@ import java.time.LocalDate
@Tag(name = "Catalogue objects", description = "Endpoints related to catalog data for all text material")
@RequestMapping("")
class CoreController (
- private val newspaperService: NewspaperService
+ private val newspaperService: NewspaperService,
+ private val titleIndexService: TitleIndexService
){
companion object {
const val DATE_REGEX = "^(17|18|19|20)\\d{2}(-)?(0[1-9]|1[0-2])(-)?(0[1-9]|[12][0-9]|3[01])$"
@@ -86,10 +88,10 @@ class CoreController (
fun searchTitle(
@RequestParam searchTerm: String,
@RequestParam materialType: MaterialType
- ): ResponseEntity> {
+ ): ResponseEntity> {
if (searchTerm.isEmpty()) throw BadRequestBodyException("Search term cannot be empty.")
return when(materialTypeToCatalogueName(materialType)) {
- CatalogueName.COLLECTIONS -> ResponseEntity.ok(newspaperService.searchTitleByName(searchTerm))
+ CatalogueName.COLLECTIONS -> ResponseEntity.ok(titleIndexService.searchTitle(searchTerm))
else -> throw NotSupportedException("Material type $materialType is not supported.")
}
}
diff --git a/src/main/kotlin/no/nb/bikube/core/controller/GlobalControllerExceptionHandler.kt b/src/main/kotlin/no/nb/bikube/core/controller/GlobalControllerExceptionHandler.kt
index a76e4933..c50da956 100644
--- a/src/main/kotlin/no/nb/bikube/core/controller/GlobalControllerExceptionHandler.kt
+++ b/src/main/kotlin/no/nb/bikube/core/controller/GlobalControllerExceptionHandler.kt
@@ -4,6 +4,7 @@ import jakarta.validation.ConstraintViolationException
import no.nb.bikube.core.exception.BadRequestBodyException
import no.nb.bikube.core.exception.NotSupportedException
import no.nb.bikube.core.exception.RecordAlreadyExistsException
+import no.nb.bikube.core.exception.SearchIndexNotAvailableException
import no.nb.bikube.core.util.logger
import org.springframework.http.HttpStatus
import org.springframework.http.ProblemDetail
@@ -58,6 +59,17 @@ class GlobalControllerExceptionHandler {
return problemDetail
}
+
+ @ExceptionHandler
+ fun handlerSearchIndexNotAvailableException(exception: SearchIndexNotAvailableException): ProblemDetail {
+ logger().error("SearchIndexNotAvailableException occurred")
+
+ val problemDetail = ProblemDetail.forStatus(HttpStatus.SERVICE_UNAVAILABLE)
+ problemDetail.detail = "The search index is unavailable"
+ problemDetail.addDefaultProperties()
+
+ return problemDetail
+ }
}
fun ProblemDetail.addDefaultProperties() {
diff --git a/src/main/kotlin/no/nb/bikube/core/exception/SearchIndexNotAvailableException.kt b/src/main/kotlin/no/nb/bikube/core/exception/SearchIndexNotAvailableException.kt
new file mode 100644
index 00000000..fdaaa3e5
--- /dev/null
+++ b/src/main/kotlin/no/nb/bikube/core/exception/SearchIndexNotAvailableException.kt
@@ -0,0 +1,3 @@
+package no.nb.bikube.core.exception
+
+class SearchIndexNotAvailableException: Exception()
diff --git a/src/main/kotlin/no/nb/bikube/newspaper/controller/TitleController.kt b/src/main/kotlin/no/nb/bikube/newspaper/controller/TitleController.kt
index e53dd66e..6983a029 100644
--- a/src/main/kotlin/no/nb/bikube/newspaper/controller/TitleController.kt
+++ b/src/main/kotlin/no/nb/bikube/newspaper/controller/TitleController.kt
@@ -14,6 +14,7 @@ import no.nb.bikube.core.model.Title
import no.nb.bikube.core.model.inputDto.TitleInputDto
import no.nb.bikube.core.util.logger
import no.nb.bikube.newspaper.service.NewspaperService
+import no.nb.bikube.newspaper.service.TitleIndexService
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
@@ -27,7 +28,8 @@ import reactor.core.publisher.Mono
@Tag(name="Newspaper titles", description="Endpoints related to newspaper titles.")
@RequestMapping("/newspapers/titles")
class TitleController (
- private val newspaperService: NewspaperService
+ private val newspaperService: NewspaperService,
+ private val titleIndexService: TitleIndexService
) {
@PostMapping("/", produces = [MediaType.APPLICATION_JSON_VALUE])
@Operation(summary = "Create a newspaper title")
@@ -68,7 +70,10 @@ class TitleController (
return Mono.`when`(publisherMono, locationMono, languageMono)
.then(newspaperService.createNewspaperTitle(title))
- .map { createdTitle -> ResponseEntity.status(HttpStatus.CREATED).body(createdTitle) }
+ .map { createdTitle ->
+ titleIndexService.addTitle(createdTitle)
+ ResponseEntity.status(HttpStatus.CREATED).body(createdTitle)
+ }
.doOnSuccess { responseEntity ->
logger().info("Newspaper title created with id: ${responseEntity.body?.catalogueId}")
}
diff --git a/src/main/kotlin/no/nb/bikube/newspaper/service/NewspaperService.kt b/src/main/kotlin/no/nb/bikube/newspaper/service/NewspaperService.kt
index d03e9ce5..60db80a0 100644
--- a/src/main/kotlin/no/nb/bikube/newspaper/service/NewspaperService.kt
+++ b/src/main/kotlin/no/nb/bikube/newspaper/service/NewspaperService.kt
@@ -18,6 +18,7 @@ import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.publisher.SynchronousSink
import reactor.kotlin.core.publisher.toMono
+import reactor.util.function.Tuple2
import java.time.LocalDate
import java.time.format.DateTimeFormatter
@@ -95,10 +96,20 @@ class NewspaperService (
}
}
- fun searchTitleByName(name: String): Flux {
- return collectionsRepository.getTitleByName(name)
- .flatMapIterable { it.getObjects() ?: emptyList() }
- .map { mapCollectionsObjectToGenericTitle(it) }
+ fun getTitlesPage(pageNumber: Int): Mono, Int>> {
+ val pageContent = collectionsRepository.getAllNewspaperTitles(pageNumber)
+ .mapNotNull { model ->
+ model.getObjects()
+ ?. map { mapCollectionsObjectToGenericTitle(it) }
+ }
+ return Mono.zip(pageContent, Mono.just(pageNumber))
+ }
+
+ fun getAllTitles(): Mono> {
+ return getTitlesPage(1)
+ .expand { p -> getTitlesPage(p.t2 + 1) }
+ .flatMapIterable { it.t1 }
+ .collectList()
}
fun getItemsByTitle(
diff --git a/src/main/kotlin/no/nb/bikube/newspaper/service/TitleIndexService.kt b/src/main/kotlin/no/nb/bikube/newspaper/service/TitleIndexService.kt
new file mode 100644
index 00000000..270c83ae
--- /dev/null
+++ b/src/main/kotlin/no/nb/bikube/newspaper/service/TitleIndexService.kt
@@ -0,0 +1,161 @@
+package no.nb.bikube.newspaper.service
+
+import no.nb.bikube.core.exception.SearchIndexNotAvailableException
+import no.nb.bikube.core.model.Title
+import no.nb.bikube.core.util.logger
+import org.apache.lucene.analysis.core.LowerCaseFilterFactory
+import org.apache.lucene.analysis.core.WhitespaceTokenizerFactory
+import org.apache.lucene.analysis.custom.CustomAnalyzer
+import org.apache.lucene.document.Document
+import org.apache.lucene.document.Field
+import org.apache.lucene.document.StoredField
+import org.apache.lucene.document.TextField
+import org.apache.lucene.index.IndexWriter
+import org.apache.lucene.index.IndexWriterConfig
+import org.apache.lucene.index.Term
+import org.apache.lucene.search.BooleanClause
+import org.apache.lucene.search.BooleanQuery
+import org.apache.lucene.search.SearcherManager
+import org.apache.lucene.search.WildcardQuery
+import org.apache.lucene.store.FSDirectory
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
+import org.springframework.scheduling.annotation.Scheduled
+import org.springframework.stereotype.Service
+import java.nio.file.Paths
+import java.time.LocalDate
+import java.util.concurrent.atomic.AtomicInteger
+
+interface TitleIndexService {
+ fun indexAllTitles()
+ fun addTitle(title: Title)
+ fun searchTitle(query: String): List
+}
+
+@ConditionalOnProperty(
+ prefix = "search-index",
+ name = ["enabled"],
+ havingValue = "true"
+)
+@Service
+class TitleIndexServiceImpl(
+ private val newspaperService: NewspaperService,
+ @Value("\${search-index.path}") private val searchIndexPath: String
+): TitleIndexService {
+ private val titleAnalyzer = CustomAnalyzer.builder()
+ .withTokenizer(WhitespaceTokenizerFactory.NAME)
+ .addTokenFilter(LowerCaseFilterFactory.NAME)
+ .build()
+
+ private val indexWriter = IndexWriter(
+ FSDirectory.open(Paths.get(searchIndexPath)),
+ IndexWriterConfig(titleAnalyzer)
+ )
+
+ private val searcherManager = SearcherManager(indexWriter, null)
+
+ private fun makeDocument(title: Title): Document? {
+ if (title.name == null)
+ return null
+ val document = Document()
+ document.add(TextField("name", title.name, Field.Store.YES))
+ document.add(StoredField("catalogueId", title.catalogueId))
+ title.startDate ?. let { document.add(StoredField("startDate", it.toString())) }
+ title.endDate ?. let { document.add(StoredField("endDate", it.toString())) }
+ title.publisher ?. let { document.add(StoredField("publisher", it)) }
+ title.publisherPlace ?. let { document.add(StoredField("publisherPlace", it)) }
+ title.language ?. let { document.add(StoredField("language", it)) }
+ title.materialType ?. let { document.add(StoredField("materialType", it)) }
+ return document
+ }
+
+ private val indexStatus = AtomicInteger(IndexStatus.UNINITIALIZED.ordinal)
+
+ @Scheduled(
+ initialDelayString = "\${search-index.initial-delay}",
+ fixedDelayString = "\${search-index.rebuild-index-delay}"
+ )
+ override fun indexAllTitles() {
+ if (indexStatus.get() == IndexStatus.INDEXING.ordinal)
+ return
+ logger().debug("Start fetching all titles to index...")
+ newspaperService.getAllTitles()
+ .map { titles ->
+ titles.mapNotNull { makeDocument(it) }
+ }
+ .doOnSuccess { documents ->
+ indexStatus.set(IndexStatus.INDEXING.ordinal)
+ indexWriter.deleteAll()
+ indexWriter.addDocuments(documents)
+ indexWriter.commit()
+ searcherManager.maybeRefresh()
+ indexStatus.set(IndexStatus.READY.ordinal)
+ logger().info("Titles index ready")
+ }
+ .subscribe()
+ }
+
+ override fun addTitle(title: Title) {
+ logger().debug("Adding title ${title.name} to index")
+ indexWriter.addDocument(makeDocument(title))
+ indexWriter.commit()
+ searcherManager.maybeRefresh()
+ }
+
+ @Throws(SearchIndexNotAvailableException::class)
+ override fun searchTitle(query: String): List {
+ if (indexStatus.get() != IndexStatus.READY.ordinal)
+ throw SearchIndexNotAvailableException()
+ val indexSearcher = searcherManager.acquire()
+ val terms = query.split(Regex("\\s+"))
+ val queryBuilder = BooleanQuery.Builder()
+ terms.forEach {
+ queryBuilder.add(
+ WildcardQuery(Term("name", "*${it.lowercase()}*")),
+ BooleanClause.Occur.MUST
+ )
+ }
+
+ val q = queryBuilder.build()
+ logger().debug("Title search: {}", q)
+ val storedFields = indexSearcher.storedFields()
+ return indexSearcher.search(q, 50)
+ .scoreDocs
+ .map { storedFields.document(it.doc) }
+ .map { doc ->
+ Title(
+ catalogueId = doc.get("catalogueId"),
+ name = doc.get("name"),
+ startDate = doc.get("startDate") ?. let { LocalDate.parse(it) },
+ endDate = doc.get("endDate") ?. let { LocalDate.parse(it) },
+ publisher = doc.get("publisher"),
+ publisherPlace = doc.get("publisherPlace"),
+ language = doc.get("language"),
+ materialType = doc.get("materialType")
+ )
+ }
+ }
+
+ @Scheduled(fixedDelayString = "\${search-index.searcher-refresh-delay}")
+ fun refresh() {
+ searcherManager.maybeRefresh()
+ }
+}
+
+@ConditionalOnProperty(
+ prefix = "search-index",
+ name = ["enabled"],
+ havingValue = "false"
+)
+@Service
+class TitleIndexServiceDisabledImpl: TitleIndexService {
+ override fun indexAllTitles() {}
+ override fun addTitle(title: Title) {}
+ override fun searchTitle(query: String) = emptyList()
+}
+
+enum class IndexStatus {
+ UNINITIALIZED,
+ INDEXING,
+ READY
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index a3f7bb19..af1175e2 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -43,3 +43,10 @@ alma:
http-proxy:
host: ""
port: 0
+
+search-index:
+ enabled: true
+ path: index-data
+ initial-delay: 1000
+ rebuild-index-delay: 1800000
+ searcher-refresh-delay: 600000
diff --git a/src/test/kotlin/no/nb/bikube/core/controller/CoreControllerIntegrationTest.kt b/src/test/kotlin/no/nb/bikube/core/controller/CoreControllerIntegrationTest.kt
index 46798a83..2785d371 100644
--- a/src/test/kotlin/no/nb/bikube/core/controller/CoreControllerIntegrationTest.kt
+++ b/src/test/kotlin/no/nb/bikube/core/controller/CoreControllerIntegrationTest.kt
@@ -18,6 +18,7 @@ import no.nb.bikube.core.enum.MaterialType
import no.nb.bikube.core.model.Item
import no.nb.bikube.core.model.Title
import no.nb.bikube.core.util.DateUtils
+import no.nb.bikube.newspaper.service.TitleIndexService
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -41,6 +42,9 @@ class CoreControllerIntegrationTest (
@MockkBean
private lateinit var collectionsRepository: CollectionsRepository
+ @MockkBean
+ private lateinit var titleIndexService: TitleIndexService
+
private val titleId = "1"
private val yearWorkId = "2"
private val manifestationId = "3"
@@ -56,8 +60,9 @@ class CoreControllerIntegrationTest (
every { collectionsRepository.getSingleCollectionsModel(yearWorkId) } returns Mono.just(collectionsModelMockYearWorkA.copy())
every { collectionsRepository.getSingleCollectionsModel(manifestationId) } returns Mono.just(collectionsModelMockManifestationA.copy())
every { collectionsRepository.getSingleCollectionsModel(itemId) } returns Mono.just(collectionsModelMockItemA.copy())
- every { collectionsRepository.getTitleByName(any()) } returns Mono.just(collectionsModelMockAllTitles.copy())
every { collectionsRepository.getWorkYearForTitle(any(), any()) } returns Mono.just(collectionsModelMockYearWorkA.copy())
+ every { titleIndexService.searchTitle(any()) } returns
+ collectionsModelMockAllTitles.getObjects()!!.map { mapCollectionsObjectToGenericTitle(it) }
}
private fun getItem(itemId: String, materialType: MaterialType): ResponseSpec {
@@ -331,7 +336,7 @@ class CoreControllerIntegrationTest (
@Test
fun `get-title-search endpoint should return empty flux when no items match search term`() {
- every { collectionsRepository.getTitleByName("no match") } returns Mono.just(collectionsModelEmptyRecordListMock.copy())
+ every { titleIndexService.searchTitle("no match") } returns emptyList()
searchTitle("no match", MaterialType.NEWSPAPER)
.returnResult()
diff --git a/src/test/kotlin/no/nb/bikube/core/controller/CoreControllerTest.kt b/src/test/kotlin/no/nb/bikube/core/controller/CoreControllerTest.kt
index 68c7f0a9..b239395f 100644
--- a/src/test/kotlin/no/nb/bikube/core/controller/CoreControllerTest.kt
+++ b/src/test/kotlin/no/nb/bikube/core/controller/CoreControllerTest.kt
@@ -7,10 +7,12 @@ import io.mockk.verify
import no.nb.bikube.core.enum.MaterialType
import no.nb.bikube.core.exception.BadRequestBodyException
import no.nb.bikube.core.exception.NotSupportedException
+import no.nb.bikube.core.model.Title
import no.nb.bikube.newspaper.NewspaperMockData.Companion.newspaperItemMockA
import no.nb.bikube.newspaper.NewspaperMockData.Companion.newspaperTitleMockA
import no.nb.bikube.newspaper.NewspaperMockData.Companion.newspaperTitleMockB
import no.nb.bikube.newspaper.service.NewspaperService
+import no.nb.bikube.newspaper.service.TitleIndexService
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
@@ -30,6 +32,9 @@ class CoreControllerTest {
@MockkBean
private lateinit var newspaperService: NewspaperService
+ @MockkBean
+ private lateinit var titleIndexService: TitleIndexService
+
@Test
fun `get single item for newspaper should return item in body`() {
every { newspaperService.getSingleItem(any()) } returns Mono.just(newspaperItemMockA.copy())
@@ -88,20 +93,16 @@ class CoreControllerTest {
@Test
fun `search title should return a list of titles matching name`() {
- every { newspaperService.searchTitleByName(any()) } returns Flux.just(
+ every { titleIndexService.searchTitle(any()) } returns listOf(
newspaperTitleMockA.copy(), newspaperTitleMockB.copy()
)
- coreController.searchTitle("Avis", MaterialType.NEWSPAPER).body!!
- .test()
- .expectSubscription()
- .assertNext {
- Assertions.assertEquals(newspaperTitleMockA, it)
- }
- .assertNext {
- Assertions.assertEquals(newspaperTitleMockB, it)
- }
- .verifyComplete()
+ Assertions.assertEquals(
+ coreController.searchTitle("Avis", MaterialType.NEWSPAPER).body!!,
+ listOf(
+ newspaperTitleMockA.copy(), newspaperTitleMockB.copy()
+ )
+ )
}
@Test
@@ -110,18 +111,19 @@ class CoreControllerTest {
assertThrows { coreController.searchTitle("Avis", MaterialType.PERIODICAL) }
assertThrows { coreController.searchTitle("Avis", MaterialType.MONOGRAPH) }
- verify { newspaperService.searchTitleByName(any()) wasNot Called }
+ verify(exactly = 0) { titleIndexService.searchTitle(any()) }
}
@Test
- fun `search title should call on newspaperService function when materialType is NEWSPAPER`() {
- every { newspaperService.searchTitleByName(any()) } returns Flux.empty()
+ fun `search title should call on titleIndexService function when materialType is NEWSPAPER`() {
+ every { titleIndexService.searchTitle(any()) } returns emptyList()
- coreController.searchTitle("Avis", MaterialType.NEWSPAPER).body!!
- .test()
- .verifyComplete()
+ Assertions.assertEquals(
+ coreController.searchTitle("Avis", MaterialType.NEWSPAPER).body!!,
+ emptyList()
+ )
- verify { newspaperService.searchTitleByName(any()) }
+ verify(exactly = 1) { titleIndexService.searchTitle(any()) }
}
@Test
diff --git a/src/test/kotlin/no/nb/bikube/newspaper/service/NewspaperServiceTest.kt b/src/test/kotlin/no/nb/bikube/newspaper/service/NewspaperServiceTest.kt
index dcf3f914..8ff502b2 100644
--- a/src/test/kotlin/no/nb/bikube/newspaper/service/NewspaperServiceTest.kt
+++ b/src/test/kotlin/no/nb/bikube/newspaper/service/NewspaperServiceTest.kt
@@ -441,42 +441,6 @@ class NewspaperServiceTest(
.verify()
}
- @Test
- fun `getTitleByName should return correctly mapped title`() {
- every { collectionsRepository.getTitleByName(any()) } returns Mono.just(collectionsModelMockTitleA)
-
- newspaperService.searchTitleByName("1")
- .test()
- .expectSubscription()
- .assertNext {
- Assertions.assertEquals(
- Title(
- name = collectionsModelMockTitleA.getFirstObject()!!.getName(),
- startDate = collectionsModelMockTitleA.getFirstObject()!!.getStartDate(),
- endDate = null,
- publisher = collectionsModelMockTitleA.getFirstObject()!!.getPublisher(),
- publisherPlace = collectionsModelMockTitleA.getFirstObject()!!.getPublisherPlace(),
- language = collectionsModelMockTitleA.getFirstObject()!!.getLanguage(),
- materialType = collectionsModelMockTitleA.getFirstObject()!!.getMaterialType()!!.norwegian,
- catalogueId = collectionsModelMockTitleA.getFirstObject()!!.priRef
- ),
- it
- )
- }
- .verifyComplete()
- }
-
- @Test
- fun `getTitleByName should return empty Mono if no titles are found`() {
- every { collectionsRepository.getTitleByName(any()) } returns Mono.just(collectionsModelEmptyRecordListMock)
-
- newspaperService.searchTitleByName("1")
- .test()
- .expectSubscription()
- .expectNextCount(0)
- .verifyComplete()
- }
-
@Test
fun `createTitle should return correctly mapped record`() {
every { collectionsRepository.createTextsRecord(any()) } returns Mono.just(collectionsModelMockTitleE)
@@ -503,26 +467,6 @@ class NewspaperServiceTest(
verify { collectionsRepository.createTextsRecord(titleEncodedDto) }
}
- @Test
- fun `searchTitleByName should return a correctly mapped catalogue record`() {
- every { collectionsRepository.getTitleByName(any()) } returns Mono.just(collectionsModelMockTitleE)
- newspaperService.searchTitleByName("1")
- .test()
- .expectSubscription()
- .assertNext { Assertions.assertEquals(newspaperTitleMockB, it) }
- .verifyComplete()
- }
-
- @Test
- fun `searchTitleByName should return a flux of an empty list if no titles are found`() {
- every { collectionsRepository.getTitleByName(any()) } returns Mono.empty()
- newspaperService.searchTitleByName("1")
- .test()
- .expectSubscription()
- .expectNextCount(0)
- .verifyComplete()
- }
-
@Test
fun `createPublisher should return RecordAlreadyExistsException if searchPublisher returns non-empty list`() {
every { collectionsRepository.searchPublisher(any()) } returns Mono.just(collectionsNameModelMockA)
diff --git a/src/test/kotlin/no/nb/bikube/newspaper/service/TitleIndexServiceTest.kt b/src/test/kotlin/no/nb/bikube/newspaper/service/TitleIndexServiceTest.kt
new file mode 100644
index 00000000..f325d35f
--- /dev/null
+++ b/src/test/kotlin/no/nb/bikube/newspaper/service/TitleIndexServiceTest.kt
@@ -0,0 +1,76 @@
+package no.nb.bikube.newspaper.service
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.every
+import no.nb.bikube.catalogue.collections.CollectionsModelMockData.Companion.collectionsModelMockAllTitles
+import no.nb.bikube.catalogue.collections.mapper.mapCollectionsObjectToGenericTitle
+import no.nb.bikube.catalogue.collections.model.getObjects
+import no.nb.bikube.newspaper.NewspaperMockData.Companion.newspaperTitleInputDtoMockA
+import no.nb.bikube.newspaper.NewspaperMockData.Companion.newspaperTitleMockA
+import no.nb.bikube.newspaper.controller.TitleController
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ActiveProfiles
+import org.springframework.test.context.TestPropertySource
+import reactor.core.publisher.Mono
+
+@SpringBootTest
+@ActiveProfiles("test")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@TestPropertySource(
+ properties = [
+ "search-index.enabled=true",
+ "search-index.path=index-data-test",
+ ]
+)
+class TitleIndexServiceTest(
+ @Autowired private val titleIndexService: TitleIndexService,
+ @Autowired private val titleController: TitleController
+) {
+ @MockkBean
+ private lateinit var newspaperService: NewspaperService
+
+ @BeforeAll
+ fun mockTitleList() {
+ every { newspaperService.getAllTitles() } returns
+ Mono.just(
+ collectionsModelMockAllTitles
+ .getObjects()!!
+ .map { mapCollectionsObjectToGenericTitle(it) }
+ )
+ titleIndexService.indexAllTitles()
+ }
+
+ @Test
+ fun `All titles should be indexed and searchable`() {
+ Assertions.assertEquals(
+ titleIndexService.searchTitle("avis").size,
+ 5
+ )
+ }
+
+ @Test
+ fun `A newly created title should be searchable immediately`() {
+ Assertions.assertEquals(
+ titleIndexService.searchTitle("Unique title").size,
+ 0
+ )
+ every { newspaperService.createPublisher(any(), any()) } returns Mono.empty()
+ every { newspaperService.createPublisherPlace(any(), any()) } returns Mono.empty()
+ every { newspaperService.createLanguage(any(), any()) } returns Mono.empty()
+ every { newspaperService.createNewspaperTitle(any()) } returns
+ Mono.just(newspaperTitleMockA.copy(name = "Unique title"))
+
+ titleController.createTitle(newspaperTitleInputDtoMockA)
+ .subscribe()
+
+ Assertions.assertEquals(
+ titleIndexService.searchTitle("Unique title").size,
+ 1
+ )
+ }
+}
diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml
index eb490f24..5364f1e8 100644
--- a/src/test/resources/application-test.yml
+++ b/src/test/resources/application-test.yml
@@ -1,2 +1,7 @@
alma:
api-key: SECRETKEY
+
+search-index:
+ enabled: false
+
+app.scheduling.enabled: false