diff --git a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/NearSearchOperatorDsl.kt b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/NearSearchOperatorDsl.kt
new file mode 100644
index 00000000..d9d99f66
--- /dev/null
+++ b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/NearSearchOperatorDsl.kt
@@ -0,0 +1,141 @@
+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.firstOrAll
+import com.github.inflab.spring.data.mongodb.core.extension.toDotPath
+import org.bson.Document
+import org.springframework.data.mongodb.core.geo.GeoJsonPoint
+import java.time.temporal.Temporal
+import kotlin.reflect.KProperty
+
+/**
+ * A Kotlin DSL to configure near search operator using idiomatic Kotlin code.
+ *
+ * @author username1103
+ * @since 1.0
+ * @see near
+ */
+@AggregationMarker
+class NearSearchOperatorDsl {
+ private val document = Document()
+
+ /**
+ * Indexed field or fields to search.
+ * See Path Construction.
+ *
+ * @param path The indexed field or fields to search.
+ * @see Path Construction
+ */
+ fun path(vararg path: String) {
+ document["path"] = path.toList().firstOrAll()
+ }
+
+ /**
+ * Indexed number type field or fields to search.
+ * See Path Construction.
+ *
+ * @param path The indexed field or fields to search.
+ * @see Path Construction
+ */
+ @JvmName("pathNumber")
+ fun path(vararg path: KProperty) {
+ document["path"] = path.map { it.toDotPath() }.firstOrAll()
+ }
+
+ /**
+ * Indexed date type field or fields to search.
+ * See Path Construction.
+ *
+ * @param path The indexed field or fields to search.
+ * @see Path Construction
+ */
+ @JvmName("pathDate")
+ fun path(vararg path: KProperty) {
+ document["path"] = path.map { it.toDotPath() }.firstOrAll()
+ }
+
+ /**
+ * Indexed geo type field or fields to search.
+ * See Path Construction for more information.
+ *
+ * @param path The indexed field or fields to search.
+ * @see Path Construction
+ */
+ @JvmName("pathPoint")
+ fun path(vararg path: KProperty) {
+ document["path"] = path.map { it.toDotPath() }.firstOrAll()
+ }
+
+ /**
+ * Origin to query for Number field.
+ * This is the origin from which the proximity of the results is measured.
+ *
+ * For number fields, the value must be of BSON int32, int64, or double data types.
+ */
+ fun origin(origin: Number) {
+ document["origin"] = origin
+ }
+
+ /**
+ * Origin to query for Date field.
+ * This is the origin from which the proximity of the results is measured.
+ *
+ * For date fields, the value must be an ISODate formatted date.
+ * @see ISODate
+ */
+ fun origin(origin: Temporal) {
+ document["origin"] = origin
+ }
+
+ /**
+ * Origin to query for Geo field.
+ * This is the origin from which the proximity of the results is measured.
+ *
+ * For geo fields. the value must be a GeoJSON point.
+ * @see GeoJson Point
+ */
+ fun origin(origin: GeoJsonPoint) {
+ document["origin"] = Document("type", "Point").append("coordinates", origin.coordinates)
+ }
+
+ /**
+ * Value to use to calculate scores of Atlas Search result documents. Score is calculated using the following formula:
+ *
+ * ```
+ * pivot
+ * score = ------------------
+ * pivot + distance
+ * ```
+ *
+ * where distance is the difference between origin and the indexed field value.
+ *
+ * Results have a score equal to 1/2 (or 0.5) when their indexed field value is pivot units away from origin.
+ * The value of pivot must be greater than (i.e. >) 0.
+ *
+ * If origin is a:
+ * - Number, pivot can be specified as an integer or floating point number.
+ * - Date, pivot must be specified in milliseconds and can be specified as a 32 or 64 bit integer.
+ * - GeoJSON point, pivot is measured in meters and must be specified as an integer or floating point number.
+ *
+ * @param pivot The value to use to calculate scores of Atlas Search result documents.
+ */
+ fun pivot(pivot: Number) {
+ document["pivot"] = pivot
+ }
+
+ /**
+ * The score assigned to matching search term results. Use one of the following options to modify the score:
+ *
+ * - 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.
+ *
+ * @param scoreConfiguration The configuration block for [ScoreSearchOptionDsl]
+ * @see Modify the Score
+ */
+ fun score(scoreConfiguration: ScoreSearchOptionDsl.() -> Unit) {
+ document["score"] = ScoreSearchOptionDsl().apply(scoreConfiguration).get()
+ }
+
+ internal fun build() = Document("near", 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 20f3ec8b..7232be08 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
@@ -125,4 +125,14 @@ interface SearchOperator {
* @see queryString
*/
fun queryString(configuration: QueryStringSearchOperatorDsl.() -> Unit)
+
+ /**
+ * Supports querying and scoring numeric, date, and GeoJSON point values.
+ * You can use the near operator to find results that are near a number or a date.
+ * The near operator scores the Atlas Search results by proximity to the number or date.
+ *
+ * @param configuration The Configuration block for the [NearSearchOperatorDsl].
+ * @see near
+ */
+ fun near(configuration: NearSearchOperatorDsl.() -> 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 d022600d..99d7ec69 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
@@ -61,4 +61,8 @@ class SearchOperatorDsl : SearchOperator {
override fun queryString(configuration: QueryStringSearchOperatorDsl.() -> Unit) {
operators.add(QueryStringSearchOperatorDsl().apply(configuration).build())
}
+
+ override fun near(configuration: NearSearchOperatorDsl.() -> Unit) {
+ operators.add(NearSearchOperatorDsl().apply(configuration).build())
+ }
}
diff --git a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/NearSearchOperatorDslTest.kt b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/NearSearchOperatorDslTest.kt
new file mode 100644
index 00000000..b1195db6
--- /dev/null
+++ b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/search/NearSearchOperatorDslTest.kt
@@ -0,0 +1,237 @@
+package com.github.inflab.spring.data.mongodb.core.aggregation.search
+
+import com.github.inflab.spring.data.mongodb.core.mapping.rangeTo
+import com.github.inflab.spring.data.mongodb.core.util.shouldBeJson
+import io.kotest.core.spec.style.FreeSpec
+import org.springframework.data.mongodb.core.geo.GeoJsonPoint
+import java.time.LocalDate
+import java.time.LocalDateTime
+
+internal class NearSearchOperatorDslTest : FreeSpec({
+ fun near(block: NearSearchOperatorDsl.() -> Unit): NearSearchOperatorDsl =
+ NearSearchOperatorDsl().apply(block)
+
+ "path" - {
+ "should build a path by strings" {
+ // given
+ val operator = near {
+ path("path1", "path2")
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "near": {
+ "path": [
+ "path1",
+ "path2"
+ ]
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ "should build a path by multiple properties" {
+ // given
+ data class TestCollection(val path1: Number, val path2: Number)
+
+ val operator = near {
+ path(TestCollection::path1, TestCollection::path2)
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "near": {
+ "path": [
+ "path1",
+ "path2"
+ ]
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ "should build a path by nested property" {
+ // given
+ data class Child(val path: Number)
+ data class Parent(val child: Child)
+
+ val operator = near {
+ path(Parent::child..Child::path)
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "near": {
+ "path": "child.path"
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+ }
+
+ "origin" - {
+ "should build a origin by number" {
+ // given
+ val operator = near {
+ origin(123)
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "near": {
+ "origin": 123
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ "should build a origin by LocalDateTime" {
+ // given
+ val operator = near {
+ origin(LocalDateTime.of(2023, 9, 18, 4, 10, 50, 1))
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "near": {
+ "origin": {
+ "${'$'}date": "2023-09-18T04:10:50Z"
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ "should build a origin by LocalDate" {
+ // given
+ val operator = near {
+ origin(LocalDate.of(2023, 9, 18))
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "near": {
+ "origin": {
+ "${'$'}date": "2023-09-18T00:00:00Z"
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ "should build a origin by GeoJson Point" {
+ // given
+ val operator = near {
+ origin(GeoJsonPoint(1.0, 2.0))
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "near": {
+ "origin": {
+ "type": "Point",
+ "coordinates": [
+ 1.0,
+ 2.0
+ ]
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+ }
+
+ "pivot" - {
+ "should build a pivot by Number" {
+ // given
+ val operator = near {
+ pivot(123)
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "near": {
+ "pivot": 123
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+ }
+
+ "score" - {
+ "should build a score" {
+ // given
+ val operator = near {
+ score {
+ boost(5.0)
+ }
+ }
+
+ // when
+ val result = operator.build()
+
+ // then
+ result.shouldBeJson(
+ """
+ {
+ "near": {
+ "score": {
+ "boost": {
+ "value": 5.0
+ }
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+ }
+})
diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/annotation/Database.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/annotation/Database.kt
new file mode 100644
index 00000000..9bc70556
--- /dev/null
+++ b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/annotation/Database.kt
@@ -0,0 +1,4 @@
+package com.github.inflab.example.spring.data.mongodb.annotation
+
+@Target(AnnotationTarget.VALUE_PARAMETER)
+annotation class Database(val value: String)
diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/airbnb/ListingsAndReviews.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/airbnb/ListingsAndReviews.kt
index 9e5d3947..2eca7316 100644
--- a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/airbnb/ListingsAndReviews.kt
+++ b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/airbnb/ListingsAndReviews.kt
@@ -2,11 +2,14 @@ package com.github.inflab.example.spring.data.mongodb.entity.airbnb
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document
+import org.springframework.data.mongodb.core.mapping.Field
@Document("listingsAndReviews")
data class ListingsAndReviews(
@Id
val id: String,
val name: String,
+ @Field("property_type")
+ val propertyType: String,
val address: ListingsAndReviewsAddress,
)
diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/NearSearchRepository.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/NearSearchRepository.kt
new file mode 100644
index 00000000..47e17c21
--- /dev/null
+++ b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/NearSearchRepository.kt
@@ -0,0 +1,163 @@
+package com.github.inflab.example.spring.data.mongodb.repository.atlas
+
+import com.github.inflab.example.spring.data.mongodb.annotation.Database
+import com.github.inflab.example.spring.data.mongodb.entity.airbnb.ListingsAndReviews
+import com.github.inflab.example.spring.data.mongodb.entity.airbnb.ListingsAndReviewsAddress
+import com.github.inflab.example.spring.data.mongodb.entity.mflix.Movies
+import com.github.inflab.spring.data.mongodb.core.aggregation.aggregation
+import com.github.inflab.spring.data.mongodb.core.mapping.rangeTo
+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.data.mongodb.core.geo.GeoJsonPoint
+import org.springframework.data.mongodb.core.mapping.Field
+import org.springframework.stereotype.Repository
+import java.time.LocalDateTime
+
+@Repository
+class NearSearchRepository(
+ @Database("sample_mflix") private val mflixMongoTemplate: MongoTemplate,
+ @Database("sample_airbnb") private val airbnbMongoTemplate: MongoTemplate,
+) {
+
+ data class RuntimeDto(
+ val title: String,
+ val runtime: Int,
+ val score: Double,
+ )
+
+ data class ReleasedDto(
+ val title: String,
+ val released: LocalDateTime,
+ val score: Double,
+ )
+
+ data class GeoDto(
+ val name: String,
+ val address: ListingsAndReviewsAddress,
+ val score: Double,
+ )
+
+ data class GeoPropertyTypeDto(
+ @Field("property_type")
+ val propertyType: String,
+ val address: ListingsAndReviewsAddress,
+ val score: Double,
+ )
+
+ /**
+ * @see Number Example
+ */
+ fun findByRuntime(): AggregationResults {
+ val aggregation = aggregation {
+ search {
+ index = "runtimes"
+ near {
+ path(Movies::runtime)
+ origin(279)
+ pivot(2)
+ }
+ }
+
+ // TODO: add $limit stage
+
+ project {
+ excludeId()
+ +Movies::title
+ +Movies::runtime
+ searchScore()
+ }
+ }
+
+ return mflixMongoTemplate.aggregate(aggregation)
+ }
+
+ /**
+ * @see Date Example
+ */
+ fun findByDate(): AggregationResults {
+ val aggregation = aggregation {
+ search {
+ index = "releaseddate"
+ near {
+ path(Movies::released)
+ origin(LocalDateTime.of(1915, 9, 13, 0, 0, 0))
+ pivot(7776000000)
+ }
+ }
+
+ // TODO: add $limit stage
+
+ project {
+ excludeId()
+ +Movies::title
+ +Movies::released
+ searchScore()
+ }
+ }
+
+ return mflixMongoTemplate.aggregate(aggregation)
+ }
+
+ /**
+ * @see GeoJSON Point Basic Example
+ */
+ fun findByGeo(): AggregationResults {
+ val aggregation = aggregation {
+ search {
+ near {
+ path(ListingsAndReviews::address..ListingsAndReviewsAddress::location)
+ origin(GeoJsonPoint(-8.61308, 41.1413))
+ pivot(1000)
+ }
+ }
+
+ // TODO: add $limit stage
+
+ project {
+ excludeId()
+ +ListingsAndReviews::name
+ +ListingsAndReviews::address
+ searchScore()
+ }
+ }
+
+ return airbnbMongoTemplate.aggregate(aggregation)
+ }
+
+ /**
+ * @see GeoJSON Point Compound Example
+ */
+ fun findByGeoWithCompound(): AggregationResults {
+ val aggregation = aggregation {
+ search {
+ compound {
+ must {
+ text {
+ query("Apartment")
+ path(ListingsAndReviews::propertyType)
+ }
+ }
+ should {
+ near {
+ origin(GeoJsonPoint(114.15027, 22.28158))
+ pivot(1000)
+ path(ListingsAndReviews::address..ListingsAndReviewsAddress::location)
+ }
+ }
+ }
+ }
+
+ // TODO: add $limit stage
+
+ project {
+ excludeId()
+ +ListingsAndReviews::propertyType
+ +ListingsAndReviews::address
+ searchScore()
+ }
+ }
+
+ return airbnbMongoTemplate.aggregate(aggregation)
+ }
+}
diff --git a/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/extension/AtlasTestConstructorExtension.kt b/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/extension/AtlasTestConstructorExtension.kt
index da030b9c..9af89755 100644
--- a/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/extension/AtlasTestConstructorExtension.kt
+++ b/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/extension/AtlasTestConstructorExtension.kt
@@ -1,5 +1,6 @@
package com.github.inflab.example.spring.data.mongodb.extension
+import com.github.inflab.example.spring.data.mongodb.annotation.Database
import io.kotest.core.annotation.AutoScan
import io.kotest.core.extensions.ConstructorExtension
import io.kotest.core.spec.Spec
@@ -7,36 +8,43 @@ import org.springframework.boot.env.YamlPropertySourceLoader
import org.springframework.core.io.ClassPathResource
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory
+import org.springframework.stereotype.Repository
import kotlin.reflect.KClass
+import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.primaryConstructor
@AutoScan
internal object AtlasTestConstructorExtension : ConstructorExtension {
private val ATLAS_DOMAIN by lazy {
val property = YamlPropertySourceLoader().load("env", ClassPathResource("application.yml")).first()
- val username = property.getProperty("spring.data.mongodb.username") ?: throw IllegalStateException("spring.data.mongodb.username is not set")
- val password = property.getProperty("spring.data.mongodb.password") ?: throw IllegalStateException("spring.data.mongodb.password is not set")
- val host = property.getProperty("spring.data.mongodb.host") ?: throw IllegalStateException("spring.data.mongodb.host is not set")
+ val username = property.getProperty("spring.data.mongodb.username")
+ ?: throw IllegalStateException("spring.data.mongodb.username is not set")
+ val password = property.getProperty("spring.data.mongodb.password")
+ ?: throw IllegalStateException("spring.data.mongodb.password is not set")
+ val host = property.getProperty("spring.data.mongodb.host")
+ ?: throw IllegalStateException("spring.data.mongodb.host is not set")
"$username:$password@$host"
}
override fun instantiate(clazz: KClass): Spec? {
- val atlasTest = clazz.annotations.find { it is AtlasTest } as AtlasTest? ?: return null
+ val atlasTest = clazz.findAnnotation() ?: return null
val testConstructor = clazz.primaryConstructor
if (testConstructor == null || testConstructor.parameters.isEmpty()) {
return null
}
- val connectionString = "mongodb+srv://$ATLAS_DOMAIN/${atlasTest.database}?retryWrites=true&w=majority"
- val mongoTemplate = MongoTemplate(SimpleMongoClientDatabaseFactory(connectionString))
+ val connectionString = getConnectionString(atlasTest.database)
+ val mongoTemplate = getMongoTemplate(connectionString)
val parameters = testConstructor.parameters.associateWith { parameter ->
val parameterClass = parameter.type.classifier as KClass<*>
- check(parameterClass.annotations.any { it is org.springframework.stereotype.Repository }) {
+
+ checkNotNull(parameterClass.findAnnotation()) {
"The parameter type of constructor must be annotated with @Repository but ${parameterClass.qualifiedName}"
}
+
val repositoryConstructor = checkNotNull(parameterClass.primaryConstructor) {
"The parameter type of constructor must have primary constructor but ${parameterClass.qualifiedName}"
}
@@ -47,7 +55,9 @@ internal object AtlasTestConstructorExtension : ConstructorExtension {
"The parameter type of repository constructor must be MongoTemplate but ${repositoryParameterClass.qualifiedName} from ${parameterClass.qualifiedName}"
}
- mongoTemplate
+ repositoryParameter.findAnnotation()?.let {
+ getMongoTemplate(getConnectionString(it.value))
+ } ?: mongoTemplate
}
repositoryConstructor.callBy(repositoryParameters)
@@ -55,4 +65,12 @@ internal object AtlasTestConstructorExtension : ConstructorExtension {
return testConstructor.callBy(parameters)
}
+
+ private fun getConnectionString(databaseName: String): String {
+ return "mongodb+srv://$ATLAS_DOMAIN/$databaseName?retryWrites=true&w=majority"
+ }
+
+ private fun getMongoTemplate(connectionString: String): MongoTemplate {
+ return MongoTemplate(SimpleMongoClientDatabaseFactory(connectionString))
+ }
}
diff --git a/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/NearSearchRepositoryTest.kt b/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/NearSearchRepositoryTest.kt
new file mode 100644
index 00000000..793b9004
--- /dev/null
+++ b/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/NearSearchRepositoryTest.kt
@@ -0,0 +1,76 @@
+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.shouldBe
+import org.springframework.data.mongodb.core.geo.GeoJsonPoint
+
+@AtlasTest(database = "sample_mflix")
+class NearSearchRepositoryTest(
+ private val nearSearchRepository: NearSearchRepository,
+) : FreeSpec({
+
+ "findByRuntime" {
+ // when
+ val result = nearSearchRepository.findByRuntime()
+
+ // then
+ result.mappedResults.take(5).map { it.title } shouldBe listOf(
+ "The Kingdom",
+ "The Jinx: The Life and Deaths of Robert Durst",
+ "Shoah",
+ "Les Misèrables",
+ "Tokyo Trial",
+ )
+
+ result.mappedResults.take(5).map { it.runtime } shouldBe listOf(
+ 279,
+ 279,
+ 280,
+ 281,
+ 277,
+ )
+ }
+
+ "findByDate" {
+ // when
+ val result = nearSearchRepository.findByDate()
+
+ // then
+ result.mappedResults.take(3).map { it.title } shouldBe listOf(
+ "Regeneration",
+ "The Cheat",
+ "Hell's Hinges",
+ )
+ }
+
+ "findByGeo" {
+ // when
+ val result = nearSearchRepository.findByGeo()
+
+ // then
+ result.mappedResults.take(3).map { it.name } shouldBe listOf(
+ "Ribeira Charming Duplex",
+ "DB RIBEIRA - Grey Apartment",
+ "Ribeira 24 (4)",
+ )
+ }
+
+ "findByGeoWithCompound" {
+ // when
+ val result = nearSearchRepository.findByGeoWithCompound()
+
+ // then
+ result.mappedResults.take(3).map { it.propertyType } shouldBe listOf(
+ "Apartment",
+ "Apartment",
+ "Apartment",
+ )
+
+ result.mappedResults.take(3).map { it.address.location } shouldBe listOf(
+ GeoJsonPoint(114.15027, 22.28158),
+ GeoJsonPoint(114.15082, 22.28161),
+ GeoJsonPoint(114.15007, 22.28215),
+ )
+ }
+})