1{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE ScopedTypeVariables #-}
4{-# OPTIONS_GHC -fno-warn-orphans #-}
7import Control.Applicative
8import Control.Exception
10import Control.Monad.Reader
12import qualified Data.HashMap.Strict as HM
14import Data.List.NonEmpty (NonEmpty (..))
15import qualified Data.List.NonEmpty as NE
16import qualified Data.Map.Strict as M
17import qualified Data.Maybe as MY
19import Data.Text (Text)
20import qualified Data.Text as T
21import Data.Time.Calendar (Day (..))
22import Data.Time.Clock (UTCTime (..),
24import qualified Data.Vector as V
25import Database.Bloodhound
26import GHC.Generics (Generic)
27import Network.HTTP.Client
28import qualified Network.HTTP.Types.Status as NHTS
29import Prelude hiding (filter)
31import Test.QuickCheck.Property.Monoid (T (..), eq, prop_Monoid)
33import Test.Hspec.QuickCheck (prop)
37testServer = Server "http://localhost:9200"
39testIndex = IndexName "bloodhound-tests-twitter-1"
40testMapping :: MappingName
41testMapping = MappingName "tweet"
43withTestEnv :: BH IO a -> IO a
44withTestEnv = withBH defaultManagerSettings testServer
46validateStatus :: Response body -> Int -> Expectation
47validateStatus resp expected =
48 (NHTS.statusCode $ responseStatus resp)
49 `shouldBe` (expected :: Int)
51createExampleIndex :: BH IO Reply
52createExampleIndex = createIndex (IndexSettings (ShardCount 1) (ReplicaCount 0)) testIndex
53deleteExampleIndex :: BH IO Reply
54deleteExampleIndex = deleteIndex testIndex
56data ServerVersion = ServerVersion Int Int Int deriving (Show, Eq, Ord)
59es14 = ServerVersion 1 4 0
62es13 = ServerVersion 1 3 0
65es12 = ServerVersion 1 2 0
68es11 = ServerVersion 1 1 0
71es10 = ServerVersion 1 0 0
73serverBranch :: ServerVersion -> ServerVersion
74serverBranch (ServerVersion majorVer minorVer patchVer) =
75 ServerVersion majorVer minorVer patchVer
77mkServerVersion :: [Int] -> Maybe ServerVersion
78mkServerVersion [majorVer, minorVer, patchVer] =
79 Just (ServerVersion majorVer minorVer patchVer)
80mkServerVersion _ = Nothing
82getServerVersion :: IO (Maybe ServerVersion)
83getServerVersion = liftM extractVersion (withTestEnv getStatus)
85 version' = T.splitOn "." . number . version
86 toInt = read . T.unpack
87 parseVersion v = map toInt (version' v)
88 extractVersion = join . liftM (mkServerVersion . parseVersion)
90testServerBranch :: IO (Maybe ServerVersion)
91testServerBranch = getServerVersion >>= \v -> return $ liftM serverBranch v
93atleast :: ServerVersion -> IO Bool
94atleast v = testServerBranch >>= \x -> return $ x >= Just (serverBranch v)
96atmost :: ServerVersion -> IO Bool
97atmost v = testServerBranch >>= \x -> return $ x <= Just (serverBranch v)
99is :: ServerVersion -> IO Bool
100is v = testServerBranch >>= \x -> return $ x == Just (serverBranch v)
102when' :: Monad m => m Bool -> m () -> m ()
103when' b f = b >>= \x -> when x f
105data Location = Location { lat :: Double
106 , lon :: Double } deriving (Eq, Generic, Show)
108data Tweet = Tweet { user :: Text
109 , postDate :: UTCTime
112 , location :: Location }
113 deriving (Eq, Generic, Show)
116instance FromJSON Tweet
117instance ToJSON Location
118instance FromJSON Location
120data ParentMapping = ParentMapping deriving (Eq, Show)
122instance ToJSON ParentMapping where
123 toJSON ParentMapping =
124 object ["parent" .= Null ]
126data ChildMapping = ChildMapping deriving (Eq, Show)
128instance ToJSON ChildMapping where
129 toJSON ChildMapping =
131 object ["_parent" .= object ["type" .= ("parent" :: Text)]]
134data TweetMapping = TweetMapping deriving (Eq, Show)
136instance ToJSON TweetMapping where
137 toJSON TweetMapping =
139 object ["properties" .=
140 object [ "user" .= object ["type" .= ("string" :: Text)]
141 -- Serializing the date as a date is breaking other tests, mysteriously.
142 -- , "postDate" .= object [ "type" .= ("date" :: Text)
143 -- , "format" .= ("YYYY-MM-dd`T`HH:mm:ss.SSSZZ" :: Text)]
144 , "message" .= object ["type" .= ("string" :: Text)]
145 , "age" .= object ["type" .= ("integer" :: Text)]
146 , "location" .= object ["type" .= ("geo_point" :: Text)]
150exampleTweet = Tweet { user = "bitemyapp"
152 (ModifiedJulianDay 55000)
153 (secondsToDiffTime 10)
154 , message = "Use haskell!"
156 , location = Location 40.12 (-71.34) }
159otherTweet = Tweet { user = "notmyapp"
161 (ModifiedJulianDay 55000)
162 (secondsToDiffTime 11)
163 , message = "Use haskell!"
165 , location = Location 40.12 (-71.34) }
167resetIndex :: BH IO ()
169 _ <- deleteExampleIndex
170 _ <- createExampleIndex
171 _ <- putMapping testIndex testMapping TweetMapping
174insertData :: BH IO Reply
177 insertData' defaultIndexDocumentSettings
179insertData' :: IndexDocumentSettings -> BH IO Reply
181 r <- indexDocument testIndex testMapping ids exampleTweet (DocId "1")
182 _ <- refreshIndex testIndex
185insertOther :: BH IO ()
187 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings otherTweet (DocId "2")
188 _ <- refreshIndex testIndex
191insertWithSpaceInId :: BH IO ()
192insertWithSpaceInId = do
193 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "Hello World")
194 _ <- refreshIndex testIndex
197searchTweet :: Search -> BH IO (Either String Tweet)
198searchTweet search = do
199 result <- searchTweets search
200 let myTweet :: Either String Tweet
201 myTweet = grabFirst result
204searchTweets :: Search -> BH IO (Either String (SearchResult Tweet))
205searchTweets search = eitherDecode . responseBody <$> searchByIndex testIndex search
207searchExpectNoResults :: Search -> BH IO ()
208searchExpectNoResults search = do
209 result <- searchTweets search
210 let emptyHits = fmap (hits . searchHits) result
212 emptyHits `shouldBe` Right []
214searchExpectAggs :: Search -> BH IO ()
215searchExpectAggs search = do
216 reply <- searchAll search
217 let isEmpty x = return (M.null x)
218 let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
220 (result >>= aggregations >>= isEmpty) `shouldBe` Just False
222searchValidBucketAgg :: (BucketAggregation a, FromJSON a, Show a) =>
223 Search -> Text -> (Text -> AggregationResults -> Maybe (Bucket a)) -> BH IO ()
224searchValidBucketAgg search aggKey extractor = do
225 reply <- searchAll search
226 let bucketDocs = docCount . head . buckets
227 let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
228 let count = result >>= aggregations >>= extractor aggKey >>= \x -> return (bucketDocs x)
230 count `shouldBe` Just 1
232searchTermsAggHint :: [ExecutionHint] -> BH IO ()
233searchTermsAggHint hints = do
234 let terms hint = TermsAgg $ (mkTermsAggregation "user") { termExecutionHint = Just hint }
235 let search hint = mkAggregateSearch Nothing $ mkAggregations "users" $ terms hint
236 forM_ hints $ searchExpectAggs . search
237 forM_ hints (\x -> searchValidBucketAgg (search x) "users" toTerms)
239searchTweetHighlight :: Search -> BH IO (Either String (Maybe HitHighlight))
240searchTweetHighlight search = do
241 result <- searchTweets search
242 let myHighlight = fmap (hitHighlight . head . hits . searchHits) result
245searchExpectSource :: Source -> Either String Value -> BH IO ()
246searchExpectSource src expected = do
248 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
249 let search = (mkSearch (Just query) Nothing) { source = Just src }
250 reply <- searchAll search
251 let result = eitherDecode (responseBody reply) :: Either String (SearchResult Value)
252 let value = grabFirst result
254 value `shouldBe` expected
256data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
257instance FromJSON BulkTest
258instance ToJSON BulkTest
260noDuplicates :: Eq a => [a] -> Bool
261noDuplicates xs = nub xs == xs
263instance Arbitrary RegexpFlags where
264 arbitrary = oneof [ pure AllRegexpFlags
266 , SomeRegexpFlags <$> arbitrary
269instance Arbitrary a => Arbitrary (NonEmpty a) where
270 arbitrary = liftA2 (:|) arbitrary arbitrary
272instance Arbitrary RegexpFlag where
273 arbitrary = oneof [ pure AnyString
281arbitraryScore :: Gen Score
282arbitraryScore = fmap getPositive <$> arbitrary
284instance Arbitrary Text where
285 arbitrary = T.pack <$> arbitrary
287instance (Arbitrary k, Arbitrary v, Ord k) => Arbitrary (M.Map k v) where
288 arbitrary = M.fromList <$> arbitrary
290instance Arbitrary IndexName where
291 arbitrary = IndexName <$> arbitrary
293instance Arbitrary MappingName where
294 arbitrary = MappingName <$> arbitrary
296instance Arbitrary DocId where
297 arbitrary = DocId <$> arbitrary
299instance Arbitrary a => Arbitrary (Hit a) where
300 arbitrary = Hit <$> arbitrary
308instance Arbitrary a => Arbitrary (SearchHits a) where
309 arbitrary = sized $ \n -> resize (n `div` 2) $ do
310 tot <- getPositive <$> arbitrary
311 score <- arbitraryScore
313 return $ SearchHits tot score hs
315getSource :: EsResult a -> Maybe a
316getSource = fmap _source . foundResult
318-- grabFirst :: Either String (SearchResult a) -> Either String a
320 case fmap (hitSource . head . hits . searchHits) r of
322 (Right Nothing) -> Left "Source was missing"
323 (Right (Just x)) -> Right x
328 describe "index create/delete API" $ do
329 it "creates and then deletes the requested index" $ withTestEnv $ do
331 _ <- deleteExampleIndex
332 resp <- createExampleIndex
333 deleteResp <- deleteExampleIndex
335 validateStatus resp 200
336 validateStatus deleteResp 200
339 describe "document API" $ do
340 it "indexes, gets, and then deletes the generated document" $ withTestEnv $ do
342 docInserted <- getDocument testIndex testMapping (DocId "1")
343 let newTweet = eitherDecode
344 (responseBody docInserted) :: Either String (EsResult Tweet)
345 liftIO $ (fmap getSource newTweet `shouldBe` Right (Just exampleTweet))
347 it "indexes, gets, and then deletes the generated document with a DocId containing a space" $ withTestEnv $ do
348 _ <- insertWithSpaceInId
349 docInserted <- getDocument testIndex testMapping (DocId "Hello World")
350 let newTweet = eitherDecode
351 (responseBody docInserted) :: Either String (EsResult Tweet)
352 liftIO $ (fmap getSource newTweet `shouldBe` Right (Just exampleTweet))
354 it "produces a parseable result when looking up a bogus document" $ withTestEnv $ do
355 doc <- getDocument testIndex testMapping (DocId "bogus")
356 let noTweet = eitherDecode
357 (responseBody doc) :: Either String (EsResult Tweet)
358 liftIO $ fmap foundResult noTweet `shouldBe` Right Nothing
360 it "can use optimistic concurrency control" $ withTestEnv $ do
361 let ev = ExternalDocVersion minBound
362 let cfg = defaultIndexDocumentSettings { idsVersionControl = ExternalGT ev }
364 res <- insertData' cfg
365 liftIO $ isCreated res `shouldBe` True
366 res' <- insertData' cfg
367 liftIO $ isVersionConflict res' `shouldBe` True
369 it "indexes two documents in a parent/child relationship and checks that the child exists" $ withTestEnv $ do
371 _ <- putMapping testIndex (MappingName "parent") ParentMapping
372 _ <- putMapping testIndex (MappingName "child") ChildMapping
373 _ <- indexDocument testIndex (MappingName "parent") defaultIndexDocumentSettings exampleTweet (DocId "1")
374 let parent = (Just . DocumentParent . DocId) "1"
375 ids = IndexDocumentSettings NoVersionControl parent
376 _ <- indexDocument testIndex (MappingName "child") ids otherTweet (DocId "2")
377 _ <- refreshIndex testIndex
378 exists <- documentExists testIndex (MappingName "child") parent (DocId "2")
379 liftIO $ exists `shouldBe` True
381 describe "template API" $ do
382 it "can create a template" $ withTestEnv $ do
383 let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
384 resp <- putTemplate idxTpl (TemplateName "tweet-tpl")
385 liftIO $ validateStatus resp 200
387 it "can detect if a template exists" $ withTestEnv $ do
388 exists <- templateExists (TemplateName "tweet-tpl")
389 liftIO $ exists `shouldBe` True
391 it "can delete a template" $ withTestEnv $ do
392 resp <- deleteTemplate (TemplateName "tweet-tpl")
393 liftIO $ validateStatus resp 200
395 it "can detect if a template doesn't exist" $ withTestEnv $ do
396 exists <- templateExists (TemplateName "tweet-tpl")
397 liftIO $ exists `shouldBe` False
399 describe "bulk API" $ do
400 it "inserts all documents we request" $ withTestEnv $ do
402 let firstTest = BulkTest "blah"
403 let secondTest = BulkTest "bloo"
404 let firstDoc = BulkIndex testIndex
405 testMapping (DocId "2") (toJSON firstTest)
406 let secondDoc = BulkCreate testIndex
407 testMapping (DocId "3") (toJSON secondTest)
408 let stream = V.fromList [firstDoc, secondDoc]
410 _ <- refreshIndex testIndex
411 fDoc <- getDocument testIndex testMapping (DocId "2")
412 sDoc <- getDocument testIndex testMapping (DocId "3")
413 let maybeFirst = eitherDecode $ responseBody fDoc :: Either String (EsResult BulkTest)
414 let maybeSecond = eitherDecode $ responseBody sDoc :: Either String (EsResult BulkTest)
416 fmap getSource maybeFirst `shouldBe` Right (Just firstTest)
417 fmap getSource maybeSecond `shouldBe` Right (Just secondTest)
420 describe "query API" $ do
421 it "returns document for term query and identity filter" $ withTestEnv $ do
423 let query = TermQuery (Term "user" "bitemyapp") Nothing
424 let filter = IdentityFilter <&&> IdentityFilter
425 let search = mkSearch (Just query) (Just filter)
426 myTweet <- searchTweet search
428 myTweet `shouldBe` Right exampleTweet
430 it "returns document for terms query and identity filter" $ withTestEnv $ do
432 let query = TermsQuery (NE.fromList [(Term "user" "bitemyapp")])
433 let filter = IdentityFilter <&&> IdentityFilter
434 let search = mkSearch (Just query) (Just filter)
435 myTweet <- searchTweet search
437 myTweet `shouldBe` Right exampleTweet
439 it "returns document for match query" $ withTestEnv $ do
441 let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
442 let search = mkSearch (Just query) Nothing
443 myTweet <- searchTweet search
445 myTweet `shouldBe` Right exampleTweet
447 it "returns document for multi-match query" $ withTestEnv $ do
449 let flds = [FieldName "user", FieldName "message"]
450 let query = QueryMultiMatchQuery $ mkMultiMatchQuery flds (QueryString "bitemyapp")
451 let search = mkSearch (Just query) Nothing
452 myTweet <- searchTweet search
454 myTweet `shouldBe` Right exampleTweet
456 it "returns document for bool query" $ withTestEnv $ do
458 let innerQuery = QueryMatchQuery $
459 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
460 let query = QueryBoolQuery $
461 mkBoolQuery [innerQuery] [] []
462 let search = mkSearch (Just query) Nothing
463 myTweet <- searchTweet search
465 myTweet `shouldBe` Right exampleTweet
467 it "returns document for boosting query" $ withTestEnv $ do
469 let posQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
470 let negQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "notmyapp")
471 let query = QueryBoostingQuery $ BoostingQuery posQuery negQuery (Boost 0.2)
472 let search = mkSearch (Just query) Nothing
473 myTweet <- searchTweet search
475 myTweet `shouldBe` Right exampleTweet
477 it "returns document for common terms query" $ withTestEnv $ do
479 let query = QueryCommonTermsQuery $
480 CommonTermsQuery (FieldName "user")
481 (QueryString "bitemyapp")
482 (CutoffFrequency 0.0001)
483 Or Or Nothing Nothing Nothing Nothing
484 let search = mkSearch (Just query) Nothing
485 myTweet <- searchTweet search
487 myTweet `shouldBe` Right exampleTweet
490 describe "sorting" $ do
491 it "returns documents in the right order" $ withTestEnv $ do
494 let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
495 let search = Search Nothing
496 (Just IdentityFilter) (Just [sortSpec]) Nothing Nothing
497 False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
498 result <- searchTweets search
499 let myTweet = grabFirst result
501 myTweet `shouldBe` Right otherTweet
504 describe "filtering API" $ do
505 it "returns document for composed boolmatch and identity" $ withTestEnv $ do
507 let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
509 let search = mkSearch Nothing (Just queryFilter)
510 myTweet <- searchTweet search
512 myTweet `shouldBe` Right exampleTweet
514 it "returns document for term filter" $ withTestEnv $ do
516 let termFilter = TermFilter (Term "user" "bitemyapp") False
517 let search = mkSearch Nothing (Just termFilter)
518 myTweet <- searchTweet search
520 myTweet `shouldBe` Right exampleTweet
522 it "returns document for existential filter" $ withTestEnv $ do
524 let search = mkSearch Nothing (Just (ExistsFilter (FieldName "user")))
525 myTweet <- searchTweet search
527 myTweet `shouldBe` Right exampleTweet
529 it "returns document for geo boundingbox filter" $ withTestEnv $ do
531 let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
532 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
533 let geoFilter = GeoBoundingBoxFilter bbConstraint
534 let search = mkSearch Nothing (Just geoFilter)
535 myTweet <- searchTweet search
537 myTweet `shouldBe` Right exampleTweet
539 it "doesn't return document for nonsensical boundingbox filter" $ withTestEnv $ do
541 let box = GeoBoundingBox (LatLon 0.73 (-4.1)) (LatLon 0.10 (-1.12))
542 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
543 let geoFilter = GeoBoundingBoxFilter bbConstraint
544 let search = mkSearch Nothing (Just geoFilter)
545 searchExpectNoResults search
547 it "returns document for geo distance filter" $ withTestEnv $ do
549 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
550 let distance = Distance 10.0 Miles
551 let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
552 let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
553 let search = mkSearch Nothing (Just geoFilter)
554 myTweet <- searchTweet search
556 myTweet `shouldBe` Right exampleTweet
558 it "returns document for geo distance range filter" $ withTestEnv $ do
560 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
561 let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
562 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
563 let search = mkSearch Nothing (Just geoFilter)
564 myTweet <- searchTweet search
566 myTweet `shouldBe` Right exampleTweet
568 it "doesn't return document for wild geo distance range filter" $ withTestEnv $ do
570 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
571 let distanceRange = DistanceRange (Distance 100.0 Miles) (Distance 1000.0 Miles)
572 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
573 let search = mkSearch Nothing (Just geoFilter)
574 searchExpectNoResults search
576 it "returns document for geo polygon filter" $ withTestEnv $ do
578 let points = [LatLon 40.0 (-70.00),
579 LatLon 40.0 (-72.00),
580 LatLon 41.0 (-70.00),
581 LatLon 41.0 (-72.00)]
582 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
583 let search = mkSearch Nothing (Just geoFilter)
584 myTweet <- searchTweet search
586 myTweet `shouldBe` Right exampleTweet
588 it "doesn't return document for bad geo polygon filter" $ withTestEnv $ do
590 let points = [LatLon 40.0 (-70.00),
591 LatLon 40.0 (-71.00),
592 LatLon 41.0 (-70.00),
593 LatLon 41.0 (-71.00)]
594 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
595 let search = mkSearch Nothing (Just geoFilter)
596 searchExpectNoResults search
598 it "returns document for ids filter" $ withTestEnv $ do
600 let filter = IdsFilter (MappingName "tweet") [DocId "1"]
601 let search = mkSearch Nothing (Just filter)
602 myTweet <- searchTweet search
604 myTweet `shouldBe` Right exampleTweet
606 it "returns document for Double range filter" $ withTestEnv $ do
608 let filter = RangeFilter (FieldName "age")
609 (RangeDoubleGtLt (GreaterThan 1000.0) (LessThan 100000.0))
610 RangeExecutionIndex False
611 let search = mkSearch Nothing (Just filter)
612 myTweet <- searchTweet search
614 myTweet `shouldBe` Right exampleTweet
616 it "returns document for UTCTime date filter" $ withTestEnv $ do
618 let filter = RangeFilter (FieldName "postDate")
620 (GreaterThanD (UTCTime
621 (ModifiedJulianDay 54000)
622 (secondsToDiffTime 0)))
624 (ModifiedJulianDay 55000)
625 (secondsToDiffTime 11))))
626 RangeExecutionIndex False
627 let search = mkSearch Nothing (Just filter)
628 myTweet <- searchTweet search
630 myTweet `shouldBe` Right exampleTweet
632 it "returns document for regexp filter" $ withTestEnv $ do
634 let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
635 AllRegexpFlags (CacheName "test") False (CacheKey "key")
636 let search = mkSearch Nothing (Just filter)
637 myTweet <- searchTweet search
639 myTweet `shouldBe` Right exampleTweet
641 it "doesn't return document for non-matching regexp filter" $ withTestEnv $ do
643 let filter = RegexpFilter (FieldName "user")
644 (Regexp "boy") AllRegexpFlags
645 (CacheName "test") False (CacheKey "key")
646 let search = mkSearch Nothing (Just filter)
647 searchExpectNoResults search
649 it "returns document for query filter, uncached" $ withTestEnv $ do
651 let filter = QueryFilter (TermQuery (Term "user" "bitemyapp") Nothing) True
652 search = mkSearch Nothing (Just filter)
653 myTweet <- searchTweet search
654 liftIO $ myTweet `shouldBe` Right exampleTweet
656 it "returns document for query filter, cached" $ withTestEnv $ do
658 let filter = QueryFilter (TermQuery (Term "user" "bitemyapp") Nothing) False
659 search = mkSearch Nothing (Just filter)
660 myTweet <- searchTweet search
661 liftIO $ myTweet `shouldBe` Right exampleTweet
663 describe "Aggregation API" $ do
664 it "returns term aggregation results" $ withTestEnv $ do
666 let terms = TermsAgg $ mkTermsAggregation "user"
667 let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
668 searchExpectAggs search
669 searchValidBucketAgg search "users" toTerms
671 it "can give collection hint parameters to term aggregations" $ when' (atleast es13) $ withTestEnv $ do
673 let terms = TermsAgg $ (mkTermsAggregation "user") { termCollectMode = Just BreadthFirst }
674 let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
675 searchExpectAggs search
676 searchValidBucketAgg search "users" toTerms
678 it "can give execution hint parameters to term aggregations" $ when' (atmost es11) $ withTestEnv $ do
680 searchTermsAggHint [Map, Ordinals]
682 it "can give execution hint parameters to term aggregations" $ when' (is es12) $ withTestEnv $ do
684 searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map, Ordinals]
686 it "can give execution hint parameters to term aggregations" $ when' (atleast es12) $ withTestEnv $ do
688 searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map]
691 it "can execute value_count aggregations" $ withTestEnv $ do
694 let ags = mkAggregations "user_count" (ValueCountAgg (FieldValueCount (FieldName "user"))) <>
695 mkAggregations "bogus_count" (ValueCountAgg (FieldValueCount (FieldName "bogus")))
696 let search = mkAggregateSearch Nothing ags
697 let docCountPair k n = (k, object ["value" .= Number n])
698 res <- searchTweets search
700 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "user_count" 2
701 , docCountPair "bogus_count" 0
704 it "can execute filter aggregations" $ withTestEnv $ do
707 let ags = mkAggregations "bitemyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "bitemyapp") defaultCache) Nothing)) <>
708 mkAggregations "notmyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "notmyapp") defaultCache) Nothing))
709 let search = mkAggregateSearch Nothing ags
710 let docCountPair k n = (k, object ["doc_count" .= Number n])
711 res <- searchTweets search
713 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "bitemyapps" 1
714 , docCountPair "notmyapps" 1
717 it "returns date histogram aggregation results" $ withTestEnv $ do
719 let histogram = DateHistogramAgg $ mkDateHistogram (FieldName "postDate") Minute
720 let search = mkAggregateSearch Nothing (mkAggregations "byDate" histogram)
721 searchExpectAggs search
722 searchValidBucketAgg search "byDate" toDateHistogram
724 it "returns date histogram using fractional date" $ withTestEnv $ do
726 let periods = [Year, Quarter, Month, Week, Day, Hour, Minute, Second]
727 let fractionals = map (FractionalInterval 1.5) [Weeks, Days, Hours, Minutes, Seconds]
728 let intervals = periods ++ fractionals
729 let histogram = mkDateHistogram (FieldName "postDate")
730 let search interval = mkAggregateSearch Nothing $ mkAggregations "byDate" $ DateHistogramAgg (histogram interval)
731 let expect interval = searchExpectAggs (search interval)
732 let valid interval = searchValidBucketAgg (search interval) "byDate" toDateHistogram
733 forM_ intervals expect
734 forM_ intervals valid
736 describe "Highlights API" $ do
738 it "returns highlight from query when there should be one" $ withTestEnv $ do
741 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
742 let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing]
744 let search = mkHighlightSearch (Just query) testHighlight
745 myHighlight <- searchTweetHighlight search
747 myHighlight `shouldBe` Right (Just (M.fromList [("message",["Use <em>haskell</em>!"])]))
749 it "doesn't return highlight from a query when it shouldn't" $ withTestEnv $ do
752 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
753 let testHighlight = Highlights Nothing [FieldHighlight (FieldName "user") Nothing]
755 let search = mkHighlightSearch (Just query) testHighlight
756 myHighlight <- searchTweetHighlight search
758 myHighlight `shouldBe` Right Nothing
760 describe "Source filtering" $ do
762 it "doesn't include source when sources are disabled" $ withTestEnv $ do
765 (Left "Source was missing")
767 it "includes a source" $ withTestEnv $ do
769 (SourcePatterns (PopPattern (Pattern "message")))
770 (Right (Object (HM.fromList [("message", String "Use haskell!")])))
772 it "includes sources" $ withTestEnv $ do
774 (SourcePatterns (PopPatterns [Pattern "user", Pattern "message"]))
775 (Right (Object (HM.fromList [("user",String "bitemyapp"),("message", String "Use haskell!")])))
777 it "includes source patterns" $ withTestEnv $ do
779 (SourcePatterns (PopPattern (Pattern "*ge")))
780 (Right (Object (HM.fromList [("age", Number 10000),("message", String "Use haskell!")])))
782 it "excludes source patterns" $ withTestEnv $ do
784 (SourceIncludeExclude (Include []) (Exclude [Pattern "l*", Pattern "*ge", Pattern "postDate"]))
785 (Right (Object (HM.fromList [("user",String "bitemyapp")])))
787 describe "ToJSON RegexpFlags" $ do
788 it "generates the correct JSON for AllRegexpFlags" $
789 toJSON AllRegexpFlags `shouldBe` String "ALL"
791 it "generates the correct JSON for NoRegexpFlags" $
792 toJSON NoRegexpFlags `shouldBe` String "NONE"
794 it "generates the correct JSON for SomeRegexpFlags" $
795 let flags = AnyString :| [ Automaton
800 in toJSON (SomeRegexpFlags flags) `shouldBe` String "ANYSTRING|AUTOMATON|COMPLEMENT|EMPTY|INTERSECTION|INTERVAL"
802 prop "removes duplicates from flags" $ \(flags :: RegexpFlags) ->
803 let String str = toJSON flags
804 flagStrs = T.splitOn "|" str
805 in noDuplicates flagStrs
807 describe "omitNulls" $ do
808 it "checks that omitNulls drops list elements when it should" $
809 let dropped = omitNulls $ [ "test1" .= (toJSON ([] :: [Int]))
810 , "test2" .= (toJSON ("some value" :: Text))]
811 in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
813 it "checks that omitNulls doesn't drop list elements when it shouldn't" $
814 let notDropped = omitNulls $ [ "test1" .= (toJSON ([1] :: [Int]))
815 , "test2" .= (toJSON ("some value" :: Text))]
816 in notDropped `shouldBe` Object (HM.fromList [ ("test1", Array (V.fromList [Number 1.0]))
817 , ("test2", String "some value")])
818 it "checks that omitNulls drops non list elements when it should" $
819 let dropped = omitNulls $ [ "test1" .= (toJSON Null)
820 , "test2" .= (toJSON ("some value" :: Text))]
821 in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
822 it "checks that omitNulls doesn't drop non list elements when it shouldn't" $
823 let notDropped = omitNulls $ [ "test1" .= (toJSON (1 :: Int))
824 , "test2" .= (toJSON ("some value" :: Text))]
825 in notDropped `shouldBe` Object (HM.fromList [ ("test1", Number 1.0)
826 , ("test2", String "some value")])
827 describe "Monoid (SearchHits a)" $ do
828 prop "abides the monoid laws" $ eq $
829 prop_Monoid (T :: T (SearchHits ()))
831 describe "mkDocVersion" $ do
832 prop "can never construct an out of range docVersion" $ \i ->
833 let res = mkDocVersion i
835 Nothing -> property True
836 Just dv -> (dv >= minBound) .&&.
837 (dv <= maxBound) .&&.
838 docVersionNumber dv === i
840 describe "Enum DocVersion" $ do
841 it "follows the laws of Enum, Bounded" $ do
842 evaluate (succ maxBound :: DocVersion) `shouldThrow` anyErrorCall
843 evaluate (pred minBound :: DocVersion) `shouldThrow` anyErrorCall
844 evaluate (toEnum 0 :: DocVersion) `shouldThrow` anyErrorCall
845 evaluate (toEnum 9200000000000000001 :: DocVersion) `shouldThrow` anyErrorCall
846 enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
847 enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
848 enumFromThen minBound (pred maxBound :: DocVersion) `shouldBe` [minBound, pred maxBound]
850 describe "scan&scroll API" $ do
851 it "returns documents using the scan&scroll API" $ withTestEnv $ do
854 let search = (mkSearch (Just $ MatchAllQuery Nothing) Nothing) { size = (Size 1) }
855 regular_search <- searchTweet search
856 scan_search' <- scanSearch testIndex testMapping search :: BH IO [Hit Tweet]
857 let scan_search = map hitSource scan_search'
859 regular_search `shouldBe` Right exampleTweet -- Check that the size restrtiction is being honored
861 scan_search `shouldMatchList` [Just exampleTweet, Just otherTweet]
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