1{-# LANGUAGE DeriveGeneric #-}
5import Database.Bloodhound
7import Data.Either (Either(..))
8import Data.Maybe (fromJust)
9import Data.Time.Calendar (Day(..))
10import Data.Time.Clock (secondsToDiffTime, UTCTime(..))
11import Data.Text (Text)
12import GHC.Generics (Generic)
13import Network.HTTP.Conduit
14import qualified Network.HTTP.Types.Status as NHTS
17testServer = Server "http://localhost:9200"
18testIndex = IndexName "twitter"
19testMapping = MappingName "tweet"
21validateStatus resp expected =
22 (NHTS.statusCode $ responseStatus resp)
23 `shouldBe` (expected :: Int)
25createExampleIndex = createIndex testServer defaultIndexSettings testIndex
26deleteExampleIndex = deleteIndex testServer testIndex
28data Location = Location { lat :: Double
29 , lon :: Double } deriving (Eq, Generic, Show)
31data Tweet = Tweet { user :: Text
35 , location :: Location }
36 deriving (Eq, Generic, Show)
39instance FromJSON Tweet
40instance ToJSON Location
41instance FromJSON Location
43data TweetMapping = TweetMapping deriving (Eq, Show)
45instance ToJSON TweetMapping where
48 object ["properties" .=
49 object ["location" .= object ["type" .= ("geo_point" :: Text)]]]]
51exampleTweet = Tweet { user = "bitemyapp"
53 (ModifiedJulianDay 55000)
54 (secondsToDiffTime 10)
55 , message = "Use haskell!"
57 , location = Location 40.12 (-71.34) }
59otherTweet = Tweet { user = "notmyapp"
61 (ModifiedJulianDay 55000)
62 (secondsToDiffTime 11)
63 , message = "Use haskell!"
65 , location = Location 40.12 (-71.34) }
69 let encoded = encode exampleTweet
70 _ <- deleteExampleIndex
71 created <- createExampleIndex
72 mappingCreated <- createMapping testServer testIndex testMapping TweetMapping
73 docCreated <- indexDocument testServer testIndex testMapping exampleTweet (DocId "1")
74 _ <- refreshIndex testServer testIndex
79 docCreated <- indexDocument testServer testIndex testMapping otherTweet (DocId "2")
80 _ <- refreshIndex testServer testIndex
83searchTweet :: Search -> IO (Either String Tweet)
84searchTweet search = do
85 reply <- searchByIndex testServer testIndex search
86 let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)
87 let myTweet = fmap (hitSource . head . hits . searchHits) result
90searchExpectNoResults :: Search -> IO ()
91searchExpectNoResults search = do
92 reply <- searchByIndex testServer testIndex search
93 let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)
94 let emptyHits = fmap (hits . searchHits) result
95 emptyHits `shouldBe` Right []
97data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
98instance FromJSON BulkTest
99instance ToJSON BulkTest
104 describe "index create/delete API" $ do
105 it "creates and then deletes the requested index" $ do
107 _ <- deleteExampleIndex
108 resp <- createExampleIndex
109 deleteResp <- deleteExampleIndex
110 validateStatus resp 200
111 validateStatus deleteResp 200
113 describe "document API" $ do
114 it "indexes, gets, and then deletes the generated document" $ do
116 docInserted <- getDocument testServer testIndex testMapping (DocId "1")
117 let newTweet = eitherDecode
118 (responseBody docInserted) :: Either String (EsResult Tweet)
119 fmap _source newTweet `shouldBe` Right exampleTweet
121 describe "bulk API" $ do
122 it "inserts all documents we request" $ do
124 let firstTest = BulkTest "blah"
125 let secondTest = BulkTest "bloo"
126 let firstDoc = BulkIndex (IndexName "twitter")
127 (MappingName "tweet") (DocId "2") (object ["name" .= String "blah"])
128 let secondDoc = BulkCreate (IndexName "twitter")
129 (MappingName "tweet") (DocId "3") (object ["name" .= String "bloo"])
130 let stream = [firstDoc, secondDoc]
131 response <- bulk testServer stream
132 _ <- refreshIndex testServer testIndex
133 fDoc <- getDocument testServer testIndex testMapping (DocId "2")
134 sDoc <- getDocument testServer testIndex testMapping (DocId "3")
135 let maybeFirst = eitherDecode $ responseBody fDoc :: Either String (EsResult BulkTest)
136 let maybeSecond = eitherDecode $ responseBody sDoc :: Either String (EsResult BulkTest)
137 fmap _source maybeFirst `shouldBe` Right firstTest
138 fmap _source maybeSecond `shouldBe` Right secondTest
140 describe "query API" $ do
141 it "returns document for term query and identity filter" $ do
143 let query = TermQuery (Term "user" "bitemyapp") Nothing
144 let filter = IdentityFilter <&&> IdentityFilter
145 let search = mkSearch (Just query) (Just filter)
146 myTweet <- searchTweet search
147 myTweet `shouldBe` Right exampleTweet
149 describe "sorting" $ do
150 it "returns documents in the right order" $ do
153 let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
154 let search = Search Nothing
155 (Just IdentityFilter) (Just [sortSpec])
157 reply <- searchByIndex testServer testIndex search
158 let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)
159 let myTweet = fmap (hitSource . head . hits . searchHits) result
160 myTweet `shouldBe` Right otherTweet
162 describe "filtering API" $ do
163 it "returns document for composed boolmatch and identity" $ do
165 let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
167 let search = mkSearch Nothing (Just queryFilter)
168 myTweet <- searchTweet search
169 myTweet `shouldBe` Right exampleTweet
171 it "returns document for existential filter" $ do
173 let search = mkSearch Nothing (Just (ExistsFilter (FieldName "user")))
174 myTweet <- searchTweet search
175 myTweet `shouldBe` Right exampleTweet
177 it "returns document for geo boundingbox filter" $ do
179 let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
180 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False
181 let geoFilter = GeoBoundingBoxFilter bbConstraint GeoFilterMemory
182 let search = mkSearch Nothing (Just geoFilter)
183 myTweet <- searchTweet search
184 myTweet `shouldBe` Right exampleTweet
186 it "doesn't return document for nonsensical boundingbox filter" $ do
188 let box = GeoBoundingBox (LatLon 0.73 (-4.1)) (LatLon 0.10 (-1.12))
189 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False
190 let geoFilter = GeoBoundingBoxFilter bbConstraint GeoFilterMemory
191 let search = mkSearch Nothing (Just geoFilter)
192 searchExpectNoResults search
194 it "returns document for geo distance filter" $ do
196 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
197 let distance = Distance 10.0 Miles
198 let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
199 let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
200 let search = mkSearch Nothing (Just geoFilter)
201 myTweet <- searchTweet search
202 myTweet `shouldBe` Right exampleTweet
204 it "returns document for geo distance range filter" $ do
206 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
207 let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
208 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
209 let search = mkSearch Nothing (Just geoFilter)
210 myTweet <- searchTweet search
211 myTweet `shouldBe` Right exampleTweet
213 it "doesn't return document for wild geo distance range filter" $ do
215 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
216 let distanceRange = DistanceRange (Distance 100.0 Miles) (Distance 1000.0 Miles)
217 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
218 let search = mkSearch Nothing (Just geoFilter)
219 searchExpectNoResults search
221 it "returns document for geo polygon filter" $ do
223 let points = [LatLon 40.0 (-70.00),
224 LatLon 40.0 (-72.00),
225 LatLon 41.0 (-70.00),
226 LatLon 41.0 (-72.00)]
227 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
228 let search = mkSearch Nothing (Just geoFilter)
229 myTweet <- searchTweet search
230 myTweet `shouldBe` Right exampleTweet
232 it "doesn't return document for bad geo polygon filter" $ do
234 let points = [LatLon 40.0 (-70.00),
235 LatLon 40.0 (-71.00),
236 LatLon 41.0 (-70.00),
237 LatLon 41.0 (-71.00)]
238 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
239 let search = mkSearch Nothing (Just geoFilter)
240 searchExpectNoResults search
242 it "returns document for ids filter" $ do
244 let filter = IdsFilter (MappingName "tweet") [DocId "1"]
245 let search = mkSearch Nothing (Just filter)
246 myTweet <- searchTweet search
247 myTweet `shouldBe` Right exampleTweet
249 it "returns document for range filter" $ do
251 let filter = RangeFilter (FieldName "age")
252 (Right (RangeLtGt (LessThan 100000.0) (GreaterThan 1000.0)))
253 RangeExecutionIndex False
254 let search = mkSearch Nothing (Just filter)
255 myTweet <- searchTweet search
256 myTweet `shouldBe` Right exampleTweet
258 it "returns document for regexp filter" $ do
260 let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
261 RegexpAll (CacheName "test") False (CacheKey "key")
262 let search = mkSearch Nothing (Just filter)
263 myTweet <- searchTweet search
264 myTweet `shouldBe` Right exampleTweet
266 it "doesn't return document for non-matching regexp filter" $ do
268 let filter = RegexpFilter (FieldName "user")
269 (Regexp "boy") RegexpAll
270 (CacheName "test") False (CacheKey "key")
271 let search = mkSearch Nothing (Just filter)
272 searchExpectNoResults search
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list