diff --git a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringQueryOptionDsl.kt b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringQueryOptionDsl.kt
new file mode 100644
index 00000000..8a9ef583
--- /dev/null
+++ b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringQueryOptionDsl.kt
@@ -0,0 +1,147 @@
+package com.github.inflab.spring.data.mongodb.core.aggregation.search
+
+import com.github.inflab.spring.data.mongodb.core.annotation.AggregationMarker
+
+/**
+ * A Kotlin DSL to configure queryString query option operator using idiomatic Kotlin code.
+ *
+ * @author minwoo
+ * @since 1.0
+ * @see queryString options
+ */
+@AggregationMarker
+class QueryStringQueryOptionDsl {
+ var query: Query? = null
+
+ class Query(val value: String, val field: String?) {
+ override fun toString(): String {
+ return when (field) {
+ null -> value
+ else -> "$field:$value"
+ }
+ }
+ }
+
+ /**
+ * Indicates 0 or more characters to match.
+ */
+ val WILDCARD = "*"
+
+ /**
+ * Indicates any single character to match.
+ */
+ val QUESTION = "?"
+
+ /**
+ * Creates a text query.
+ *
+ * @param value The value to search
+ * @param field Indexed field search
+ */
+ fun text(value: String, field: String? = null): Query {
+ val escaped = value.replace("*", "\\*").replace("?", "\\?")
+
+ return Query("\"$escaped\"", field)
+ }
+
+ /**
+ * Creates a wildcard query.
+ *
+ * @param value The value to search
+ * @param field Indexed field to search
+ */
+ fun wildcard(value: String, field: String? = null): Query {
+ return Query(value, field)
+ }
+
+ /**
+ * Creates a regex query.
+ *
+ * @param pattern The pattern to search
+ * @param field Indexed field to search
+ */
+ fun regex(pattern: String, field: String? = null): Query {
+ return Query("/$pattern/", field)
+ }
+
+ /**
+ * Creates a range query.
+ *
+ * @param left The left value to search
+ * @param right The right value to search
+ * @param leftInclusion The left value is included in the range
+ * @param rightInclusion The right value is included in the range
+ * @param field Indexed field to search
+ */
+ fun range(left: String, right: String, leftInclusion: Boolean = true, rightInclusion: Boolean = true, field: String? = null): Query {
+ val leftBracket = if (leftInclusion) "[" else "{"
+ val rightBracket = if (rightInclusion) "]" else "}"
+
+ val leftExp = when (left) {
+ WILDCARD -> WILDCARD
+ QUESTION -> QUESTION
+ else -> "\"$left\""
+ }
+ val rightExp = when (right) {
+ WILDCARD -> WILDCARD
+ QUESTION -> QUESTION
+ else -> "\"$right\""
+ }
+
+ return Query("$leftBracket$leftExp TO $rightExp$rightBracket", field)
+ }
+
+ /**
+ * Creates a fuzzy query.
+ *
+ * @param value The value to search
+ * @param maxEdits Maximum number of single-character edits required to match the specified search term.
+ * @param field indexed field to search
+ */
+ fun fuzzy(value: String, maxEdits: Int, field: String? = null): Query {
+ return Query("$value~$maxEdits", field)
+ }
+
+ /**
+ * Creates a delimiters for subqueries.
+ *
+ * @param query The Query for subqueries.
+ */
+ fun sub(query: Query, field: String? = null): Query {
+ return Query("${field?.let { "$it:" }.orEmpty()}(${query.value})", query.field)
+ }
+
+ /**
+ * Creates an operator that indicates `NOT` boolean operator.
+ * Specified search value must be absent for a document to be included in the results.
+ *
+ * @param query The Query to apply.
+ */
+ fun not(query: Query): Query {
+ return Query("NOT (${query.value})", query.field)
+ }
+
+ /**
+ * Creates an operator that indicates `AND` boolean operator.
+ * All search values must be present for a document to be included in the results.
+ *
+ * @param query The Query to apply.
+ */
+ infix fun Query.and(query: Query): Query {
+ return Query("$this AND $query", null)
+ }
+
+ /**
+ * Creates an operator that indicates OR boolean operator.
+ * At least one of the search value must be present for a document to be included in the results.
+ *
+ * @param query The Query to apply.
+ */
+ infix fun Query.or(query: Query): Query {
+ return Query("$this OR $query", null)
+ }
+
+ internal fun build(): String {
+ return checkNotNull(query) { "query must not be null" }.toString()
+ }
+}
diff --git a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringSearchOperatorDsl.kt b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringSearchOperatorDsl.kt
new file mode 100644
index 00000000..88b59cef
--- /dev/null
+++ b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringSearchOperatorDsl.kt
@@ -0,0 +1,76 @@
+package com.github.inflab.spring.data.mongodb.core.aggregation.search
+
+import com.github.inflab.spring.data.mongodb.core.annotation.AggregationMarker
+import com.github.inflab.spring.data.mongodb.core.extension.toDotPath
+import org.bson.Document
+import kotlin.reflect.KProperty
+
+/**
+ * A Kotlin DSL to configure queryString search operator using idiomatic Kotlin code.
+ *
+ * @author minwoo
+ * @since 1.0
+ * @see queryString
+ */
+@AggregationMarker
+class QueryStringSearchOperatorDsl {
+ private val document = Document()
+
+ /**
+ * The indexed field to search by default.
+ * Atlas Search only searches the field in `defaultPath` if you omit the field to search in the query.
+ *
+ * @param path The indexed field to search by default.
+ */
+ fun defaultPath(path: String) {
+ document["defaultPath"] = path
+ }
+
+ /**
+ * The indexed field to search by default.
+ * Atlas Search only searches the field in `defaultPath` if you omit the field to search in the query.
+ *
+ * @param path The indexed field to search by default.
+ */
+ fun defaultPath(path: KProperty) {
+ document["defaultPath"] = path.toDotPath()
+ }
+
+ /**
+ * The indexed field to search by default.
+ * Atlas Search only searches the field in `defaultPath` if you omit the field to search in the query.
+ *
+ * @param path The indexed field to search by default.
+ */
+ @JvmName("defaultPathIterable")
+ fun defaultPath(path: KProperty?>) {
+ document["defaultPath"] = path.toDotPath()
+ }
+
+ /**
+ * Specifies one or more indexed fields and values to search.
+ * Fields and values are colon-delimited.
+ * For example, to search the plot field for the string baseball, use `plot:baseball`.
+ *
+ * @param configuration The configuration block for the [QueryStringQueryOptionDsl].
+ */
+ fun query(configuration: QueryStringQueryOptionDsl.() -> Unit) {
+ document["query"] = QueryStringQueryOptionDsl().apply(configuration).build()
+ }
+
+ /**
+ * The score assigned to matching search results.
+ * You can modify the default score using the following options:
+ *
+ * - boost: multiply the result score by the given number.
+ * - constant: replace the result score with the given number.
+ * - function: replace the result score using the given expression.
+ *
+ * @see Score the Documents in the Results
+ */
+ fun score(scoreConfiguration: ScoreSearchOptionDsl.() -> Unit) {
+ document["score"] = ScoreSearchOptionDsl().apply(scoreConfiguration).get()
+ }
+
+ internal fun build() = Document("queryString", document)
+}
diff --git a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/SearchOperator.kt b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/SearchOperator.kt
index e1f014a3..20f3ec8b 100644
--- a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/SearchOperator.kt
+++ b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/SearchOperator.kt
@@ -116,4 +116,13 @@ interface SearchOperator {
* @see wildcard
*/
fun wildcard(configuration: WildcardSearchOperatorDsl.() -> Unit)
+
+ /**
+ * Supports querying a combination of indexed fields and values.
+ * You can perform text, wildcard, regular expression, fuzzy, and range searches on string fields using the queryString operator.
+ *
+ * @param configuration The configuration block for the [QueryStringSearchOperatorDsl].
+ * @see queryString
+ */
+ fun queryString(configuration: QueryStringSearchOperatorDsl.() -> Unit)
}
diff --git a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/SearchOperatorDsl.kt b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/SearchOperatorDsl.kt
index 5bf6a2e2..d022600d 100644
--- a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/SearchOperatorDsl.kt
+++ b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/SearchOperatorDsl.kt
@@ -57,4 +57,8 @@ class SearchOperatorDsl : SearchOperator {
override fun wildcard(configuration: WildcardSearchOperatorDsl.() -> Unit) {
operators.add(WildcardSearchOperatorDsl().apply(configuration).build())
}
+
+ override fun queryString(configuration: QueryStringSearchOperatorDsl.() -> Unit) {
+ operators.add(QueryStringSearchOperatorDsl().apply(configuration).build())
+ }
}
diff --git a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringQueryOptionDslTest.kt b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringQueryOptionDslTest.kt
new file mode 100644
index 00000000..0dc11ac9
--- /dev/null
+++ b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringQueryOptionDslTest.kt
@@ -0,0 +1,261 @@
+package com.github.inflab.spring.data.mongodb.core.aggregation.search
+
+import io.kotest.core.spec.style.FreeSpec
+import io.kotest.matchers.shouldBe
+
+internal class QueryStringQueryOptionDslTest : FreeSpec({
+ fun query(block: QueryStringQueryOptionDsl.() -> Unit) =
+ QueryStringQueryOptionDsl().apply(block)
+
+ "text" - {
+ "should add a text" {
+ // given
+ val option = query {
+ query = text("search")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "\"search\""
+ }
+
+ "should add a text with field" {
+ // given
+ val option = query {
+ query = text("search", "field")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "field:\"search\""
+ }
+
+ "should escape special characters" {
+ // given
+ val option = query {
+ query = text("c?u*t")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "\"c\\?u\\*t\""
+ }
+ }
+
+ "wildcard" - {
+ "should add a wildcard" {
+ // given
+ val option = query {
+ query = wildcard("search$WILDCARD$QUESTION")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "search*?"
+ }
+
+ "should add a wildcard with field" {
+ // given
+ val option = query {
+ query = wildcard("search", "field")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "field:search"
+ }
+ }
+
+ "regex" - {
+ "should add a regex" {
+ // given
+ val option = query {
+ query = regex("search")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "/search/"
+ }
+
+ "should add a regex with field" {
+ // given
+ val option = query {
+ query = regex("search", "field")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "field:/search/"
+ }
+ }
+
+ "range" - {
+ "should add a inclusive range" {
+ // given
+ val option = query {
+ query = range("left", "right", leftInclusion = true, rightInclusion = true)
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "[\"left\" TO \"right\"]"
+ }
+
+ "should add a exclusive range" {
+ // given
+ val option = query {
+ query = range("left", "right", leftInclusion = false, rightInclusion = false)
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "{\"left\" TO \"right\"}"
+ }
+
+ "should add a half-open range" {
+ // given
+ val option = query {
+ query = range("left", "right", leftInclusion = true, rightInclusion = false)
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "[\"left\" TO \"right\"}"
+ }
+
+ "should not add double quote if wildcard is given" {
+ // given
+ val option = query {
+ query = range(WILDCARD, WILDCARD)
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "[* TO *]"
+ }
+ }
+
+ "fuzzy" - {
+ "should add a fuzzy" {
+ // given
+ val option = query {
+ query = fuzzy("search", 2)
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "search~2"
+ }
+
+ "should add a fuzzy with field" {
+ // given
+ val option = query {
+ query = fuzzy("search", 3, "field")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "field:search~3"
+ }
+ }
+
+ "not" - {
+ "should add NOT block" {
+ // given
+ val option = query {
+ query = not(text("search"))
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "NOT (\"search\")"
+ }
+ }
+
+ "and" - {
+ "should add AND block" {
+ // given
+ val option = query {
+ query = text("search1") and text("search2")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "\"search1\" AND \"search2\""
+ }
+ }
+
+ "or" - {
+ "should add OR block" {
+ // given
+ val option = query {
+ query = text("search1") or text("search2")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "\"search1\" OR \"search2\""
+ }
+ }
+
+ "sub" - {
+ "should add subquery block" {
+ // given
+ val option = query {
+ query = sub(text("a") or text("b")) and text("c")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "(\"a\" OR \"b\") AND \"c\""
+ }
+
+ "should add subquery block with field" {
+ // given
+ val option = query {
+ query = sub(text("a") or text("b"), "c")
+ }
+
+ // when
+ val result = option.build()
+
+ // then
+ result shouldBe "c:(\"a\" OR \"b\")"
+ }
+ }
+})
diff --git a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringSearchOperatorDslTest.kt b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringSearchOperatorDslTest.kt
new file mode 100644
index 00000000..5bc2b48e
--- /dev/null
+++ b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/QueryStringSearchOperatorDslTest.kt
@@ -0,0 +1,108 @@
+package com.github.inflab.spring.data.mongodb.core.aggregation.search
+
+import com.github.inflab.spring.data.mongodb.core.util.shouldBeJson
+import io.kotest.core.spec.style.FreeSpec
+
+internal class QueryStringSearchOperatorDslTest : FreeSpec({
+ fun queryString(block: QueryStringSearchOperatorDsl.() -> Unit) =
+ QueryStringSearchOperatorDsl().apply(block)
+
+ "defaultPath" - {
+ "should build a default path by string" {
+ // given
+ val operator = queryString {
+ defaultPath("path")
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "queryString": {
+ "defaultPath": "path"
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ "should build a default path by property" {
+ // given
+ data class Collection(val field: List?)
+ val operator = queryString {
+ defaultPath(Collection::field)
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "queryString": {
+ "defaultPath": "field"
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+ }
+
+ "query" - {
+ "should build a query" {
+ // given
+ val operator = queryString {
+ query {
+ query = text("search")
+ }
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "queryString": {
+ "query": "\"search\""
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+ }
+
+ "score" - {
+ "should build a score" {
+ // given
+ val operator = queryString {
+ score {
+ constant(1.0)
+ }
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "queryString": {
+ "score": {
+ "constant": {
+ "value": 1.0
+ }
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+ }
+})
diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/QueryStringSearchRepository.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/QueryStringSearchRepository.kt
new file mode 100644
index 00000000..814c1109
--- /dev/null
+++ b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/QueryStringSearchRepository.kt
@@ -0,0 +1,152 @@
+package com.github.inflab.example.spring.data.mongodb.repository.atlas
+
+import com.github.inflab.example.spring.data.mongodb.entity.mflix.Movies
+import com.github.inflab.spring.data.mongodb.core.aggregation.aggregation
+import com.mongodb.client.model.Projections.excludeId
+import org.springframework.data.mongodb.core.MongoTemplate
+import org.springframework.data.mongodb.core.aggregate
+import org.springframework.data.mongodb.core.aggregation.AggregationResults
+import org.springframework.stereotype.Repository
+
+@Repository
+class QueryStringSearchRepository(
+ private val mongoTemplate: MongoTemplate,
+) {
+
+ data class PlotAndFullplotDto(
+ val title: String,
+ val plot: String,
+ val fullplot: String,
+ val score: Double,
+ )
+
+ /**
+ * @see Boolean Operator Queries (OR AND)
+ */
+ fun findFullplotWithPlot(): AggregationResults {
+ val aggregation = aggregation {
+ search {
+ queryString {
+ defaultPath(Movies::fullplot)
+ query {
+ query = sub(text("captain") or text("kirk"), "plot") and text("enterprise")
+ }
+ }
+ }
+
+ // TODO: add $limit stage
+
+ project {
+ excludeId()
+ +Movies::title
+ +Movies::plot
+ +Movies::fullplot
+ searchScore()
+ }
+ }
+
+ return mongoTemplate.aggregate(aggregation)
+ }
+
+ data class TitleDto(val title: String)
+
+ /**
+ * @see Range Queries (TO)
+ */
+ fun findPoltWithTitleRange(): AggregationResults {
+ val aggregation = aggregation {
+ search {
+ queryString {
+ defaultPath(Movies::plot)
+ query {
+ query = range(left = "count", right = WILDCARD, leftInclusion = true, rightInclusion = true, field = "title")
+ }
+ }
+ }
+
+ // TODO: add $limit stage
+
+ project {
+ excludeId()
+ +Movies::title
+ }
+ }
+
+ return mongoTemplate.aggregate(aggregation)
+ }
+
+ /**
+ * @see Wildcard Queries (Fuzzy)
+ */
+ fun findTitleByFuzzy(): AggregationResults {
+ val aggregation = aggregation {
+ search {
+ queryString {
+ defaultPath(Movies::title)
+ query {
+ query = fuzzy("catch", 2)
+ }
+ }
+ }
+
+ // TODO: add $limit stage
+
+ project {
+ excludeId()
+ +Movies::title
+ }
+ }
+
+ return mongoTemplate.aggregate(aggregation)
+ }
+
+ /**
+ * @see Wildcard Queries (Wildcard)
+ */
+ fun findTitleByWildcard(): AggregationResults {
+ val aggregation = aggregation {
+ search {
+ queryString {
+ defaultPath(Movies::title)
+ query {
+ query = wildcard("cou*t?*")
+ }
+ }
+ }
+
+ // TODO: add $limit stage
+
+ project {
+ excludeId()
+ +Movies::title
+ }
+ }
+
+ return mongoTemplate.aggregate(aggregation)
+ }
+
+ /**
+ * @see Wildcard Queries (Regex)
+ */
+ fun findTitleByRegex(): AggregationResults {
+ val aggregation = aggregation {
+ search {
+ queryString {
+ defaultPath(Movies::title)
+ query {
+ query = regex(".tal(y|ian)")
+ }
+ }
+ }
+
+ // TODO: add $limit stage
+
+ project {
+ excludeId()
+ +Movies::title
+ }
+ }
+
+ return mongoTemplate.aggregate(aggregation)
+ }
+}
diff --git a/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/QueryStringSearchRepositoryTest.kt b/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/QueryStringSearchRepositoryTest.kt
new file mode 100644
index 00000000..8d66f1d1
--- /dev/null
+++ b/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/QueryStringSearchRepositoryTest.kt
@@ -0,0 +1,91 @@
+package com.github.inflab.example.spring.data.mongodb.repository.atlas
+
+import com.github.inflab.example.spring.data.mongodb.extension.AtlasTest
+import io.kotest.core.spec.style.FreeSpec
+import io.kotest.matchers.collections.shouldBeMonotonicallyDecreasing
+import io.kotest.matchers.shouldBe
+
+@AtlasTest(database = "sample_mflix")
+internal class QueryStringSearchRepositoryTest(
+ private val queryStringSearchRepository: QueryStringSearchRepository,
+) : FreeSpec({
+
+ "findFullplotWithPlot" {
+ // when
+ val result = queryStringSearchRepository.findFullplotWithPlot()
+
+ // then
+ result.mappedResults.take(3).map { it.title } shouldBe listOf(
+ "Star Trek: Generations",
+ "Star Trek V: The Final Frontier",
+ "Star Trek: The Motion Picture",
+ )
+ result.mappedResults.take(3).map { it.score }.shouldBeMonotonicallyDecreasing()
+ }
+
+ "findPoltWithTitleRange" {
+ // when
+ val result = queryStringSearchRepository.findPoltWithTitleRange()
+
+ // then
+ result.mappedResults.take(10).map { it.title } shouldBe listOf(
+ "The Great Train Robbery",
+ "A Corner in Wheat",
+ "Winsor McCay, the Famous Cartoonist of the N.Y. Herald and His Moving Comics",
+ "Traffic in Souls",
+ "Gertie the Dinosaur",
+ "In the Land of the Head Hunters",
+ "The Perils of Pauline",
+ "The Italian",
+ "Regeneration",
+ "Where Are My Children?",
+ )
+ }
+
+ "findTitleByFuzzy" {
+ // when
+ val result = queryStringSearchRepository.findTitleByFuzzy()
+
+ // then
+ result.mappedResults.take(10).map { it.title } shouldBe listOf(
+ "Catch-22",
+ "Catch That Girl",
+ "Catch That Kid",
+ "Catch a Fire",
+ "Catch Me Daddy",
+ "Death Watch",
+ "Patch Adams",
+ "Batch '81",
+ "Briar Patch",
+ "Night Watch",
+ )
+ }
+
+ "findTitleByWildcard" {
+ // when
+ val result = queryStringSearchRepository.findTitleByWildcard()
+
+ // then
+ result.mappedResults.take(5).map { it.title } shouldBe listOf(
+ "Diary of a Country Priest",
+ "Cry, the Beloved Country",
+ "Raintree County",
+ "Ride the High Country",
+ "The Courtship of Eddie's Father",
+ )
+ }
+
+ "findTitleByRegex" {
+ // when
+ val result = queryStringSearchRepository.findTitleByRegex()
+
+ // then
+ result.mappedResults.take(5).map { it.title } shouldBe listOf(
+ "The Italian",
+ "Marriage Italian Style",
+ "Jealousy, Italian Style",
+ "My Voyage to Italy",
+ "Italian for Beginners",
+ )
+ }
+})