1{-# LANGUAGE DefaultSignatures #-}
2{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE OverloadedStrings #-}
6{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE TemplateHaskell #-}
8{-# LANGUAGE TypeOperators #-}
9{-# OPTIONS_GHC -fno-warn-orphans #-}
12import Control.Applicative
14import Control.Exception
16import Control.Monad.Reader
18import Data.Aeson.Types (parseEither)
19import qualified Data.ByteString.Lazy.Char8 as BL8
21import qualified Data.HashMap.Strict as HM
23import qualified Data.List as L
24import Data.List.NonEmpty (NonEmpty (..))
25import qualified Data.List.NonEmpty as NE
26import qualified Data.Map.Strict as M
29import Data.Text (Text)
30import qualified Data.Text as T
31import Data.Time.Calendar (Day (..), fromGregorian)
32import Data.Time.Clock (NominalDiffTime, UTCTime (..),
35import qualified Data.Vector as V
36import Database.Bloodhound
37import GHC.Generics as G
38import Network.HTTP.Client hiding (Proxy)
39import qualified Network.HTTP.Types.Status as NHTS
40import Prelude hiding (filter)
42import Test.QuickCheck.Property.Monoid (T (..), eq, prop_Monoid)
44import Test.Hspec.QuickCheck (prop)
48testServer = Server "http://localhost:9200"
50testIndex = IndexName "bloodhound-tests-twitter-1"
51testMapping :: MappingName
52testMapping = MappingName "tweet"
54withTestEnv :: BH IO a -> IO a
55withTestEnv = withBH defaultManagerSettings testServer
57validateStatus :: Response body -> Int -> Expectation
58validateStatus resp expected =
59 (NHTS.statusCode $ responseStatus resp)
60 `shouldBe` (expected :: Int)
62createExampleIndex :: BH IO Reply
63createExampleIndex = createIndex (IndexSettings (ShardCount 1) (ReplicaCount 0)) testIndex
64deleteExampleIndex :: BH IO Reply
65deleteExampleIndex = deleteIndex testIndex
67data ServerVersion = ServerVersion Int Int Int deriving (Show, Eq, Ord)
70es13 = ServerVersion 1 3 0
73es12 = ServerVersion 1 2 0
76es11 = ServerVersion 1 1 0
78serverBranch :: ServerVersion -> ServerVersion
79serverBranch (ServerVersion majorVer minorVer patchVer) =
80 ServerVersion majorVer minorVer patchVer
82mkServerVersion :: [Int] -> Maybe ServerVersion
83mkServerVersion [majorVer, minorVer, patchVer] =
84 Just (ServerVersion majorVer minorVer patchVer)
85mkServerVersion _ = Nothing
87getServerVersion :: IO (Maybe ServerVersion)
88getServerVersion = liftM extractVersion (withTestEnv getStatus)
90 version' = T.splitOn "." . number . version
91 toInt = read . T.unpack
92 parseVersion v = map toInt (version' v)
93 extractVersion = join . liftM (mkServerVersion . parseVersion)
95testServerBranch :: IO (Maybe ServerVersion)
96testServerBranch = getServerVersion >>= \v -> return $ liftM serverBranch v
98atleast :: ServerVersion -> IO Bool
99atleast v = testServerBranch >>= \x -> return $ x >= Just (serverBranch v)
101atmost :: ServerVersion -> IO Bool
102atmost v = testServerBranch >>= \x -> return $ x <= Just (serverBranch v)
104is :: ServerVersion -> IO Bool
105is v = testServerBranch >>= \x -> return $ x == Just (serverBranch v)
107when' :: Monad m => m Bool -> m () -> m ()
108when' b f = b >>= \x -> when x f
110(==~) :: (ApproxEq a, Show a) => a -> a -> Property
111a ==~ b = counterexample (show a ++ " !=~ " ++ show b) (a =~ b)
113propJSON :: forall a. (Arbitrary a, ToJSON a, FromJSON a, Show a, ApproxEq a, Typeable a) => Proxy a -> Spec
114propJSON _ = prop testName $ \(a :: a) ->
115 let jsonStr = "via " <> BL8.unpack (encode a)
116 in counterexample jsonStr (parseEither parseJSON (toJSON a) ==~ Right a)
117 where testName = show ty <> " FromJSON/ToJSON roundtrips"
118 ty = typeOf (undefined :: a)
120data Location = Location { lat :: Double
121 , lon :: Double } deriving (Eq, Generic, Show)
123data Tweet = Tweet { user :: Text
124 , postDate :: UTCTime
127 , location :: Location
128 , extra :: Maybe Text }
129 deriving (Eq, Generic, Show)
131instance ToJSON Tweet where
132 toJSON = genericToJSON defaultOptions
133instance FromJSON Tweet where
134 parseJSON = genericParseJSON defaultOptions
135instance ToJSON Location where
136 toJSON = genericToJSON defaultOptions
137instance FromJSON Location where
138 parseJSON = genericParseJSON defaultOptions
140data ParentMapping = ParentMapping deriving (Eq, Show)
142instance ToJSON ParentMapping where
143 toJSON ParentMapping =
144 object ["parent" .= Null ]
146data ChildMapping = ChildMapping deriving (Eq, Show)
148instance ToJSON ChildMapping where
149 toJSON ChildMapping =
151 object ["_parent" .= object ["type" .= ("parent" :: Text)]]
154data TweetMapping = TweetMapping deriving (Eq, Show)
156instance ToJSON TweetMapping where
157 toJSON TweetMapping =
159 object ["properties" .=
160 object [ "user" .= object ["type" .= ("string" :: Text)]
161 -- Serializing the date as a date is breaking other tests, mysteriously.
162 -- , "postDate" .= object [ "type" .= ("date" :: Text)
163 -- , "format" .= ("YYYY-MM-dd`T`HH:mm:ss.SSSZZ" :: Text)]
164 , "message" .= object ["type" .= ("string" :: Text)]
165 , "age" .= object ["type" .= ("integer" :: Text)]
166 , "location" .= object ["type" .= ("geo_point" :: Text)]
170exampleTweet = Tweet { user = "bitemyapp"
172 (ModifiedJulianDay 55000)
173 (secondsToDiffTime 10)
174 , message = "Use haskell!"
176 , location = Location 40.12 (-71.34)
179tweetWithExtra :: Tweet
180tweetWithExtra = Tweet { user = "bitemyapp"
182 (ModifiedJulianDay 55000)
183 (secondsToDiffTime 10)
184 , message = "Use haskell!"
186 , location = Location 40.12 (-71.34)
187 , extra = Just "blah blah" }
193newUser = "someotherapp"
197 object [ "age" .= newAge
202patchedTweet = exampleTweet{age = newAge, user = newUser}
205otherTweet = Tweet { user = "notmyapp"
207 (ModifiedJulianDay 55000)
208 (secondsToDiffTime 11)
209 , message = "Use haskell!"
211 , location = Location 40.12 (-71.34)
214resetIndex :: BH IO ()
216 _ <- deleteExampleIndex
217 _ <- createExampleIndex
218 _ <- putMapping testIndex testMapping TweetMapping
221insertData :: BH IO Reply
224 insertData' defaultIndexDocumentSettings
226insertData' :: IndexDocumentSettings -> BH IO Reply
228 r <- indexDocument testIndex testMapping ids exampleTweet (DocId "1")
229 _ <- refreshIndex testIndex
232updateData :: BH IO Reply
234 r <- updateDocument testIndex testMapping defaultIndexDocumentSettings tweetPatch (DocId "1")
235 _ <- refreshIndex testIndex
238insertOther :: BH IO ()
240 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings otherTweet (DocId "2")
241 _ <- refreshIndex testIndex
244insertExtra :: BH IO ()
246 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings tweetWithExtra (DocId "4")
247 _ <- refreshIndex testIndex
250insertWithSpaceInId :: BH IO ()
251insertWithSpaceInId = do
252 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "Hello World")
253 _ <- refreshIndex testIndex
256searchTweet :: Search -> BH IO (Either EsError Tweet)
257searchTweet search = do
258 result <- searchTweets search
259 let myTweet :: Either EsError Tweet
260 myTweet = grabFirst result
263searchTweets :: Search -> BH IO (Either EsError (SearchResult Tweet))
264searchTweets search = parseEsResponse =<< searchByIndex testIndex search
266searchExpectNoResults :: Search -> BH IO ()
267searchExpectNoResults search = do
268 result <- searchTweets search
269 let emptyHits = fmap (hits . searchHits) result
271 emptyHits `shouldBe` Right []
273searchExpectAggs :: Search -> BH IO ()
274searchExpectAggs search = do
275 reply <- searchByIndex testIndex search
276 let isEmpty x = return (M.null x)
277 let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
279 (result >>= aggregations >>= isEmpty) `shouldBe` Just False
281searchValidBucketAgg :: (BucketAggregation a, FromJSON a, Show a) =>
282 Search -> Text -> (Text -> AggregationResults -> Maybe (Bucket a)) -> BH IO ()
283searchValidBucketAgg search aggKey extractor = do
284 reply <- searchByIndex testIndex search
285 let bucketDocs = docCount . head . buckets
286 let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
287 let count = result >>= aggregations >>= extractor aggKey >>= \x -> return (bucketDocs x)
289 count `shouldBe` Just 1
291searchTermsAggHint :: [ExecutionHint] -> BH IO ()
292searchTermsAggHint hints = do
293 let terms hint = TermsAgg $ (mkTermsAggregation "user") { termExecutionHint = Just hint }
294 let search hint = mkAggregateSearch Nothing $ mkAggregations "users" $ terms hint
295 forM_ hints $ searchExpectAggs . search
296 forM_ hints (\x -> searchValidBucketAgg (search x) "users" toTerms)
298searchTweetHighlight :: Search -> BH IO (Either EsError (Maybe HitHighlight))
299searchTweetHighlight search = do
300 result <- searchTweets search
301 let myHighlight = fmap (hitHighlight . head . hits . searchHits) result
304searchExpectSource :: Source -> Either EsError Value -> BH IO ()
305searchExpectSource src expected = do
307 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
308 let search = (mkSearch (Just query) Nothing) { source = Just src }
309 reply <- searchByIndex testIndex search
310 result <- parseEsResponse reply
311 let value = grabFirst result
313 value `shouldBe` expected
315data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
316instance FromJSON BulkTest where
317 parseJSON = genericParseJSON defaultOptions
318instance ToJSON BulkTest where
319 toJSON = genericToJSON defaultOptions
321class GApproxEq f where
322 gApproxEq :: f a -> f a -> Bool
325instance GApproxEq U1 where
326 gApproxEq U1 U1 = True
328-- | Sum type, ensure same constructors, recurse
329instance (GApproxEq a, GApproxEq b) => GApproxEq (a :+: b) where
330 gApproxEq (L1 a) (L1 b) = gApproxEq a b
331 gApproxEq (R1 a) (R1 b) = gApproxEq a b
332 gApproxEq _ _ = False
334-- | Product type, ensure each field is approx eq
335instance (GApproxEq a, GApproxEq b) => GApproxEq (a :*: b) where
336 gApproxEq (a1 :*: b1) (a2 :*: b2) = gApproxEq a1 a2 && gApproxEq b1 b2
338-- | Value type, actually check the values for approx equality
339instance (ApproxEq a) => GApproxEq (K1 i a) where
340 gApproxEq (K1 a) (K1 b) = a =~ b
342instance (GApproxEq f) => GApproxEq (M1 i t f) where
343 gApproxEq (M1 a) (M1 b) = gApproxEq a b
345-- | Typeclass for "equal where it matters". Use this to specify
346-- less-strict equivalence for things such as lists that can wind up
347-- in an unpredictable order
348class ApproxEq a where
349 (=~) :: a -> a -> Bool
350 default (=~) :: (Generic a, GApproxEq (Rep a)) => a -> a -> Bool
351 a =~ b = gApproxEq (G.from a) (G.from b)
353instance ApproxEq NominalDiffTime where (=~) = (==)
354instance ApproxEq UTCTime where (=~) = (==)
355instance ApproxEq Text where (=~) = (==)
356instance ApproxEq Bool where (=~) = (==)
357instance ApproxEq Int where (=~) = (==)
358instance ApproxEq Double where (=~) = (==)
359instance ApproxEq a => ApproxEq (NonEmpty a)
360instance ApproxEq a => ApproxEq (Maybe a)
361instance ApproxEq GeoPoint
362instance ApproxEq Regexp
363instance ApproxEq RangeValue
364instance ApproxEq LessThan
365instance ApproxEq LessThanEq
366instance ApproxEq LessThanD
367instance ApproxEq LessThanEqD
368instance ApproxEq GreaterThan
369instance ApproxEq GreaterThanEq
370instance ApproxEq GreaterThanD
371instance ApproxEq GreaterThanEqD
372instance ApproxEq MinimumMatchHighLow
373instance ApproxEq RegexpFlag
374instance ApproxEq RegexpFlags
375instance ApproxEq NullValue
376instance ApproxEq Version
377instance ApproxEq DistanceRange
378instance ApproxEq IndexName
379instance ApproxEq MappingName
380instance ApproxEq DocId
381instance ApproxEq IndexAliasRouting
382instance ApproxEq RoutingValue
383instance ApproxEq ShardCount
384instance ApproxEq ReplicaCount
385instance ApproxEq TemplateName
386instance ApproxEq TemplatePattern
387instance ApproxEq QueryString
388instance ApproxEq FieldName
389instance ApproxEq CacheName
390instance ApproxEq CacheKey
391instance ApproxEq Existence
392instance ApproxEq CutoffFrequency
393instance ApproxEq Analyzer
394instance ApproxEq Lenient
395instance ApproxEq Tiebreaker
396instance ApproxEq Boost
397instance ApproxEq BoostTerms
398instance ApproxEq MaxExpansions
399instance ApproxEq MinimumMatch
400instance ApproxEq DisableCoord
401instance ApproxEq IgnoreTermFrequency
402instance ApproxEq MinimumTermFrequency
403instance ApproxEq MaxQueryTerms
404instance ApproxEq Fuzziness
405instance ApproxEq PrefixLength
406instance ApproxEq TypeName
407instance ApproxEq PercentMatch
408instance ApproxEq StopWord
409instance ApproxEq QueryPath
410instance ApproxEq AllowLeadingWildcard
411instance ApproxEq LowercaseExpanded
412instance ApproxEq EnablePositionIncrements
413instance ApproxEq AnalyzeWildcard
414instance ApproxEq GeneratePhraseQueries
415instance ApproxEq Locale
416instance ApproxEq MaxWordLength
417instance ApproxEq MinWordLength
418instance ApproxEq PhraseSlop
419instance ApproxEq MinDocFrequency
420instance ApproxEq MaxDocFrequency
421instance ApproxEq Filter
422instance ApproxEq Query
423instance ApproxEq SimpleQueryStringQuery
424instance ApproxEq FieldOrFields
425instance ApproxEq SimpleQueryFlag
426instance ApproxEq RegexpQuery
427instance ApproxEq QueryStringQuery
428instance ApproxEq RangeQuery
429instance ApproxEq PrefixQuery
430instance ApproxEq NestedQuery
431instance ApproxEq MoreLikeThisFieldQuery
432instance ApproxEq MoreLikeThisQuery
433instance ApproxEq IndicesQuery
434instance ApproxEq HasParentQuery
435instance ApproxEq HasChildQuery
436instance ApproxEq FuzzyQuery
437instance ApproxEq FuzzyLikeFieldQuery
438instance ApproxEq FuzzyLikeThisQuery
439instance ApproxEq FilteredQuery
440instance ApproxEq DisMaxQuery
441instance ApproxEq CommonTermsQuery
442instance ApproxEq CommonMinimumMatch
443instance ApproxEq BoostingQuery
444instance ApproxEq BoolQuery
445instance ApproxEq MatchQuery
446instance ApproxEq MultiMatchQueryType
447instance ApproxEq BooleanOperator
448instance ApproxEq ZeroTermsQuery
449instance ApproxEq MatchQueryType
450instance ApproxEq AliasRouting
451instance ApproxEq IndexAliasCreate
452instance ApproxEq SearchAliasRouting
453instance ApproxEq ScoreType
454instance ApproxEq Distance
455instance ApproxEq DistanceUnit
456instance ApproxEq DistanceType
457instance ApproxEq OptimizeBbox
458instance ApproxEq GeoBoundingBoxConstraint
459instance ApproxEq GeoFilterType
460instance ApproxEq GeoBoundingBox
461instance ApproxEq LatLon
462instance ApproxEq RangeExecution
463instance ApproxEq FSType
464instance ApproxEq CompoundFormat
465instance ApproxEq InitialShardCount
466instance ApproxEq Bytes
467instance ApproxEq ReplicaBounds
468instance ApproxEq Term
469instance ApproxEq BoolMatch
470instance ApproxEq MultiMatchQuery
471instance ApproxEq IndexSettings
472instance ApproxEq AllocationPolicy
473instance ApproxEq Char
474instance ApproxEq a => ApproxEq [a] where
475 as =~ bs = and (zipWith (=~) as bs)
476instance (ApproxEq l, ApproxEq r) => ApproxEq (Either l r) where
477 Left a =~ Left b = a =~ b
478 Right a =~ Right b = a =~ b
480instance ApproxEq NodeAttrFilter
481instance ApproxEq NodeAttrName
483-- | Due to the way nodeattrfilters get serialized here, they may come
484-- out in a different order, but they are morally equivalent
485instance ApproxEq UpdatableIndexSetting where
486 RoutingAllocationInclude a =~ RoutingAllocationInclude b =
487 NE.sort a =~ NE.sort b
488 RoutingAllocationExclude a =~ RoutingAllocationExclude b =
489 NE.sort a =~ NE.sort b
490 RoutingAllocationRequire a =~ RoutingAllocationRequire b =
491 NE.sort a =~ NE.sort b
495noDuplicates :: Eq a => [a] -> Bool
496noDuplicates xs = nub xs == xs
498instance Arbitrary NominalDiffTime where
499 arbitrary = fromInteger <$> arbitrary
501instance (Arbitrary k, Ord k, Arbitrary v) => Arbitrary (M.Map k v) where
502 arbitrary = M.fromList <$> arbitrary
504instance Arbitrary Text where
505 arbitrary = T.pack <$> arbitrary
507instance Arbitrary UTCTime where
510 <*> (fromRational . toRational <$> choose (0::Double, 86400))
512instance Arbitrary Day where
513 arbitrary = ModifiedJulianDay <$> (2000 +) <$> arbitrary
514 shrink = (ModifiedJulianDay <$>) . shrink . toModifiedJulianDay
516instance Arbitrary a => Arbitrary (NonEmpty a) where
517 arbitrary = liftA2 (:|) arbitrary arbitrary
519arbitraryScore :: Gen Score
520arbitraryScore = fmap getPositive <$> arbitrary
522instance (Arbitrary a, Typeable a) => Arbitrary (Hit a) where
523 arbitrary = Hit <$> arbitrary
529 shrink = genericShrink
532instance (Arbitrary a, Typeable a) => Arbitrary (SearchHits a) where
533 arbitrary = reduceSize $ do
534 tot <- getPositive <$> arbitrary
535 score <- arbitraryScore
537 return $ SearchHits tot score hs
538 shrink = genericShrink
540reduceSize :: Gen a -> Gen a
541reduceSize f = sized $ \n -> resize (n `div` 2) f
543getSource :: EsResult a -> Maybe a
544getSource = fmap _source . foundResult
546grabFirst :: Either EsError (SearchResult a) -> Either EsError a
548 case fmap (hitSource . head . hits . searchHits) r of
550 (Right Nothing) -> Left (EsError 500 "Source was missing")
551 (Right (Just x)) -> Right x
553-------------------------------------------------------------------------------
554arbitraryAlphaNum :: Gen Char
555arbitraryAlphaNum = oneof [choose ('a', 'z')
559instance Arbitrary RoutingValue where
560 arbitrary = RoutingValue . T.pack <$> listOf1 arbitraryAlphaNum
562instance Arbitrary AliasRouting where
563 arbitrary = oneof [allAlias
567 where one = GranularAliasRouting
568 <$> (Just <$> arbitrary)
570 theOther = GranularAliasRouting Nothing
571 <$> (Just <$> arbitrary)
572 both = GranularAliasRouting
573 <$> (Just <$> arbitrary)
574 <*> (Just <$> arbitrary)
575 allAlias = AllAliasRouting <$> arbitrary
576 shrink = genericShrink
579instance Arbitrary FieldName where
580 arbitrary = FieldName . T.pack <$> listOf1 arbitraryAlphaNum
581 shrink = genericShrink
584instance Arbitrary RegexpFlags where
585 arbitrary = oneof [ pure AllRegexpFlags
587 , SomeRegexpFlags <$> genUniqueFlags
589 where genUniqueFlags = NE.fromList . nub <$> listOf1 arbitrary
590 shrink = genericShrink
593instance Arbitrary IndexAliasCreate where
594 arbitrary = IndexAliasCreate <$> arbitrary <*> reduceSize arbitrary
595 shrink = genericShrink
597instance Arbitrary Query where
598 arbitrary = reduceSize $ oneof [ TermQuery <$> arbitrary <*> arbitrary
599 , TermsQuery <$> arbitrary <*> arbitrary
600 , QueryMatchQuery <$> arbitrary
601 , QueryMultiMatchQuery <$> arbitrary
602 , QueryBoolQuery <$> arbitrary
603 , QueryBoostingQuery <$> arbitrary
604 , QueryCommonTermsQuery <$> arbitrary
605 , ConstantScoreFilter <$> arbitrary <*> arbitrary
606 , ConstantScoreQuery <$> arbitrary <*> arbitrary
607 , QueryDisMaxQuery <$> arbitrary
608 , QueryFilteredQuery <$> arbitrary
609 , QueryFuzzyLikeThisQuery <$> arbitrary
610 , QueryFuzzyLikeFieldQuery <$> arbitrary
611 , QueryFuzzyQuery <$> arbitrary
612 , QueryHasChildQuery <$> arbitrary
613 , QueryHasParentQuery <$> arbitrary
614 , IdsQuery <$> arbitrary <*> arbitrary
615 , QueryIndicesQuery <$> arbitrary
616 , MatchAllQuery <$> arbitrary
617 , QueryMoreLikeThisQuery <$> arbitrary
618 , QueryMoreLikeThisFieldQuery <$> arbitrary
619 , QueryNestedQuery <$> arbitrary
620 , QueryPrefixQuery <$> arbitrary
621 , QueryQueryStringQuery <$> arbitrary
622 , QuerySimpleQueryStringQuery <$> arbitrary
623 , QueryRangeQuery <$> arbitrary
624 , QueryRegexpQuery <$> arbitrary
626 shrink = genericShrink
628instance Arbitrary Filter where
629 arbitrary = reduceSize $ oneof [ AndFilter <$> arbitrary <*> arbitrary
630 , OrFilter <$> arbitrary <*> arbitrary
631 , NotFilter <$> arbitrary <*> arbitrary
632 , pure IdentityFilter
633 , BoolFilter <$> arbitrary
634 , ExistsFilter <$> arbitrary
635 , GeoBoundingBoxFilter <$> arbitrary
636 , GeoDistanceFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
637 , GeoDistanceRangeFilter <$> arbitrary <*> arbitrary
638 , GeoPolygonFilter <$> arbitrary <*> arbitrary
639 , IdsFilter <$> arbitrary <*> arbitrary
640 , LimitFilter <$> arbitrary
641 , MissingFilter <$> arbitrary <*> arbitrary <*> arbitrary
642 , PrefixFilter <$> arbitrary <*> arbitrary <*> arbitrary
643 , QueryFilter <$> arbitrary <*> arbitrary
644 , RangeFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
645 , RegexpFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
646 , TermFilter <$> arbitrary <*> arbitrary]
647 shrink = genericShrink
649instance Arbitrary ReplicaBounds where
650 arbitrary = oneof [ replicasBounded
651 , replicasLowerBounded
652 , pure ReplicasUnbounded
654 where replicasBounded = do Positive a <- arbitrary
655 Positive b <- arbitrary
656 return (ReplicasBounded a b)
657 replicasLowerBounded = do Positive a <- arbitrary
658 return (ReplicasLowerBounded a)
660instance Arbitrary NodeAttrName where
661 arbitrary = NodeAttrName . T.pack . getNonEmpty <$> arbitrary
664instance Arbitrary NodeAttrFilter where
667 s:ss <- listOf1 (listOf1 arbitraryAlphaNum)
668 let ts = T.pack <$> s :| ss
669 return (NodeAttrFilter n ts)
671$(derive makeArbitrary ''IndexName)
672$(derive makeArbitrary ''MappingName)
673$(derive makeArbitrary ''DocId)
674$(derive makeArbitrary ''Version)
675$(derive makeArbitrary ''IndexAliasRouting)
676$(derive makeArbitrary ''ShardCount)
677$(derive makeArbitrary ''ReplicaCount)
678$(derive makeArbitrary ''TemplateName)
679$(derive makeArbitrary ''TemplatePattern)
680$(derive makeArbitrary ''QueryString)
681$(derive makeArbitrary ''CacheName)
682$(derive makeArbitrary ''CacheKey)
683$(derive makeArbitrary ''Existence)
684$(derive makeArbitrary ''CutoffFrequency)
685$(derive makeArbitrary ''Analyzer)
686$(derive makeArbitrary ''MaxExpansions)
687$(derive makeArbitrary ''Lenient)
688$(derive makeArbitrary ''Tiebreaker)
689$(derive makeArbitrary ''Boost)
690$(derive makeArbitrary ''BoostTerms)
691$(derive makeArbitrary ''MinimumMatch)
692$(derive makeArbitrary ''DisableCoord)
693$(derive makeArbitrary ''IgnoreTermFrequency)
694$(derive makeArbitrary ''MinimumTermFrequency)
695$(derive makeArbitrary ''MaxQueryTerms)
696$(derive makeArbitrary ''Fuzziness)
697$(derive makeArbitrary ''PrefixLength)
698$(derive makeArbitrary ''TypeName)
699$(derive makeArbitrary ''PercentMatch)
700$(derive makeArbitrary ''StopWord)
701$(derive makeArbitrary ''QueryPath)
702$(derive makeArbitrary ''AllowLeadingWildcard)
703$(derive makeArbitrary ''LowercaseExpanded)
704$(derive makeArbitrary ''EnablePositionIncrements)
705$(derive makeArbitrary ''AnalyzeWildcard)
706$(derive makeArbitrary ''GeneratePhraseQueries)
707$(derive makeArbitrary ''Locale)
708$(derive makeArbitrary ''MaxWordLength)
709$(derive makeArbitrary ''MinWordLength)
710$(derive makeArbitrary ''PhraseSlop)
711$(derive makeArbitrary ''MinDocFrequency)
712$(derive makeArbitrary ''MaxDocFrequency)
713$(derive makeArbitrary ''Regexp)
714$(derive makeArbitrary ''SimpleQueryStringQuery)
715$(derive makeArbitrary ''FieldOrFields)
716$(derive makeArbitrary ''SimpleQueryFlag)
717$(derive makeArbitrary ''RegexpQuery)
718$(derive makeArbitrary ''QueryStringQuery)
719$(derive makeArbitrary ''RangeQuery)
720$(derive makeArbitrary ''RangeValue)
721$(derive makeArbitrary ''PrefixQuery)
722$(derive makeArbitrary ''NestedQuery)
723$(derive makeArbitrary ''MoreLikeThisFieldQuery)
724$(derive makeArbitrary ''MoreLikeThisQuery)
725$(derive makeArbitrary ''IndicesQuery)
726$(derive makeArbitrary ''HasParentQuery)
727$(derive makeArbitrary ''HasChildQuery)
728$(derive makeArbitrary ''FuzzyQuery)
729$(derive makeArbitrary ''FuzzyLikeFieldQuery)
730$(derive makeArbitrary ''FuzzyLikeThisQuery)
731$(derive makeArbitrary ''FilteredQuery)
732$(derive makeArbitrary ''DisMaxQuery)
733$(derive makeArbitrary ''CommonTermsQuery)
734$(derive makeArbitrary ''DistanceRange)
735$(derive makeArbitrary ''MultiMatchQuery)
736$(derive makeArbitrary ''LessThanD)
737$(derive makeArbitrary ''LessThanEqD)
738$(derive makeArbitrary ''GreaterThanD)
739$(derive makeArbitrary ''GreaterThanEqD)
740$(derive makeArbitrary ''LessThan)
741$(derive makeArbitrary ''LessThanEq)
742$(derive makeArbitrary ''GreaterThan)
743$(derive makeArbitrary ''GreaterThanEq)
744$(derive makeArbitrary ''GeoPoint)
745$(derive makeArbitrary ''NullValue)
746$(derive makeArbitrary ''MinimumMatchHighLow)
747$(derive makeArbitrary ''CommonMinimumMatch)
748$(derive makeArbitrary ''BoostingQuery)
749$(derive makeArbitrary ''BoolQuery)
750$(derive makeArbitrary ''MatchQuery)
751$(derive makeArbitrary ''MultiMatchQueryType)
752$(derive makeArbitrary ''BooleanOperator)
753$(derive makeArbitrary ''ZeroTermsQuery)
754$(derive makeArbitrary ''MatchQueryType)
755$(derive makeArbitrary ''SearchAliasRouting)
756$(derive makeArbitrary ''ScoreType)
757$(derive makeArbitrary ''Distance)
758$(derive makeArbitrary ''DistanceUnit)
759$(derive makeArbitrary ''DistanceType)
760$(derive makeArbitrary ''OptimizeBbox)
761$(derive makeArbitrary ''GeoBoundingBoxConstraint)
762$(derive makeArbitrary ''GeoFilterType)
763$(derive makeArbitrary ''GeoBoundingBox)
764$(derive makeArbitrary ''LatLon)
765$(derive makeArbitrary ''RangeExecution)
766$(derive makeArbitrary ''RegexpFlag)
767$(derive makeArbitrary ''BoolMatch)
768$(derive makeArbitrary ''Term)
769$(derive makeArbitrary ''IndexSettings)
770$(derive makeArbitrary ''UpdatableIndexSetting)
771$(derive makeArbitrary ''Bytes)
772$(derive makeArbitrary ''AllocationPolicy)
773$(derive makeArbitrary ''InitialShardCount)
774$(derive makeArbitrary ''FSType)
775$(derive makeArbitrary ''CompoundFormat)
781 describe "index create/delete API" $ do
782 it "creates and then deletes the requested index" $ withTestEnv $ do
784 _ <- deleteExampleIndex
785 resp <- createExampleIndex
786 deleteResp <- deleteExampleIndex
788 validateStatus resp 200
789 validateStatus deleteResp 200
791 describe "error parsing" $ do
792 it "can parse EsErrors" $ withTestEnv $ do
793 res <- getDocument (IndexName "bogus") (MappingName "also_bogus") (DocId "bogus_as_well")
794 let errorResp = eitherDecode (responseBody res)
795 liftIO (errorResp `shouldBe` Right (EsError 404 "IndexMissingException[[bogus] missing]"))
797 describe "document API" $ do
798 it "indexes, updates, gets, and then deletes the generated document" $ withTestEnv $ do
801 docInserted <- getDocument testIndex testMapping (DocId "1")
802 let newTweet = eitherDecode
803 (responseBody docInserted) :: Either String (EsResult Tweet)
804 liftIO $ (fmap getSource newTweet `shouldBe` Right (Just patchedTweet))
806 it "indexes, gets, and then deletes the generated document with a DocId containing a space" $ withTestEnv $ do
807 _ <- insertWithSpaceInId
808 docInserted <- getDocument testIndex testMapping (DocId "Hello World")
809 let newTweet = eitherDecode
810 (responseBody docInserted) :: Either String (EsResult Tweet)
811 liftIO $ (fmap getSource newTweet `shouldBe` Right (Just exampleTweet))
813 it "produces a parseable result when looking up a bogus document" $ withTestEnv $ do
814 doc <- getDocument testIndex testMapping (DocId "bogus")
815 let noTweet = eitherDecode
816 (responseBody doc) :: Either String (EsResult Tweet)
817 liftIO $ fmap foundResult noTweet `shouldBe` Right Nothing
819 it "can use optimistic concurrency control" $ withTestEnv $ do
820 let ev = ExternalDocVersion minBound
821 let cfg = defaultIndexDocumentSettings { idsVersionControl = ExternalGT ev }
823 res <- insertData' cfg
824 liftIO $ isCreated res `shouldBe` True
825 res' <- insertData' cfg
826 liftIO $ isVersionConflict res' `shouldBe` True
828 it "indexes two documents in a parent/child relationship and checks that the child exists" $ withTestEnv $ do
830 _ <- putMapping testIndex (MappingName "parent") ParentMapping
831 _ <- putMapping testIndex (MappingName "child") ChildMapping
832 _ <- indexDocument testIndex (MappingName "parent") defaultIndexDocumentSettings exampleTweet (DocId "1")
833 let parent = (Just . DocumentParent . DocId) "1"
834 ids = IndexDocumentSettings NoVersionControl parent
835 _ <- indexDocument testIndex (MappingName "child") ids otherTweet (DocId "2")
836 _ <- refreshIndex testIndex
837 exists <- documentExists testIndex (MappingName "child") parent (DocId "2")
838 liftIO $ exists `shouldBe` True
840 describe "template API" $ do
841 it "can create a template" $ withTestEnv $ do
842 let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
843 resp <- putTemplate idxTpl (TemplateName "tweet-tpl")
844 liftIO $ validateStatus resp 200
846 it "can detect if a template exists" $ withTestEnv $ do
847 exists <- templateExists (TemplateName "tweet-tpl")
848 liftIO $ exists `shouldBe` True
850 it "can delete a template" $ withTestEnv $ do
851 resp <- deleteTemplate (TemplateName "tweet-tpl")
852 liftIO $ validateStatus resp 200
854 it "can detect if a template doesn't exist" $ withTestEnv $ do
855 exists <- templateExists (TemplateName "tweet-tpl")
856 liftIO $ exists `shouldBe` False
858 describe "bulk API" $ do
859 it "inserts all documents we request" $ withTestEnv $ do
861 let firstTest = BulkTest "blah"
862 let secondTest = BulkTest "bloo"
863 let firstDoc = BulkIndex testIndex
864 testMapping (DocId "2") (toJSON firstTest)
865 let secondDoc = BulkCreate testIndex
866 testMapping (DocId "3") (toJSON secondTest)
867 let stream = V.fromList [firstDoc, secondDoc]
869 _ <- refreshIndex testIndex
870 fDoc <- getDocument testIndex testMapping (DocId "2")
871 sDoc <- getDocument testIndex testMapping (DocId "3")
872 let maybeFirst = eitherDecode $ responseBody fDoc :: Either String (EsResult BulkTest)
873 let maybeSecond = eitherDecode $ responseBody sDoc :: Either String (EsResult BulkTest)
875 fmap getSource maybeFirst `shouldBe` Right (Just firstTest)
876 fmap getSource maybeSecond `shouldBe` Right (Just secondTest)
879 describe "query API" $ do
880 it "returns document for term query and identity filter" $ withTestEnv $ do
882 let query = TermQuery (Term "user" "bitemyapp") Nothing
883 let filter = IdentityFilter <&&> IdentityFilter
884 let search = mkSearch (Just query) (Just filter)
885 myTweet <- searchTweet search
887 myTweet `shouldBe` Right exampleTweet
889 it "handles constant score queries" $ withTestEnv $ do
891 let query = TermsQuery "user" ("bitemyapp" :| [])
892 let cfQuery = ConstantScoreQuery query (Boost 1.0)
893 let filter = IdentityFilter
894 let search = mkSearch (Just cfQuery) (Just filter)
895 myTweet <- searchTweet search
897 myTweet `shouldBe` Right exampleTweet
898 it "handles constant score filters" $ withTestEnv $ do
900 let query = TermsQuery "user" ("bitemyapp" :| [])
901 let cfFilter = ConstantScoreFilter IdentityFilter (Boost 1.0)
902 let boolQuery = mkBoolQuery [query, cfFilter] [] []
903 let search = mkSearch (Just (QueryBoolQuery boolQuery)) Nothing
904 myTweet <- searchTweet search
906 myTweet `shouldBe` Right exampleTweet
909 it "returns document for terms query and identity filter" $ withTestEnv $ do
911 let query = TermsQuery "user" ("bitemyapp" :| [])
912 let filter = IdentityFilter <&&> IdentityFilter
913 let search = mkSearch (Just query) (Just filter)
914 myTweet <- searchTweet search
916 myTweet `shouldBe` Right exampleTweet
918 it "returns document for match query" $ withTestEnv $ do
920 let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
921 let search = mkSearch (Just query) Nothing
922 myTweet <- searchTweet search
924 myTweet `shouldBe` Right exampleTweet
926 it "returns document for multi-match query" $ withTestEnv $ do
928 let flds = [FieldName "user", FieldName "message"]
929 let query = QueryMultiMatchQuery $ mkMultiMatchQuery flds (QueryString "bitemyapp")
930 let search = mkSearch (Just query) Nothing
931 myTweet <- searchTweet search
933 myTweet `shouldBe` Right exampleTweet
935 it "returns document for bool query" $ withTestEnv $ do
937 let innerQuery = QueryMatchQuery $
938 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
939 let query = QueryBoolQuery $
940 mkBoolQuery [innerQuery] [] []
941 let search = mkSearch (Just query) Nothing
942 myTweet <- searchTweet search
944 myTweet `shouldBe` Right exampleTweet
946 it "returns document for boosting query" $ withTestEnv $ do
948 let posQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
949 let negQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "notmyapp")
950 let query = QueryBoostingQuery $ BoostingQuery posQuery negQuery (Boost 0.2)
951 let search = mkSearch (Just query) Nothing
952 myTweet <- searchTweet search
954 myTweet `shouldBe` Right exampleTweet
956 it "returns document for common terms query" $ withTestEnv $ do
958 let query = QueryCommonTermsQuery $
959 CommonTermsQuery (FieldName "user")
960 (QueryString "bitemyapp")
961 (CutoffFrequency 0.0001)
962 Or Or Nothing Nothing Nothing Nothing
963 let search = mkSearch (Just query) Nothing
964 myTweet <- searchTweet search
966 myTweet `shouldBe` Right exampleTweet
969 describe "sorting" $ do
970 it "returns documents in the right order" $ withTestEnv $ do
973 let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
974 let search = Search Nothing
975 (Just IdentityFilter) (Just [sortSpec]) Nothing Nothing
976 False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
977 result <- searchTweets search
978 let myTweet = grabFirst result
980 myTweet `shouldBe` Right otherTweet
983 describe "filtering API" $ do
984 it "returns document for composed boolmatch and identity" $ withTestEnv $ do
986 let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
988 let search = mkSearch Nothing (Just queryFilter)
989 myTweet <- searchTweet search
991 myTweet `shouldBe` Right exampleTweet
993 it "returns document for term filter" $ withTestEnv $ do
995 let termFilter = TermFilter (Term "user" "bitemyapp") False
996 let search = mkSearch Nothing (Just termFilter)
997 myTweet <- searchTweet search
999 myTweet `shouldBe` Right exampleTweet
1001 it "returns document for existential filter" $ withTestEnv $ do
1003 let search = mkSearch Nothing (Just (ExistsFilter (FieldName "user")))
1004 myTweet <- searchTweet search
1006 myTweet `shouldBe` Right exampleTweet
1008 it "returns document for geo boundingbox filter" $ withTestEnv $ do
1010 let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
1011 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
1012 let geoFilter = GeoBoundingBoxFilter bbConstraint
1013 let search = mkSearch Nothing (Just geoFilter)
1014 myTweet <- searchTweet search
1016 myTweet `shouldBe` Right exampleTweet
1018 it "doesn't return document for nonsensical boundingbox filter" $ withTestEnv $ do
1020 let box = GeoBoundingBox (LatLon 0.73 (-4.1)) (LatLon 0.10 (-1.12))
1021 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
1022 let geoFilter = GeoBoundingBoxFilter bbConstraint
1023 let search = mkSearch Nothing (Just geoFilter)
1024 searchExpectNoResults search
1026 it "returns document for geo distance filter" $ withTestEnv $ do
1028 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1029 let distance = Distance 10.0 Miles
1030 let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
1031 let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
1032 let search = mkSearch Nothing (Just geoFilter)
1033 myTweet <- searchTweet search
1035 myTweet `shouldBe` Right exampleTweet
1037 it "returns document for geo distance range filter" $ withTestEnv $ do
1039 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1040 let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
1041 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
1042 let search = mkSearch Nothing (Just geoFilter)
1043 myTweet <- searchTweet search
1045 myTweet `shouldBe` Right exampleTweet
1047 it "doesn't return document for wild geo distance range filter" $ withTestEnv $ do
1049 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1050 let distanceRange = DistanceRange (Distance 100.0 Miles) (Distance 1000.0 Miles)
1051 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
1052 let search = mkSearch Nothing (Just geoFilter)
1053 searchExpectNoResults search
1055 it "returns document for geo polygon filter" $ withTestEnv $ do
1057 let points = [LatLon 40.0 (-70.00),
1058 LatLon 40.0 (-72.00),
1059 LatLon 41.0 (-70.00),
1060 LatLon 41.0 (-72.00)]
1061 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
1062 let search = mkSearch Nothing (Just geoFilter)
1063 myTweet <- searchTweet search
1065 myTweet `shouldBe` Right exampleTweet
1067 it "doesn't return document for bad geo polygon filter" $ withTestEnv $ do
1069 let points = [LatLon 40.0 (-70.00),
1070 LatLon 40.0 (-71.00),
1071 LatLon 41.0 (-70.00),
1072 LatLon 41.0 (-71.00)]
1073 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
1074 let search = mkSearch Nothing (Just geoFilter)
1075 searchExpectNoResults search
1077 it "returns document for ids filter" $ withTestEnv $ do
1079 let filter = IdsFilter (MappingName "tweet") [DocId "1"]
1080 let search = mkSearch Nothing (Just filter)
1081 myTweet <- searchTweet search
1083 myTweet `shouldBe` Right exampleTweet
1085 it "returns document for Double range filter" $ withTestEnv $ do
1087 let filter = RangeFilter (FieldName "age")
1088 (RangeDoubleGtLt (GreaterThan 1000.0) (LessThan 100000.0))
1089 RangeExecutionIndex False
1090 let search = mkSearch Nothing (Just filter)
1091 myTweet <- searchTweet search
1093 myTweet `shouldBe` Right exampleTweet
1095 it "returns document for UTCTime date filter" $ withTestEnv $ do
1097 let filter = RangeFilter (FieldName "postDate")
1099 (GreaterThanD (UTCTime
1100 (ModifiedJulianDay 54000)
1101 (secondsToDiffTime 0)))
1103 (ModifiedJulianDay 55000)
1104 (secondsToDiffTime 11))))
1105 RangeExecutionIndex False
1106 let search = mkSearch Nothing (Just filter)
1107 myTweet <- searchTweet search
1109 myTweet `shouldBe` Right exampleTweet
1111 it "returns document for regexp filter" $ withTestEnv $ do
1113 let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
1114 AllRegexpFlags (CacheName "test") False (CacheKey "key")
1115 let search = mkSearch Nothing (Just filter)
1116 myTweet <- searchTweet search
1118 myTweet `shouldBe` Right exampleTweet
1120 it "doesn't return document for non-matching regexp filter" $ withTestEnv $ do
1122 let filter = RegexpFilter (FieldName "user")
1123 (Regexp "boy") AllRegexpFlags
1124 (CacheName "test") False (CacheKey "key")
1125 let search = mkSearch Nothing (Just filter)
1126 searchExpectNoResults search
1128 it "returns document for query filter, uncached" $ withTestEnv $ do
1130 let filter = QueryFilter (TermQuery (Term "user" "bitemyapp") Nothing) True
1131 search = mkSearch Nothing (Just filter)
1132 myTweet <- searchTweet search
1133 liftIO $ myTweet `shouldBe` Right exampleTweet
1135 it "returns document for query filter, cached" $ withTestEnv $ do
1137 let filter = QueryFilter (TermQuery (Term "user" "bitemyapp") Nothing) False
1138 search = mkSearch Nothing (Just filter)
1139 myTweet <- searchTweet search
1140 liftIO $ myTweet `shouldBe` Right exampleTweet
1142 describe "Aggregation API" $ do
1143 it "returns term aggregation results" $ withTestEnv $ do
1145 let terms = TermsAgg $ mkTermsAggregation "user"
1146 let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
1147 searchExpectAggs search
1148 searchValidBucketAgg search "users" toTerms
1150 it "can give collection hint parameters to term aggregations" $ when' (atleast es13) $ withTestEnv $ do
1152 let terms = TermsAgg $ (mkTermsAggregation "user") { termCollectMode = Just BreadthFirst }
1153 let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
1154 searchExpectAggs search
1155 searchValidBucketAgg search "users" toTerms
1157 it "can give execution hint parameters to term aggregations" $ when' (atmost es11) $ withTestEnv $ do
1159 searchTermsAggHint [Map, Ordinals]
1161 it "can give execution hint parameters to term aggregations" $ when' (is es12) $ withTestEnv $ do
1163 searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map, Ordinals]
1165 it "can give execution hint parameters to term aggregations" $ when' (atleast es12) $ withTestEnv $ do
1167 searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map]
1170 it "can execute value_count aggregations" $ withTestEnv $ do
1173 let ags = mkAggregations "user_count" (ValueCountAgg (FieldValueCount (FieldName "user"))) <>
1174 mkAggregations "bogus_count" (ValueCountAgg (FieldValueCount (FieldName "bogus")))
1175 let search = mkAggregateSearch Nothing ags
1176 let docCountPair k n = (k, object ["value" .= Number n])
1177 res <- searchTweets search
1179 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "user_count" 2
1180 , docCountPair "bogus_count" 0
1183 it "can execute filter aggregations" $ withTestEnv $ do
1186 let ags = mkAggregations "bitemyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "bitemyapp") defaultCache) Nothing)) <>
1187 mkAggregations "notmyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "notmyapp") defaultCache) Nothing))
1188 let search = mkAggregateSearch Nothing ags
1189 let docCountPair k n = (k, object ["doc_count" .= Number n])
1190 res <- searchTweets search
1192 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "bitemyapps" 1
1193 , docCountPair "notmyapps" 1
1195 it "can execute date_range aggregations" $ withTestEnv $ do
1196 let now = fromGregorian 2015 3 14
1197 let ltAMonthAgo = UTCTime (fromGregorian 2015 3 1) 0
1198 let ltAWeekAgo = UTCTime (fromGregorian 2015 3 10) 0
1199 let oldDoc = exampleTweet { postDate = ltAMonthAgo }
1200 let newDoc = exampleTweet { postDate = ltAWeekAgo }
1201 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings oldDoc (DocId "1")
1202 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings newDoc (DocId "2")
1203 _ <- refreshIndex testIndex
1204 let thisMonth = DateRangeFrom (DateMathExpr (DMDate now) [SubtractTime 1 DMMonth])
1205 let thisWeek = DateRangeFrom (DateMathExpr (DMDate now) [SubtractTime 1 DMWeek])
1206 let agg = DateRangeAggregation (FieldName "postDate") Nothing (thisMonth :| [thisWeek])
1207 let ags = mkAggregations "date_ranges" (DateRangeAgg agg)
1208 let search = mkAggregateSearch Nothing ags
1209 res <- searchTweets search
1210 liftIO $ hitsTotal . searchHits <$> res `shouldBe` Right 2
1211 let bucks = do magrs <- fmapL show (aggregations <$> res)
1212 agrs <- note "no aggregations returned" magrs
1213 rawBucks <- note "no date_ranges aggregation" $ M.lookup "date_ranges" agrs
1214 parseEither parseJSON rawBucks
1215 let fromMonthT = UTCTime (fromGregorian 2015 2 14) 0
1216 let fromWeekT = UTCTime (fromGregorian 2015 3 7) 0
1217 liftIO $ buckets <$> bucks `shouldBe` Right [ DateRangeResult "2015-02-14T00:00:00.000Z-*"
1219 (Just "2015-02-14T00:00:00.000Z")
1224 , DateRangeResult "2015-03-07T00:00:00.000Z-*"
1226 (Just "2015-03-07T00:00:00.000Z")
1233 it "returns date histogram aggregation results" $ withTestEnv $ do
1235 let histogram = DateHistogramAgg $ mkDateHistogram (FieldName "postDate") Minute
1236 let search = mkAggregateSearch Nothing (mkAggregations "byDate" histogram)
1237 searchExpectAggs search
1238 searchValidBucketAgg search "byDate" toDateHistogram
1240 it "returns date histogram using fractional date" $ withTestEnv $ do
1242 let periods = [Year, Quarter, Month, Week, Day, Hour, Minute, Second]
1243 let fractionals = map (FractionalInterval 1.5) [Weeks, Days, Hours, Minutes, Seconds]
1244 let intervals = periods ++ fractionals
1245 let histogram = mkDateHistogram (FieldName "postDate")
1246 let search interval = mkAggregateSearch Nothing $ mkAggregations "byDate" $ DateHistogramAgg (histogram interval)
1247 let expect interval = searchExpectAggs (search interval)
1248 let valid interval = searchValidBucketAgg (search interval) "byDate" toDateHistogram
1249 forM_ intervals expect
1250 forM_ intervals valid
1252 it "can execute missing aggregations" $ withTestEnv $ do
1255 let ags = mkAggregations "missing_agg" (MissingAgg (MissingAggregation "extra"))
1256 let search = mkAggregateSearch Nothing ags
1257 let docCountPair k n = (k, object ["doc_count" .= Number n])
1258 res <- searchTweets search
1260 fmap aggregations res `shouldBe` Right (Just (M.fromList [docCountPair "missing_agg" 1]))
1262 it "can parse sub aggregations" $ withTestEnv $ do
1265 let l2 = mkAggregations "level2" (TermsAgg (mkTermsAggregation "user"))
1266 let l1 = mkAggregations "level1" (TermsAgg ((mkTermsAggregation "age") { termAggs = Just l2 }))
1267 let search = mkAggregateSearch Nothing l1
1268 eres <- fmapL show <$> searchTweets search
1269 let finalResult = do
1271 actualAggs <- note "no agg results returned" (aggregations res)
1272 aggResults <- note "could not parse level1" (toTerms "level1" actualAggs)
1273 let bucketsByTermKey = [(termKey tr, tr) | tr <- buckets aggResults]
1275 nmaResult <- note "could not find notmyapp bucket" (lookup (TextValue "notmyapp") bucketsByTermKey)
1276 nmaResultAgg <- note "notmyapp result had no aggs" (termsAggs nmaResult)
1277 nmaBuckets <- note "could not parse notmyapp level2" (toTerms "level2" nmaResultAgg)
1278 nmaBucket <- note "expected 1 notmyapp bucket" (headMay (buckets nmaBuckets))
1280 bmaResult <- note "could not find bitemyapp bucket" (lookup (TextValue "bitemyapp") bucketsByTermKey)
1281 bmaResultAgg <- note "bitemyapp result had no aggs" (termsAggs bmaResult)
1282 bmaBuckets <- note "could not parse bitemyapp level2" (toTerms "level2" bmaResultAgg)
1283 bmaBucket <- note "expected 1 bitemyapp bucket" (headMay (buckets bmaBuckets))
1284 return (nmaResult, nmaBucket, bmaResult, bmaBucket)
1285 liftIO $ case finalResult of
1286 Left e -> expectationFailure e
1287 Right (nmaResult, nmaBucket, bmaResult, bmaBucket) -> do
1288 let nmaBucketExpected = TermsResult {
1289 termKey = TextValue "notmyapp"
1291 , termsAggs = Nothing
1293 let bmaBucketExpected = TermsResult {
1294 termKey = TextValue "bitemyapp"
1296 , termsAggs = Nothing
1298 termKey nmaResult `shouldBe` ScientificValue 1000.0
1299 termsDocCount nmaResult `shouldBe` 1
1300 nmaBucket `shouldBe` nmaBucketExpected
1302 termKey bmaResult `shouldBe` ScientificValue 1000.0
1303 termsDocCount bmaResult `shouldBe` 1
1304 bmaBucket `shouldBe` bmaBucketExpected
1307 describe "Highlights API" $ do
1309 it "returns highlight from query when there should be one" $ withTestEnv $ do
1312 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
1313 let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing]
1315 let search = mkHighlightSearch (Just query) testHighlight
1316 myHighlight <- searchTweetHighlight search
1318 myHighlight `shouldBe` Right (Just (M.fromList [("message",["Use <em>haskell</em>!"])]))
1320 it "doesn't return highlight from a query when it shouldn't" $ withTestEnv $ do
1323 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
1324 let testHighlight = Highlights Nothing [FieldHighlight (FieldName "user") Nothing]
1326 let search = mkHighlightSearch (Just query) testHighlight
1327 myHighlight <- searchTweetHighlight search
1329 myHighlight `shouldBe` Right Nothing
1331 describe "Source filtering" $ do
1333 it "doesn't include source when sources are disabled" $ withTestEnv $ do
1336 (Left (EsError 500 "Source was missing"))
1338 it "includes a source" $ withTestEnv $ do
1340 (SourcePatterns (PopPattern (Pattern "message")))
1341 (Right (Object (HM.fromList [("message", String "Use haskell!")])))
1343 it "includes sources" $ withTestEnv $ do
1345 (SourcePatterns (PopPatterns [Pattern "user", Pattern "message"]))
1346 (Right (Object (HM.fromList [("user",String "bitemyapp"),("message", String "Use haskell!")])))
1348 it "includes source patterns" $ withTestEnv $ do
1350 (SourcePatterns (PopPattern (Pattern "*ge")))
1351 (Right (Object (HM.fromList [("age", Number 10000),("message", String "Use haskell!")])))
1353 it "excludes source patterns" $ withTestEnv $ do
1355 (SourceIncludeExclude (Include [])
1356 (Exclude [Pattern "l*", Pattern "*ge", Pattern "postDate", Pattern "extra"]))
1357 (Right (Object (HM.fromList [("user",String "bitemyapp")])))
1359 describe "ToJSON RegexpFlags" $ do
1360 it "generates the correct JSON for AllRegexpFlags" $
1361 toJSON AllRegexpFlags `shouldBe` String "ALL"
1363 it "generates the correct JSON for NoRegexpFlags" $
1364 toJSON NoRegexpFlags `shouldBe` String "NONE"
1366 it "generates the correct JSON for SomeRegexpFlags" $
1367 let flags = AnyString :| [ Automaton
1372 in toJSON (SomeRegexpFlags flags) `shouldBe` String "ANYSTRING|AUTOMATON|COMPLEMENT|EMPTY|INTERSECTION|INTERVAL"
1374 prop "removes duplicates from flags" $ \(flags :: RegexpFlags) ->
1375 let String str = toJSON flags
1376 flagStrs = T.splitOn "|" str
1377 in noDuplicates flagStrs
1379 describe "omitNulls" $ do
1380 it "checks that omitNulls drops list elements when it should" $
1381 let dropped = omitNulls $ [ "test1" .= (toJSON ([] :: [Int]))
1382 , "test2" .= (toJSON ("some value" :: Text))]
1383 in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
1385 it "checks that omitNulls doesn't drop list elements when it shouldn't" $
1386 let notDropped = omitNulls $ [ "test1" .= (toJSON ([1] :: [Int]))
1387 , "test2" .= (toJSON ("some value" :: Text))]
1388 in notDropped `shouldBe` Object (HM.fromList [ ("test1", Array (V.fromList [Number 1.0]))
1389 , ("test2", String "some value")])
1390 it "checks that omitNulls drops non list elements when it should" $
1391 let dropped = omitNulls $ [ "test1" .= (toJSON Null)
1392 , "test2" .= (toJSON ("some value" :: Text))]
1393 in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
1394 it "checks that omitNulls doesn't drop non list elements when it shouldn't" $
1395 let notDropped = omitNulls $ [ "test1" .= (toJSON (1 :: Int))
1396 , "test2" .= (toJSON ("some value" :: Text))]
1397 in notDropped `shouldBe` Object (HM.fromList [ ("test1", Number 1.0)
1398 , ("test2", String "some value")])
1399 describe "Monoid (SearchHits a)" $ do
1400 prop "abides the monoid laws" $ eq $
1401 prop_Monoid (T :: T (SearchHits ()))
1403 describe "mkDocVersion" $ do
1404 prop "can never construct an out of range docVersion" $ \i ->
1405 let res = mkDocVersion i
1407 Nothing -> property True
1408 Just dv -> (dv >= minBound) .&&.
1409 (dv <= maxBound) .&&.
1410 docVersionNumber dv === i
1412 describe "Enum DocVersion" $ do
1413 it "follows the laws of Enum, Bounded" $ do
1414 evaluate (succ maxBound :: DocVersion) `shouldThrow` anyErrorCall
1415 evaluate (pred minBound :: DocVersion) `shouldThrow` anyErrorCall
1416 evaluate (toEnum 0 :: DocVersion) `shouldThrow` anyErrorCall
1417 evaluate (toEnum 9200000000000000001 :: DocVersion) `shouldThrow` anyErrorCall
1418 enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
1419 enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
1420 enumFromThen minBound (pred maxBound :: DocVersion) `shouldBe` [minBound, pred maxBound]
1422 describe "scan&scroll API" $ do
1423 it "returns documents using the scan&scroll API" $ withTestEnv $ do
1426 let search = (mkSearch (Just $ MatchAllQuery Nothing) Nothing) { size = (Size 1) }
1427 regular_search <- searchTweet search
1428 scan_search' <- scanSearch testIndex testMapping search :: BH IO [Hit Tweet]
1429 let scan_search = map hitSource scan_search'
1431 regular_search `shouldBe` Right exampleTweet -- Check that the size restrtiction is being honored
1433 scan_search `shouldMatchList` [Just exampleTweet, Just otherTweet]
1435 describe "index aliases" $ do
1436 it "handles the simple case of aliasing an existing index" $ do
1437 let alias = IndexAlias (testIndex) (IndexAliasName (IndexName "bloodhound-tests-twitter-1-alias"))
1438 let create = IndexAliasCreate Nothing Nothing
1439 let action = AddAlias alias create
1443 resp <- updateIndexAliases (action :| [])
1444 liftIO $ validateStatus resp 200
1445 let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
1446 (do aliases <- withTestEnv getIndexAliases
1447 let expected = IndexAliasSummary alias create
1449 Right (IndexAliasesSummary summs) ->
1450 L.find ((== alias) . indexAliasSummaryAlias) summs `shouldBe` Just expected
1451 Left e -> expectationFailure ("Expected an IndexAliasesSummary but got " <> show e)) `finally` cleanup
1453 it "handles an alias with routing and a filter" $ do
1454 let alias = IndexAlias (testIndex) (IndexAliasName (IndexName "bloodhound-tests-twitter-1-alias"))
1455 let sar = SearchAliasRouting (RoutingValue "search val" :| [])
1456 let iar = IndexAliasRouting (RoutingValue "index val")
1457 let routing = GranularAliasRouting (Just sar) (Just iar)
1458 let filter = LimitFilter 42
1459 let create = IndexAliasCreate (Just routing) (Just filter)
1460 let action = AddAlias alias create
1464 resp <- updateIndexAliases (action :| [])
1465 liftIO $ validateStatus resp 200
1466 let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
1467 (do aliases <- withTestEnv getIndexAliases
1468 let expected = IndexAliasSummary alias create
1470 Right (IndexAliasesSummary summs) ->
1471 L.find ((== alias) . indexAliasSummaryAlias) summs `shouldBe` Just expected
1472 Left e -> expectationFailure ("Expected an IndexAliasesSummary but got " <> show e)) `finally` cleanup
1474 describe "Index Settings" $ do
1475 it "persists settings" $ withTestEnv $ do
1476 _ <- deleteExampleIndex
1477 _ <- createExampleIndex
1478 let updates = BlocksWrite False :| []
1479 updateResp <- updateIndexSettings updates testIndex
1480 liftIO $ validateStatus updateResp 200
1481 getResp <- getIndexSettings testIndex
1483 getResp `shouldBe` Right (IndexSettingsSummary
1485 (IndexSettings (ShardCount 1) (ReplicaCount 0))
1486 (NE.toList updates))
1488 describe "Index Optimization" $ do
1489 it "returns a successful response upon completion" $ withTestEnv $ do
1490 _ <- createExampleIndex
1491 resp <- optimizeIndex (IndexList (testIndex :| [])) defaultIndexOptimizationSettings
1492 liftIO $ validateStatus resp 200
1494 describe "JSON instances" $ do
1495 propJSON (Proxy :: Proxy Version)
1496 propJSON (Proxy :: Proxy IndexName)
1497 propJSON (Proxy :: Proxy MappingName)
1498 propJSON (Proxy :: Proxy DocId)
1499 propJSON (Proxy :: Proxy IndexAliasRouting)
1500 propJSON (Proxy :: Proxy RoutingValue)
1501 propJSON (Proxy :: Proxy ShardCount)
1502 propJSON (Proxy :: Proxy ReplicaCount)
1503 propJSON (Proxy :: Proxy TemplateName)
1504 propJSON (Proxy :: Proxy TemplatePattern)
1505 propJSON (Proxy :: Proxy QueryString)
1506 propJSON (Proxy :: Proxy FieldName)
1507 propJSON (Proxy :: Proxy CacheName)
1508 propJSON (Proxy :: Proxy CacheKey)
1509 propJSON (Proxy :: Proxy Existence)
1510 propJSON (Proxy :: Proxy CutoffFrequency)
1511 propJSON (Proxy :: Proxy Analyzer)
1512 propJSON (Proxy :: Proxy MaxExpansions)
1513 propJSON (Proxy :: Proxy Lenient)
1514 propJSON (Proxy :: Proxy Tiebreaker)
1515 propJSON (Proxy :: Proxy Boost)
1516 propJSON (Proxy :: Proxy BoostTerms)
1517 propJSON (Proxy :: Proxy MinimumMatch)
1518 propJSON (Proxy :: Proxy DisableCoord)
1519 propJSON (Proxy :: Proxy IgnoreTermFrequency)
1520 propJSON (Proxy :: Proxy MinimumTermFrequency)
1521 propJSON (Proxy :: Proxy MaxQueryTerms)
1522 propJSON (Proxy :: Proxy Fuzziness)
1523 propJSON (Proxy :: Proxy PrefixLength)
1524 propJSON (Proxy :: Proxy TypeName)
1525 propJSON (Proxy :: Proxy PercentMatch)
1526 propJSON (Proxy :: Proxy StopWord)
1527 propJSON (Proxy :: Proxy QueryPath)
1528 propJSON (Proxy :: Proxy AllowLeadingWildcard)
1529 propJSON (Proxy :: Proxy LowercaseExpanded)
1530 propJSON (Proxy :: Proxy EnablePositionIncrements)
1531 propJSON (Proxy :: Proxy AnalyzeWildcard)
1532 propJSON (Proxy :: Proxy GeneratePhraseQueries)
1533 propJSON (Proxy :: Proxy Locale)
1534 propJSON (Proxy :: Proxy MaxWordLength)
1535 propJSON (Proxy :: Proxy MinWordLength)
1536 propJSON (Proxy :: Proxy PhraseSlop)
1537 propJSON (Proxy :: Proxy MinDocFrequency)
1538 propJSON (Proxy :: Proxy MaxDocFrequency)
1539 propJSON (Proxy :: Proxy Filter)
1540 propJSON (Proxy :: Proxy Query)
1541 propJSON (Proxy :: Proxy SimpleQueryStringQuery)
1542 propJSON (Proxy :: Proxy FieldOrFields)
1543 propJSON (Proxy :: Proxy SimpleQueryFlag)
1544 propJSON (Proxy :: Proxy RegexpQuery)
1545 propJSON (Proxy :: Proxy QueryStringQuery)
1546 propJSON (Proxy :: Proxy RangeQuery)
1547 propJSON (Proxy :: Proxy PrefixQuery)
1548 propJSON (Proxy :: Proxy NestedQuery)
1549 propJSON (Proxy :: Proxy MoreLikeThisFieldQuery)
1550 propJSON (Proxy :: Proxy MoreLikeThisQuery)
1551 propJSON (Proxy :: Proxy IndicesQuery)
1552 propJSON (Proxy :: Proxy HasParentQuery)
1553 propJSON (Proxy :: Proxy HasChildQuery)
1554 propJSON (Proxy :: Proxy FuzzyQuery)
1555 propJSON (Proxy :: Proxy FuzzyLikeFieldQuery)
1556 propJSON (Proxy :: Proxy FuzzyLikeThisQuery)
1557 propJSON (Proxy :: Proxy FilteredQuery)
1558 propJSON (Proxy :: Proxy DisMaxQuery)
1559 propJSON (Proxy :: Proxy CommonTermsQuery)
1560 propJSON (Proxy :: Proxy CommonMinimumMatch)
1561 propJSON (Proxy :: Proxy BoostingQuery)
1562 propJSON (Proxy :: Proxy BoolQuery)
1563 propJSON (Proxy :: Proxy MatchQuery)
1564 propJSON (Proxy :: Proxy MultiMatchQueryType)
1565 propJSON (Proxy :: Proxy BooleanOperator)
1566 propJSON (Proxy :: Proxy ZeroTermsQuery)
1567 propJSON (Proxy :: Proxy MatchQueryType)
1568 propJSON (Proxy :: Proxy AliasRouting)
1569 propJSON (Proxy :: Proxy IndexAliasCreate)
1570 propJSON (Proxy :: Proxy SearchAliasRouting)
1571 propJSON (Proxy :: Proxy ScoreType)
1572 propJSON (Proxy :: Proxy Distance)
1573 propJSON (Proxy :: Proxy DistanceUnit)
1574 propJSON (Proxy :: Proxy DistanceType)
1575 propJSON (Proxy :: Proxy OptimizeBbox)
1576 propJSON (Proxy :: Proxy GeoBoundingBoxConstraint)
1577 propJSON (Proxy :: Proxy GeoFilterType)
1578 propJSON (Proxy :: Proxy GeoBoundingBox)
1579 propJSON (Proxy :: Proxy LatLon)
1580 propJSON (Proxy :: Proxy RangeExecution)
1581 prop "RegexpFlags FromJSON/ToJSON roundtrips, removing dups " $ \rfs ->
1582 let expected = case rfs of
1583 SomeRegexpFlags fs -> SomeRegexpFlags (NE.fromList (nub (NE.toList fs)))
1585 in parseEither parseJSON (toJSON rfs) === Right expected
1586 propJSON (Proxy :: Proxy BoolMatch)
1587 propJSON (Proxy :: Proxy Term)
1588 propJSON (Proxy :: Proxy MultiMatchQuery)
1589 propJSON (Proxy :: Proxy IndexSettings)
1590 propJSON (Proxy :: Proxy UpdatableIndexSetting)
1591 propJSON (Proxy :: Proxy ReplicaBounds)
1592 propJSON (Proxy :: Proxy Bytes)
1593 propJSON (Proxy :: Proxy AllocationPolicy)
1594 propJSON (Proxy :: Proxy InitialShardCount)
1595 propJSON (Proxy :: Proxy FSType)
1596 propJSON (Proxy :: Proxy CompoundFormat)
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