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
17import Control.Monad.Trans.Resource
19import Data.Aeson.Types (parseEither)
20import qualified Data.ByteString.Lazy.Char8 as BL8
22import qualified Data.HashMap.Strict as HM
24import qualified Data.List as L
25import Data.List.NonEmpty (NonEmpty (..))
26import qualified Data.List.NonEmpty as NE
27import qualified Data.Map.Strict as M
30import Data.Text (Text)
31import qualified Data.Text as T
32import Data.Time.Calendar (Day (..), fromGregorian)
33import Data.Time.Clock (NominalDiffTime, UTCTime (..),
36import qualified Data.Vector as V
37import Database.Bloodhound
38import GHC.Generics as G
39import Network.HTTP.Client hiding (Proxy)
40import qualified Network.HTTP.Types.Status as NHTS
41import Prelude hiding (filter)
44import Test.QuickCheck.Property.Monoid (T (..), eq, prop_Monoid)
46import Test.Hspec.QuickCheck (prop)
50testServer = Server "http://localhost:9200"
52testIndex = IndexName "bloodhound-tests-twitter-1"
53testMapping :: MappingName
54testMapping = MappingName "tweet"
56withTestEnv :: BH IO a -> IO a
57withTestEnv = withBH defaultManagerSettings testServer
59validateStatus :: Response body -> Int -> Expectation
60validateStatus resp expected =
61 (NHTS.statusCode $ responseStatus resp)
62 `shouldBe` (expected :: Int)
64createExampleIndex :: BH IO Reply
65createExampleIndex = createIndex (IndexSettings (ShardCount 1) (ReplicaCount 0)) testIndex
66deleteExampleIndex :: BH IO Reply
67deleteExampleIndex = deleteIndex testIndex
69data ServerVersion = ServerVersion Int Int Int deriving (Show, Eq, Ord)
72es13 = ServerVersion 1 3 0
75es12 = ServerVersion 1 2 0
78es11 = ServerVersion 1 1 0
80serverBranch :: ServerVersion -> ServerVersion
81serverBranch (ServerVersion majorVer minorVer patchVer) =
82 ServerVersion majorVer minorVer patchVer
84mkServerVersion :: [Int] -> Maybe ServerVersion
85mkServerVersion [majorVer, minorVer, patchVer] =
86 Just (ServerVersion majorVer minorVer patchVer)
87mkServerVersion _ = Nothing
89getServerVersion :: IO (Maybe ServerVersion)
90getServerVersion = liftM extractVersion (withTestEnv getStatus)
92 version' = T.splitOn "." . number . version
93 toInt = read . T.unpack
94 parseVersion v = map toInt (version' v)
95 extractVersion = join . liftM (mkServerVersion . parseVersion)
97testServerBranch :: IO (Maybe ServerVersion)
98testServerBranch = getServerVersion >>= \v -> return $ liftM serverBranch v
100atleast :: ServerVersion -> IO Bool
101atleast v = testServerBranch >>= \x -> return $ x >= Just (serverBranch v)
103atmost :: ServerVersion -> IO Bool
104atmost v = testServerBranch >>= \x -> return $ x <= Just (serverBranch v)
106is :: ServerVersion -> IO Bool
107is v = testServerBranch >>= \x -> return $ x == Just (serverBranch v)
109when' :: Monad m => m Bool -> m () -> m ()
110when' b f = b >>= \x -> when x f
112(==~) :: (ApproxEq a, Show a) => a -> a -> Property
113a ==~ b = counterexample (show a ++ " !=~ " ++ show b) (a =~ b)
115propJSON :: forall a. (Arbitrary a, ToJSON a, FromJSON a, Show a, ApproxEq a, Typeable a) => Proxy a -> Spec
116propJSON _ = prop testName $ \(a :: a) ->
117 let jsonStr = "via " <> BL8.unpack (encode a)
118 in counterexample jsonStr (parseEither parseJSON (toJSON a) ==~ Right a)
119 where testName = show ty <> " FromJSON/ToJSON roundtrips"
120 ty = typeOf (undefined :: a)
122data Location = Location { lat :: Double
123 , lon :: Double } deriving (Eq, Generic, Show)
125data Tweet = Tweet { user :: Text
126 , postDate :: UTCTime
129 , location :: Location
130 , extra :: Maybe Text }
131 deriving (Eq, Generic, Show)
133instance ToJSON Tweet where
134 toJSON = genericToJSON defaultOptions
135instance FromJSON Tweet where
136 parseJSON = genericParseJSON defaultOptions
137instance ToJSON Location where
138 toJSON = genericToJSON defaultOptions
139instance FromJSON Location where
140 parseJSON = genericParseJSON defaultOptions
142data ParentMapping = ParentMapping deriving (Eq, Show)
144instance ToJSON ParentMapping where
145 toJSON ParentMapping =
146 object ["parent" .= Null ]
148data ChildMapping = ChildMapping deriving (Eq, Show)
150instance ToJSON ChildMapping where
151 toJSON ChildMapping =
153 object ["_parent" .= object ["type" .= ("parent" :: Text)]]
156data TweetMapping = TweetMapping deriving (Eq, Show)
158instance ToJSON TweetMapping where
159 toJSON TweetMapping =
161 object ["properties" .=
162 object [ "user" .= object ["type" .= ("string" :: Text)]
163 -- Serializing the date as a date is breaking other tests, mysteriously.
164 -- , "postDate" .= object [ "type" .= ("date" :: Text)
165 -- , "format" .= ("YYYY-MM-dd`T`HH:mm:ss.SSSZZ" :: Text)]
166 , "message" .= object ["type" .= ("string" :: Text)]
167 , "age" .= object ["type" .= ("integer" :: Text)]
168 , "location" .= object ["type" .= ("geo_point" :: Text)]
172exampleTweet = Tweet { user = "bitemyapp"
174 (ModifiedJulianDay 55000)
175 (secondsToDiffTime 10)
176 , message = "Use haskell!"
178 , location = Location 40.12 (-71.34)
181tweetWithExtra :: Tweet
182tweetWithExtra = Tweet { user = "bitemyapp"
184 (ModifiedJulianDay 55000)
185 (secondsToDiffTime 10)
186 , message = "Use haskell!"
188 , location = Location 40.12 (-71.34)
189 , extra = Just "blah blah" }
195newUser = "someotherapp"
199 object [ "age" .= newAge
204patchedTweet = exampleTweet{age = newAge, user = newUser}
207otherTweet = Tweet { user = "notmyapp"
209 (ModifiedJulianDay 55000)
210 (secondsToDiffTime 11)
211 , message = "Use haskell!"
213 , location = Location 40.12 (-71.34)
216resetIndex :: BH IO ()
218 _ <- deleteExampleIndex
219 _ <- createExampleIndex
220 _ <- putMapping testIndex testMapping TweetMapping
223insertData :: BH IO Reply
226 insertData' defaultIndexDocumentSettings
228insertData' :: IndexDocumentSettings -> BH IO Reply
230 r <- indexDocument testIndex testMapping ids exampleTweet (DocId "1")
231 _ <- refreshIndex testIndex
234updateData :: BH IO Reply
236 r <- updateDocument testIndex testMapping defaultIndexDocumentSettings tweetPatch (DocId "1")
237 _ <- refreshIndex testIndex
240insertOther :: BH IO ()
242 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings otherTweet (DocId "2")
243 _ <- refreshIndex testIndex
246insertExtra :: BH IO ()
248 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings tweetWithExtra (DocId "4")
249 _ <- refreshIndex testIndex
252insertWithSpaceInId :: BH IO ()
253insertWithSpaceInId = do
254 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "Hello World")
255 _ <- refreshIndex testIndex
258searchTweet :: Search -> BH IO (Either EsError Tweet)
259searchTweet search = do
260 result <- searchTweets search
261 let myTweet :: Either EsError Tweet
262 myTweet = grabFirst result
265searchTweets :: Search -> BH IO (Either EsError (SearchResult Tweet))
266searchTweets search = parseEsResponse =<< searchByIndex testIndex search
268searchExpectNoResults :: Search -> BH IO ()
269searchExpectNoResults search = do
270 result <- searchTweets search
271 let emptyHits = fmap (hits . searchHits) result
273 emptyHits `shouldBe` Right []
275searchExpectAggs :: Search -> BH IO ()
276searchExpectAggs search = do
277 reply <- searchByIndex testIndex search
278 let isEmpty x = return (M.null x)
279 let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
281 (result >>= aggregations >>= isEmpty) `shouldBe` Just False
283searchValidBucketAgg :: (BucketAggregation a, FromJSON a, Show a) =>
284 Search -> Text -> (Text -> AggregationResults -> Maybe (Bucket a)) -> BH IO ()
285searchValidBucketAgg search aggKey extractor = do
286 reply <- searchByIndex testIndex search
287 let bucketDocs = docCount . head . buckets
288 let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
289 let count = result >>= aggregations >>= extractor aggKey >>= \x -> return (bucketDocs x)
291 count `shouldBe` Just 1
293searchTermsAggHint :: [ExecutionHint] -> BH IO ()
294searchTermsAggHint hints = do
295 let terms hint = TermsAgg $ (mkTermsAggregation "user") { termExecutionHint = Just hint }
296 let search hint = mkAggregateSearch Nothing $ mkAggregations "users" $ terms hint
297 forM_ hints $ searchExpectAggs . search
298 forM_ hints (\x -> searchValidBucketAgg (search x) "users" toTerms)
300searchTweetHighlight :: Search -> BH IO (Either EsError (Maybe HitHighlight))
301searchTweetHighlight search = do
302 result <- searchTweets search
303 let myHighlight = fmap (hitHighlight . head . hits . searchHits) result
306searchExpectSource :: Source -> Either EsError Value -> BH IO ()
307searchExpectSource src expected = do
309 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
310 let search = (mkSearch (Just query) Nothing) { source = Just src }
311 reply <- searchByIndex testIndex search
312 result <- parseEsResponse reply
313 let value = grabFirst result
315 value `shouldBe` expected
317data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
318instance FromJSON BulkTest where
319 parseJSON = genericParseJSON defaultOptions
320instance ToJSON BulkTest where
321 toJSON = genericToJSON defaultOptions
323class GApproxEq f where
324 gApproxEq :: f a -> f a -> Bool
327instance GApproxEq U1 where
328 gApproxEq U1 U1 = True
330-- | Sum type, ensure same constructors, recurse
331instance (GApproxEq a, GApproxEq b) => GApproxEq (a :+: b) where
332 gApproxEq (L1 a) (L1 b) = gApproxEq a b
333 gApproxEq (R1 a) (R1 b) = gApproxEq a b
334 gApproxEq _ _ = False
336-- | Product type, ensure each field is approx eq
337instance (GApproxEq a, GApproxEq b) => GApproxEq (a :*: b) where
338 gApproxEq (a1 :*: b1) (a2 :*: b2) = gApproxEq a1 a2 && gApproxEq b1 b2
340-- | Value type, actually check the values for approx equality
341instance (ApproxEq a) => GApproxEq (K1 i a) where
342 gApproxEq (K1 a) (K1 b) = a =~ b
344instance (GApproxEq f) => GApproxEq (M1 i t f) where
345 gApproxEq (M1 a) (M1 b) = gApproxEq a b
347-- | Typeclass for "equal where it matters". Use this to specify
348-- less-strict equivalence for things such as lists that can wind up
349-- in an unpredictable order
350class ApproxEq a where
351 (=~) :: a -> a -> Bool
352 default (=~) :: (Generic a, GApproxEq (Rep a)) => a -> a -> Bool
353 a =~ b = gApproxEq (G.from a) (G.from b)
355instance ApproxEq NominalDiffTime where (=~) = (==)
356instance ApproxEq UTCTime where (=~) = (==)
357instance ApproxEq Text where (=~) = (==)
358instance ApproxEq Bool where (=~) = (==)
359instance ApproxEq Int where (=~) = (==)
360instance ApproxEq Double where (=~) = (==)
361instance ApproxEq a => ApproxEq (NonEmpty a)
362instance ApproxEq a => ApproxEq (Maybe a)
363instance ApproxEq GeoPoint
364instance ApproxEq Regexp
365instance ApproxEq RangeValue
366instance ApproxEq LessThan
367instance ApproxEq LessThanEq
368instance ApproxEq LessThanD
369instance ApproxEq LessThanEqD
370instance ApproxEq GreaterThan
371instance ApproxEq GreaterThanEq
372instance ApproxEq GreaterThanD
373instance ApproxEq GreaterThanEqD
374instance ApproxEq MinimumMatchHighLow
375instance ApproxEq RegexpFlag
376instance ApproxEq RegexpFlags
377instance ApproxEq NullValue
378instance ApproxEq Version
379instance ApproxEq DistanceRange
380instance ApproxEq IndexName
381instance ApproxEq MappingName
382instance ApproxEq DocId
383instance ApproxEq IndexAliasRouting
384instance ApproxEq RoutingValue
385instance ApproxEq ShardCount
386instance ApproxEq ReplicaCount
387instance ApproxEq TemplateName
388instance ApproxEq TemplatePattern
389instance ApproxEq QueryString
390instance ApproxEq FieldName
391instance ApproxEq CacheName
392instance ApproxEq CacheKey
393instance ApproxEq Existence
394instance ApproxEq CutoffFrequency
395instance ApproxEq Analyzer
396instance ApproxEq Lenient
397instance ApproxEq Tiebreaker
398instance ApproxEq Boost
399instance ApproxEq BoostTerms
400instance ApproxEq MaxExpansions
401instance ApproxEq MinimumMatch
402instance ApproxEq DisableCoord
403instance ApproxEq IgnoreTermFrequency
404instance ApproxEq MinimumTermFrequency
405instance ApproxEq MaxQueryTerms
406instance ApproxEq Fuzziness
407instance ApproxEq PrefixLength
408instance ApproxEq TypeName
409instance ApproxEq PercentMatch
410instance ApproxEq StopWord
411instance ApproxEq QueryPath
412instance ApproxEq AllowLeadingWildcard
413instance ApproxEq LowercaseExpanded
414instance ApproxEq EnablePositionIncrements
415instance ApproxEq AnalyzeWildcard
416instance ApproxEq GeneratePhraseQueries
417instance ApproxEq Locale
418instance ApproxEq MaxWordLength
419instance ApproxEq MinWordLength
420instance ApproxEq PhraseSlop
421instance ApproxEq MinDocFrequency
422instance ApproxEq MaxDocFrequency
423instance ApproxEq Filter
424instance ApproxEq Query
425instance ApproxEq SimpleQueryStringQuery
426instance ApproxEq FieldOrFields
427instance ApproxEq SimpleQueryFlag
428instance ApproxEq RegexpQuery
429instance ApproxEq QueryStringQuery
430instance ApproxEq RangeQuery
431instance ApproxEq PrefixQuery
432instance ApproxEq NestedQuery
433instance ApproxEq MoreLikeThisFieldQuery
434instance ApproxEq MoreLikeThisQuery
435instance ApproxEq IndicesQuery
436instance ApproxEq HasParentQuery
437instance ApproxEq HasChildQuery
438instance ApproxEq FuzzyQuery
439instance ApproxEq FuzzyLikeFieldQuery
440instance ApproxEq FuzzyLikeThisQuery
441instance ApproxEq FilteredQuery
442instance ApproxEq DisMaxQuery
443instance ApproxEq CommonTermsQuery
444instance ApproxEq CommonMinimumMatch
445instance ApproxEq BoostingQuery
446instance ApproxEq BoolQuery
447instance ApproxEq MatchQuery
448instance ApproxEq MultiMatchQueryType
449instance ApproxEq BooleanOperator
450instance ApproxEq ZeroTermsQuery
451instance ApproxEq MatchQueryType
452instance ApproxEq AliasRouting
453instance ApproxEq IndexAliasCreate
454instance ApproxEq SearchAliasRouting
455instance ApproxEq ScoreType
456instance ApproxEq Distance
457instance ApproxEq DistanceUnit
458instance ApproxEq DistanceType
459instance ApproxEq OptimizeBbox
460instance ApproxEq GeoBoundingBoxConstraint
461instance ApproxEq GeoFilterType
462instance ApproxEq GeoBoundingBox
463instance ApproxEq LatLon
464instance ApproxEq RangeExecution
465instance ApproxEq FSType
466instance ApproxEq CompoundFormat
467instance ApproxEq InitialShardCount
468instance ApproxEq Bytes
469instance ApproxEq ReplicaBounds
470instance ApproxEq Term
471instance ApproxEq BoolMatch
472instance ApproxEq MultiMatchQuery
473instance ApproxEq IndexSettings
474instance ApproxEq AllocationPolicy
475instance ApproxEq Char
476instance ApproxEq a => ApproxEq [a] where
477 as =~ bs = and (zipWith (=~) as bs)
478instance (ApproxEq l, ApproxEq r) => ApproxEq (Either l r) where
479 Left a =~ Left b = a =~ b
480 Right a =~ Right b = a =~ b
482instance ApproxEq NodeAttrFilter
483instance ApproxEq NodeAttrName
485-- | Due to the way nodeattrfilters get serialized here, they may come
486-- out in a different order, but they are morally equivalent
487instance ApproxEq UpdatableIndexSetting where
488 RoutingAllocationInclude a =~ RoutingAllocationInclude b =
489 NE.sort a =~ NE.sort b
490 RoutingAllocationExclude a =~ RoutingAllocationExclude b =
491 NE.sort a =~ NE.sort b
492 RoutingAllocationRequire a =~ RoutingAllocationRequire b =
493 NE.sort a =~ NE.sort b
497noDuplicates :: Eq a => [a] -> Bool
498noDuplicates xs = nub xs == xs
500instance Arbitrary NominalDiffTime where
501 arbitrary = fromInteger <$> arbitrary
503instance (Arbitrary k, Ord k, Arbitrary v) => Arbitrary (M.Map k v) where
504 arbitrary = M.fromList <$> arbitrary
506instance Arbitrary Text where
507 arbitrary = T.pack <$> arbitrary
509instance Arbitrary UTCTime where
512 <*> (fromRational . toRational <$> choose (0::Double, 86400))
514instance Arbitrary Day where
515 arbitrary = ModifiedJulianDay <$> (2000 +) <$> arbitrary
516 shrink = (ModifiedJulianDay <$>) . shrink . toModifiedJulianDay
518instance Arbitrary a => Arbitrary (NonEmpty a) where
519 arbitrary = liftA2 (:|) arbitrary arbitrary
521arbitraryScore :: Gen Score
522arbitraryScore = fmap getPositive <$> arbitrary
524instance (Arbitrary a, Typeable a) => Arbitrary (Hit a) where
525 arbitrary = Hit <$> arbitrary
531 shrink = genericShrink
534instance (Arbitrary a, Typeable a) => Arbitrary (SearchHits a) where
535 arbitrary = reduceSize $ do
536 tot <- getPositive <$> arbitrary
537 score <- arbitraryScore
539 return $ SearchHits tot score hs
540 shrink = genericShrink
542reduceSize :: Gen a -> Gen a
543reduceSize f = sized $ \n -> resize (n `div` 2) f
545getSource :: EsResult a -> Maybe a
546getSource = fmap _source . foundResult
548grabFirst :: Either EsError (SearchResult a) -> Either EsError a
550 case fmap (hitSource . head . hits . searchHits) r of
552 (Right Nothing) -> Left (EsError 500 "Source was missing")
553 (Right (Just x)) -> Right x
555-------------------------------------------------------------------------------
556arbitraryAlphaNum :: Gen Char
557arbitraryAlphaNum = oneof [choose ('a', 'z')
561instance Arbitrary RoutingValue where
562 arbitrary = RoutingValue . T.pack <$> listOf1 arbitraryAlphaNum
564instance Arbitrary AliasRouting where
565 arbitrary = oneof [allAlias
569 where one = GranularAliasRouting
570 <$> (Just <$> arbitrary)
572 theOther = GranularAliasRouting Nothing
573 <$> (Just <$> arbitrary)
574 both = GranularAliasRouting
575 <$> (Just <$> arbitrary)
576 <*> (Just <$> arbitrary)
577 allAlias = AllAliasRouting <$> arbitrary
578 shrink = genericShrink
581instance Arbitrary FieldName where
582 arbitrary = FieldName . T.pack <$> listOf1 arbitraryAlphaNum
583 shrink = genericShrink
586instance Arbitrary RegexpFlags where
587 arbitrary = oneof [ pure AllRegexpFlags
589 , SomeRegexpFlags <$> genUniqueFlags
591 where genUniqueFlags = NE.fromList . nub <$> listOf1 arbitrary
592 shrink = genericShrink
595instance Arbitrary IndexAliasCreate where
596 arbitrary = IndexAliasCreate <$> arbitrary <*> reduceSize arbitrary
597 shrink = genericShrink
599instance Arbitrary Query where
600 arbitrary = reduceSize $ oneof [ TermQuery <$> arbitrary <*> arbitrary
601 , TermsQuery <$> arbitrary <*> arbitrary
602 , QueryMatchQuery <$> arbitrary
603 , QueryMultiMatchQuery <$> arbitrary
604 , QueryBoolQuery <$> arbitrary
605 , QueryBoostingQuery <$> arbitrary
606 , QueryCommonTermsQuery <$> arbitrary
607 , ConstantScoreFilter <$> arbitrary <*> arbitrary
608 , ConstantScoreQuery <$> arbitrary <*> arbitrary
609 , QueryDisMaxQuery <$> arbitrary
610 , QueryFilteredQuery <$> arbitrary
611 , QueryFuzzyLikeThisQuery <$> arbitrary
612 , QueryFuzzyLikeFieldQuery <$> arbitrary
613 , QueryFuzzyQuery <$> arbitrary
614 , QueryHasChildQuery <$> arbitrary
615 , QueryHasParentQuery <$> arbitrary
616 , IdsQuery <$> arbitrary <*> arbitrary
617 , QueryIndicesQuery <$> arbitrary
618 , MatchAllQuery <$> arbitrary
619 , QueryMoreLikeThisQuery <$> arbitrary
620 , QueryMoreLikeThisFieldQuery <$> arbitrary
621 , QueryNestedQuery <$> arbitrary
622 , QueryPrefixQuery <$> arbitrary
623 , QueryQueryStringQuery <$> arbitrary
624 , QuerySimpleQueryStringQuery <$> arbitrary
625 , QueryRangeQuery <$> arbitrary
626 , QueryRegexpQuery <$> arbitrary
628 shrink = genericShrink
630instance Arbitrary Filter where
631 arbitrary = reduceSize $ oneof [ AndFilter <$> arbitrary <*> arbitrary
632 , OrFilter <$> arbitrary <*> arbitrary
633 , NotFilter <$> arbitrary <*> arbitrary
634 , pure IdentityFilter
635 , BoolFilter <$> arbitrary
636 , ExistsFilter <$> arbitrary
637 , GeoBoundingBoxFilter <$> arbitrary
638 , GeoDistanceFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
639 , GeoDistanceRangeFilter <$> arbitrary <*> arbitrary
640 , GeoPolygonFilter <$> arbitrary <*> arbitrary
641 , IdsFilter <$> arbitrary <*> arbitrary
642 , LimitFilter <$> arbitrary
643 , MissingFilter <$> arbitrary <*> arbitrary <*> arbitrary
644 , PrefixFilter <$> arbitrary <*> arbitrary <*> arbitrary
645 , QueryFilter <$> arbitrary <*> arbitrary
646 , RangeFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
647 , RegexpFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
648 , TermFilter <$> arbitrary <*> arbitrary]
649 shrink = genericShrink
651instance Arbitrary ReplicaBounds where
652 arbitrary = oneof [ replicasBounded
653 , replicasLowerBounded
654 , pure ReplicasUnbounded
656 where replicasBounded = do Positive a <- arbitrary
657 Positive b <- arbitrary
658 return (ReplicasBounded a b)
659 replicasLowerBounded = do Positive a <- arbitrary
660 return (ReplicasLowerBounded a)
662instance Arbitrary NodeAttrName where
663 arbitrary = NodeAttrName . T.pack . getNonEmpty <$> arbitrary
666instance Arbitrary NodeAttrFilter where
669 s:ss <- listOf1 (listOf1 arbitraryAlphaNum)
670 let ts = T.pack <$> s :| ss
671 return (NodeAttrFilter n ts)
673$(derive makeArbitrary ''IndexName)
674$(derive makeArbitrary ''MappingName)
675$(derive makeArbitrary ''DocId)
676$(derive makeArbitrary ''Version)
677$(derive makeArbitrary ''IndexAliasRouting)
678$(derive makeArbitrary ''ShardCount)
679$(derive makeArbitrary ''ReplicaCount)
680$(derive makeArbitrary ''TemplateName)
681$(derive makeArbitrary ''TemplatePattern)
682$(derive makeArbitrary ''QueryString)
683$(derive makeArbitrary ''CacheName)
684$(derive makeArbitrary ''CacheKey)
685$(derive makeArbitrary ''Existence)
686$(derive makeArbitrary ''CutoffFrequency)
687$(derive makeArbitrary ''Analyzer)
688$(derive makeArbitrary ''MaxExpansions)
689$(derive makeArbitrary ''Lenient)
690$(derive makeArbitrary ''Tiebreaker)
691$(derive makeArbitrary ''Boost)
692$(derive makeArbitrary ''BoostTerms)
693$(derive makeArbitrary ''MinimumMatch)
694$(derive makeArbitrary ''DisableCoord)
695$(derive makeArbitrary ''IgnoreTermFrequency)
696$(derive makeArbitrary ''MinimumTermFrequency)
697$(derive makeArbitrary ''MaxQueryTerms)
698$(derive makeArbitrary ''Fuzziness)
699$(derive makeArbitrary ''PrefixLength)
700$(derive makeArbitrary ''TypeName)
701$(derive makeArbitrary ''PercentMatch)
702$(derive makeArbitrary ''StopWord)
703$(derive makeArbitrary ''QueryPath)
704$(derive makeArbitrary ''AllowLeadingWildcard)
705$(derive makeArbitrary ''LowercaseExpanded)
706$(derive makeArbitrary ''EnablePositionIncrements)
707$(derive makeArbitrary ''AnalyzeWildcard)
708$(derive makeArbitrary ''GeneratePhraseQueries)
709$(derive makeArbitrary ''Locale)
710$(derive makeArbitrary ''MaxWordLength)
711$(derive makeArbitrary ''MinWordLength)
712$(derive makeArbitrary ''PhraseSlop)
713$(derive makeArbitrary ''MinDocFrequency)
714$(derive makeArbitrary ''MaxDocFrequency)
715$(derive makeArbitrary ''Regexp)
716$(derive makeArbitrary ''SimpleQueryStringQuery)
717$(derive makeArbitrary ''FieldOrFields)
718$(derive makeArbitrary ''SimpleQueryFlag)
719$(derive makeArbitrary ''RegexpQuery)
720$(derive makeArbitrary ''QueryStringQuery)
721$(derive makeArbitrary ''RangeQuery)
722$(derive makeArbitrary ''RangeValue)
723$(derive makeArbitrary ''PrefixQuery)
724$(derive makeArbitrary ''NestedQuery)
725$(derive makeArbitrary ''MoreLikeThisFieldQuery)
726$(derive makeArbitrary ''MoreLikeThisQuery)
727$(derive makeArbitrary ''IndicesQuery)
728$(derive makeArbitrary ''HasParentQuery)
729$(derive makeArbitrary ''HasChildQuery)
730$(derive makeArbitrary ''FuzzyQuery)
731$(derive makeArbitrary ''FuzzyLikeFieldQuery)
732$(derive makeArbitrary ''FuzzyLikeThisQuery)
733$(derive makeArbitrary ''FilteredQuery)
734$(derive makeArbitrary ''DisMaxQuery)
735$(derive makeArbitrary ''CommonTermsQuery)
736$(derive makeArbitrary ''DistanceRange)
737$(derive makeArbitrary ''MultiMatchQuery)
738$(derive makeArbitrary ''LessThanD)
739$(derive makeArbitrary ''LessThanEqD)
740$(derive makeArbitrary ''GreaterThanD)
741$(derive makeArbitrary ''GreaterThanEqD)
742$(derive makeArbitrary ''LessThan)
743$(derive makeArbitrary ''LessThanEq)
744$(derive makeArbitrary ''GreaterThan)
745$(derive makeArbitrary ''GreaterThanEq)
746$(derive makeArbitrary ''GeoPoint)
747$(derive makeArbitrary ''NullValue)
748$(derive makeArbitrary ''MinimumMatchHighLow)
749$(derive makeArbitrary ''CommonMinimumMatch)
750$(derive makeArbitrary ''BoostingQuery)
751$(derive makeArbitrary ''BoolQuery)
752$(derive makeArbitrary ''MatchQuery)
753$(derive makeArbitrary ''MultiMatchQueryType)
754$(derive makeArbitrary ''BooleanOperator)
755$(derive makeArbitrary ''ZeroTermsQuery)
756$(derive makeArbitrary ''MatchQueryType)
757$(derive makeArbitrary ''SearchAliasRouting)
758$(derive makeArbitrary ''ScoreType)
759$(derive makeArbitrary ''Distance)
760$(derive makeArbitrary ''DistanceUnit)
761$(derive makeArbitrary ''DistanceType)
762$(derive makeArbitrary ''OptimizeBbox)
763$(derive makeArbitrary ''GeoBoundingBoxConstraint)
764$(derive makeArbitrary ''GeoFilterType)
765$(derive makeArbitrary ''GeoBoundingBox)
766$(derive makeArbitrary ''LatLon)
767$(derive makeArbitrary ''RangeExecution)
768$(derive makeArbitrary ''RegexpFlag)
769$(derive makeArbitrary ''BoolMatch)
770$(derive makeArbitrary ''Term)
771$(derive makeArbitrary ''IndexSettings)
772$(derive makeArbitrary ''UpdatableIndexSetting)
773$(derive makeArbitrary ''Bytes)
774$(derive makeArbitrary ''AllocationPolicy)
775$(derive makeArbitrary ''InitialShardCount)
776$(derive makeArbitrary ''FSType)
777$(derive makeArbitrary ''CompoundFormat)
778$(derive makeArbitrary ''FsSnapshotRepo)
779$(derive makeArbitrary ''SnapshotRepoName)
784 describe "index create/delete API" $ do
785 it "creates and then deletes the requested index" $ withTestEnv $ do
787 _ <- deleteExampleIndex
788 resp <- createExampleIndex
789 deleteResp <- deleteExampleIndex
791 validateStatus resp 200
792 validateStatus deleteResp 200
794 describe "error parsing" $ do
795 it "can parse EsErrors" $ withTestEnv $ do
796 res <- getDocument (IndexName "bogus") (MappingName "also_bogus") (DocId "bogus_as_well")
797 let errorResp = eitherDecode (responseBody res)
798 liftIO (errorResp `shouldBe` Right (EsError 404 "IndexMissingException[[bogus] missing]"))
800 describe "document API" $ do
801 it "indexes, updates, gets, and then deletes the generated document" $ withTestEnv $ do
804 docInserted <- getDocument testIndex testMapping (DocId "1")
805 let newTweet = eitherDecode
806 (responseBody docInserted) :: Either String (EsResult Tweet)
807 liftIO $ (fmap getSource newTweet `shouldBe` Right (Just patchedTweet))
809 it "indexes, gets, and then deletes the generated document with a DocId containing a space" $ withTestEnv $ do
810 _ <- insertWithSpaceInId
811 docInserted <- getDocument testIndex testMapping (DocId "Hello World")
812 let newTweet = eitherDecode
813 (responseBody docInserted) :: Either String (EsResult Tweet)
814 liftIO $ (fmap getSource newTweet `shouldBe` Right (Just exampleTweet))
816 it "produces a parseable result when looking up a bogus document" $ withTestEnv $ do
817 doc <- getDocument testIndex testMapping (DocId "bogus")
818 let noTweet = eitherDecode
819 (responseBody doc) :: Either String (EsResult Tweet)
820 liftIO $ fmap foundResult noTweet `shouldBe` Right Nothing
822 it "can use optimistic concurrency control" $ withTestEnv $ do
823 let ev = ExternalDocVersion minBound
824 let cfg = defaultIndexDocumentSettings { idsVersionControl = ExternalGT ev }
826 res <- insertData' cfg
827 liftIO $ isCreated res `shouldBe` True
828 res' <- insertData' cfg
829 liftIO $ isVersionConflict res' `shouldBe` True
831 it "indexes two documents in a parent/child relationship and checks that the child exists" $ withTestEnv $ do
833 _ <- putMapping testIndex (MappingName "parent") ParentMapping
834 _ <- putMapping testIndex (MappingName "child") ChildMapping
835 _ <- indexDocument testIndex (MappingName "parent") defaultIndexDocumentSettings exampleTweet (DocId "1")
836 let parent = (Just . DocumentParent . DocId) "1"
837 ids = IndexDocumentSettings NoVersionControl parent
838 _ <- indexDocument testIndex (MappingName "child") ids otherTweet (DocId "2")
839 _ <- refreshIndex testIndex
840 exists <- documentExists testIndex (MappingName "child") parent (DocId "2")
841 liftIO $ exists `shouldBe` True
843 describe "template API" $ do
844 it "can create a template" $ withTestEnv $ do
845 let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
846 resp <- putTemplate idxTpl (TemplateName "tweet-tpl")
847 liftIO $ validateStatus resp 200
849 it "can detect if a template exists" $ withTestEnv $ do
850 exists <- templateExists (TemplateName "tweet-tpl")
851 liftIO $ exists `shouldBe` True
853 it "can delete a template" $ withTestEnv $ do
854 resp <- deleteTemplate (TemplateName "tweet-tpl")
855 liftIO $ validateStatus resp 200
857 it "can detect if a template doesn't exist" $ withTestEnv $ do
858 exists <- templateExists (TemplateName "tweet-tpl")
859 liftIO $ exists `shouldBe` False
861 describe "bulk API" $ do
862 it "inserts all documents we request" $ withTestEnv $ do
864 let firstTest = BulkTest "blah"
865 let secondTest = BulkTest "bloo"
866 let firstDoc = BulkIndex testIndex
867 testMapping (DocId "2") (toJSON firstTest)
868 let secondDoc = BulkCreate testIndex
869 testMapping (DocId "3") (toJSON secondTest)
870 let stream = V.fromList [firstDoc, secondDoc]
872 _ <- refreshIndex testIndex
873 fDoc <- getDocument testIndex testMapping (DocId "2")
874 sDoc <- getDocument testIndex testMapping (DocId "3")
875 let maybeFirst = eitherDecode $ responseBody fDoc :: Either String (EsResult BulkTest)
876 let maybeSecond = eitherDecode $ responseBody sDoc :: Either String (EsResult BulkTest)
878 fmap getSource maybeFirst `shouldBe` Right (Just firstTest)
879 fmap getSource maybeSecond `shouldBe` Right (Just secondTest)
882 describe "query API" $ do
883 it "returns document for term query and identity filter" $ withTestEnv $ do
885 let query = TermQuery (Term "user" "bitemyapp") Nothing
886 let filter = IdentityFilter <&&> IdentityFilter
887 let search = mkSearch (Just query) (Just filter)
888 myTweet <- searchTweet search
890 myTweet `shouldBe` Right exampleTweet
892 it "handles constant score queries" $ withTestEnv $ do
894 let query = TermsQuery "user" ("bitemyapp" :| [])
895 let cfQuery = ConstantScoreQuery query (Boost 1.0)
896 let filter = IdentityFilter
897 let search = mkSearch (Just cfQuery) (Just filter)
898 myTweet <- searchTweet search
900 myTweet `shouldBe` Right exampleTweet
901 it "handles constant score filters" $ withTestEnv $ do
903 let query = TermsQuery "user" ("bitemyapp" :| [])
904 let cfFilter = ConstantScoreFilter IdentityFilter (Boost 1.0)
905 let boolQuery = mkBoolQuery [query, cfFilter] [] []
906 let search = mkSearch (Just (QueryBoolQuery boolQuery)) Nothing
907 myTweet <- searchTweet search
909 myTweet `shouldBe` Right exampleTweet
912 it "returns document for terms query and identity filter" $ withTestEnv $ do
914 let query = TermsQuery "user" ("bitemyapp" :| [])
915 let filter = IdentityFilter <&&> IdentityFilter
916 let search = mkSearch (Just query) (Just filter)
917 myTweet <- searchTweet search
919 myTweet `shouldBe` Right exampleTweet
921 it "returns document for match query" $ withTestEnv $ do
923 let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
924 let search = mkSearch (Just query) Nothing
925 myTweet <- searchTweet search
927 myTweet `shouldBe` Right exampleTweet
929 it "returns document for multi-match query" $ withTestEnv $ do
931 let flds = [FieldName "user", FieldName "message"]
932 let query = QueryMultiMatchQuery $ mkMultiMatchQuery flds (QueryString "bitemyapp")
933 let search = mkSearch (Just query) Nothing
934 myTweet <- searchTweet search
936 myTweet `shouldBe` Right exampleTweet
938 it "returns document for bool query" $ withTestEnv $ do
940 let innerQuery = QueryMatchQuery $
941 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
942 let query = QueryBoolQuery $
943 mkBoolQuery [innerQuery] [] []
944 let search = mkSearch (Just query) Nothing
945 myTweet <- searchTweet search
947 myTweet `shouldBe` Right exampleTweet
949 it "returns document for boosting query" $ withTestEnv $ do
951 let posQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
952 let negQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "notmyapp")
953 let query = QueryBoostingQuery $ BoostingQuery posQuery negQuery (Boost 0.2)
954 let search = mkSearch (Just query) Nothing
955 myTweet <- searchTweet search
957 myTweet `shouldBe` Right exampleTweet
959 it "returns document for common terms query" $ withTestEnv $ do
961 let query = QueryCommonTermsQuery $
962 CommonTermsQuery (FieldName "user")
963 (QueryString "bitemyapp")
964 (CutoffFrequency 0.0001)
965 Or Or Nothing Nothing Nothing Nothing
966 let search = mkSearch (Just query) Nothing
967 myTweet <- searchTweet search
969 myTweet `shouldBe` Right exampleTweet
972 describe "sorting" $ do
973 it "returns documents in the right order" $ withTestEnv $ do
976 let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
977 let search = Search Nothing
978 (Just IdentityFilter) (Just [sortSpec]) Nothing Nothing
979 False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
980 result <- searchTweets search
981 let myTweet = grabFirst result
983 myTweet `shouldBe` Right otherTweet
986 describe "filtering API" $ do
987 it "returns document for composed boolmatch and identity" $ withTestEnv $ do
989 let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
991 let search = mkSearch Nothing (Just queryFilter)
992 myTweet <- searchTweet search
994 myTweet `shouldBe` Right exampleTweet
996 it "returns document for term filter" $ withTestEnv $ do
998 let termFilter = TermFilter (Term "user" "bitemyapp") False
999 let search = mkSearch Nothing (Just termFilter)
1000 myTweet <- searchTweet search
1002 myTweet `shouldBe` Right exampleTweet
1004 it "returns document for existential filter" $ withTestEnv $ do
1006 let search = mkSearch Nothing (Just (ExistsFilter (FieldName "user")))
1007 myTweet <- searchTweet search
1009 myTweet `shouldBe` Right exampleTweet
1011 it "returns document for geo boundingbox filter" $ withTestEnv $ do
1013 let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
1014 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
1015 let geoFilter = GeoBoundingBoxFilter bbConstraint
1016 let search = mkSearch Nothing (Just geoFilter)
1017 myTweet <- searchTweet search
1019 myTweet `shouldBe` Right exampleTweet
1021 it "doesn't return document for nonsensical boundingbox filter" $ withTestEnv $ do
1023 let box = GeoBoundingBox (LatLon 0.73 (-4.1)) (LatLon 0.10 (-1.12))
1024 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
1025 let geoFilter = GeoBoundingBoxFilter bbConstraint
1026 let search = mkSearch Nothing (Just geoFilter)
1027 searchExpectNoResults search
1029 it "returns document for geo distance filter" $ withTestEnv $ do
1031 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1032 let distance = Distance 10.0 Miles
1033 let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
1034 let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
1035 let search = mkSearch Nothing (Just geoFilter)
1036 myTweet <- searchTweet search
1038 myTweet `shouldBe` Right exampleTweet
1040 it "returns document for geo distance range filter" $ withTestEnv $ do
1042 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1043 let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
1044 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
1045 let search = mkSearch Nothing (Just geoFilter)
1046 myTweet <- searchTweet search
1048 myTweet `shouldBe` Right exampleTweet
1050 it "doesn't return document for wild geo distance range filter" $ withTestEnv $ do
1052 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1053 let distanceRange = DistanceRange (Distance 100.0 Miles) (Distance 1000.0 Miles)
1054 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
1055 let search = mkSearch Nothing (Just geoFilter)
1056 searchExpectNoResults search
1058 it "returns document for geo polygon filter" $ withTestEnv $ do
1060 let points = [LatLon 40.0 (-70.00),
1061 LatLon 40.0 (-72.00),
1062 LatLon 41.0 (-70.00),
1063 LatLon 41.0 (-72.00)]
1064 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
1065 let search = mkSearch Nothing (Just geoFilter)
1066 myTweet <- searchTweet search
1068 myTweet `shouldBe` Right exampleTweet
1070 it "doesn't return document for bad geo polygon filter" $ withTestEnv $ do
1072 let points = [LatLon 40.0 (-70.00),
1073 LatLon 40.0 (-71.00),
1074 LatLon 41.0 (-70.00),
1075 LatLon 41.0 (-71.00)]
1076 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
1077 let search = mkSearch Nothing (Just geoFilter)
1078 searchExpectNoResults search
1080 it "returns document for ids filter" $ withTestEnv $ do
1082 let filter = IdsFilter (MappingName "tweet") [DocId "1"]
1083 let search = mkSearch Nothing (Just filter)
1084 myTweet <- searchTweet search
1086 myTweet `shouldBe` Right exampleTweet
1088 it "returns document for Double range filter" $ withTestEnv $ do
1090 let filter = RangeFilter (FieldName "age")
1091 (RangeDoubleGtLt (GreaterThan 1000.0) (LessThan 100000.0))
1092 RangeExecutionIndex False
1093 let search = mkSearch Nothing (Just filter)
1094 myTweet <- searchTweet search
1096 myTweet `shouldBe` Right exampleTweet
1098 it "returns document for UTCTime date filter" $ withTestEnv $ do
1100 let filter = RangeFilter (FieldName "postDate")
1102 (GreaterThanD (UTCTime
1103 (ModifiedJulianDay 54000)
1104 (secondsToDiffTime 0)))
1106 (ModifiedJulianDay 55000)
1107 (secondsToDiffTime 11))))
1108 RangeExecutionIndex False
1109 let search = mkSearch Nothing (Just filter)
1110 myTweet <- searchTweet search
1112 myTweet `shouldBe` Right exampleTweet
1114 it "returns document for regexp filter" $ withTestEnv $ do
1116 let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
1117 AllRegexpFlags (CacheName "test") False (CacheKey "key")
1118 let search = mkSearch Nothing (Just filter)
1119 myTweet <- searchTweet search
1121 myTweet `shouldBe` Right exampleTweet
1123 it "doesn't return document for non-matching regexp filter" $ withTestEnv $ do
1125 let filter = RegexpFilter (FieldName "user")
1126 (Regexp "boy") AllRegexpFlags
1127 (CacheName "test") False (CacheKey "key")
1128 let search = mkSearch Nothing (Just filter)
1129 searchExpectNoResults search
1131 it "returns document for query filter, uncached" $ withTestEnv $ do
1133 let filter = QueryFilter (TermQuery (Term "user" "bitemyapp") Nothing) True
1134 search = mkSearch Nothing (Just filter)
1135 myTweet <- searchTweet search
1136 liftIO $ myTweet `shouldBe` Right exampleTweet
1138 it "returns document for query filter, cached" $ withTestEnv $ do
1140 let filter = QueryFilter (TermQuery (Term "user" "bitemyapp") Nothing) False
1141 search = mkSearch Nothing (Just filter)
1142 myTweet <- searchTweet search
1143 liftIO $ myTweet `shouldBe` Right exampleTweet
1145 describe "Aggregation API" $ do
1146 it "returns term aggregation results" $ withTestEnv $ do
1148 let terms = TermsAgg $ mkTermsAggregation "user"
1149 let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
1150 searchExpectAggs search
1151 searchValidBucketAgg search "users" toTerms
1153 it "can give collection hint parameters to term aggregations" $ when' (atleast es13) $ withTestEnv $ do
1155 let terms = TermsAgg $ (mkTermsAggregation "user") { termCollectMode = Just BreadthFirst }
1156 let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
1157 searchExpectAggs search
1158 searchValidBucketAgg search "users" toTerms
1160 -- One of these fails with 1.7.3
1161 it "can give execution hint parameters to term aggregations" $ when' (atmost es11) $ withTestEnv $ do
1163 searchTermsAggHint [Map, Ordinals]
1165 it "can give execution hint parameters to term aggregations" $ when' (is es12) $ withTestEnv $ do
1167 searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map, Ordinals]
1169 it "can give execution hint parameters to term aggregations" $ when' (atleast es12) $ withTestEnv $ do
1171 searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map]
1172 -- One of the above.
1174 it "can execute value_count aggregations" $ withTestEnv $ do
1177 let ags = mkAggregations "user_count" (ValueCountAgg (FieldValueCount (FieldName "user"))) <>
1178 mkAggregations "bogus_count" (ValueCountAgg (FieldValueCount (FieldName "bogus")))
1179 let search = mkAggregateSearch Nothing ags
1180 let docCountPair k n = (k, object ["value" .= Number n])
1181 res <- searchTweets search
1183 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "user_count" 2
1184 , docCountPair "bogus_count" 0
1187 it "can execute filter aggregations" $ withTestEnv $ do
1190 let ags = mkAggregations "bitemyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "bitemyapp") defaultCache) Nothing)) <>
1191 mkAggregations "notmyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "notmyapp") defaultCache) Nothing))
1192 let search = mkAggregateSearch Nothing ags
1193 let docCountPair k n = (k, object ["doc_count" .= Number n])
1194 res <- searchTweets search
1196 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "bitemyapps" 1
1197 , docCountPair "notmyapps" 1
1199 it "can execute date_range aggregations" $ withTestEnv $ do
1200 let now = fromGregorian 2015 3 14
1201 let ltAMonthAgo = UTCTime (fromGregorian 2015 3 1) 0
1202 let ltAWeekAgo = UTCTime (fromGregorian 2015 3 10) 0
1203 let oldDoc = exampleTweet { postDate = ltAMonthAgo }
1204 let newDoc = exampleTweet { postDate = ltAWeekAgo }
1205 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings oldDoc (DocId "1")
1206 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings newDoc (DocId "2")
1207 _ <- refreshIndex testIndex
1208 let thisMonth = DateRangeFrom (DateMathExpr (DMDate now) [SubtractTime 1 DMMonth])
1209 let thisWeek = DateRangeFrom (DateMathExpr (DMDate now) [SubtractTime 1 DMWeek])
1210 let agg = DateRangeAggregation (FieldName "postDate") Nothing (thisMonth :| [thisWeek])
1211 let ags = mkAggregations "date_ranges" (DateRangeAgg agg)
1212 let search = mkAggregateSearch Nothing ags
1213 res <- searchTweets search
1214 liftIO $ hitsTotal . searchHits <$> res `shouldBe` Right 2
1215 let bucks = do magrs <- fmapL show (aggregations <$> res)
1216 agrs <- note "no aggregations returned" magrs
1217 rawBucks <- note "no date_ranges aggregation" $ M.lookup "date_ranges" agrs
1218 parseEither parseJSON rawBucks
1219 let fromMonthT = UTCTime (fromGregorian 2015 2 14) 0
1220 let fromWeekT = UTCTime (fromGregorian 2015 3 7) 0
1221 liftIO $ buckets <$> bucks `shouldBe` Right [ DateRangeResult "2015-02-14T00:00:00.000Z-*"
1223 (Just "2015-02-14T00:00:00.000Z")
1228 , DateRangeResult "2015-03-07T00:00:00.000Z-*"
1230 (Just "2015-03-07T00:00:00.000Z")
1237 it "returns date histogram aggregation results" $ withTestEnv $ do
1239 let histogram = DateHistogramAgg $ mkDateHistogram (FieldName "postDate") Minute
1240 let search = mkAggregateSearch Nothing (mkAggregations "byDate" histogram)
1241 searchExpectAggs search
1242 searchValidBucketAgg search "byDate" toDateHistogram
1244 it "returns date histogram using fractional date" $ withTestEnv $ do
1246 let periods = [Year, Quarter, Month, Week, Day, Hour, Minute, Second]
1247 let fractionals = map (FractionalInterval 1.5) [Weeks, Days, Hours, Minutes, Seconds]
1248 let intervals = periods ++ fractionals
1249 let histogram = mkDateHistogram (FieldName "postDate")
1250 let search interval = mkAggregateSearch Nothing $ mkAggregations "byDate" $ DateHistogramAgg (histogram interval)
1251 let expect interval = searchExpectAggs (search interval)
1252 let valid interval = searchValidBucketAgg (search interval) "byDate" toDateHistogram
1253 forM_ intervals expect
1254 forM_ intervals valid
1256 it "can execute missing aggregations" $ withTestEnv $ do
1259 let ags = mkAggregations "missing_agg" (MissingAgg (MissingAggregation "extra"))
1260 let search = mkAggregateSearch Nothing ags
1261 let docCountPair k n = (k, object ["doc_count" .= Number n])
1262 res <- searchTweets search
1264 fmap aggregations res `shouldBe` Right (Just (M.fromList [docCountPair "missing_agg" 1]))
1266 describe "Highlights API" $ do
1268 it "returns highlight from query when there should be one" $ withTestEnv $ do
1271 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
1272 let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing]
1274 let search = mkHighlightSearch (Just query) testHighlight
1275 myHighlight <- searchTweetHighlight search
1277 myHighlight `shouldBe` Right (Just (M.fromList [("message",["Use <em>haskell</em>!"])]))
1279 it "doesn't return highlight from a query when it shouldn't" $ withTestEnv $ do
1282 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
1283 let testHighlight = Highlights Nothing [FieldHighlight (FieldName "user") Nothing]
1285 let search = mkHighlightSearch (Just query) testHighlight
1286 myHighlight <- searchTweetHighlight search
1288 myHighlight `shouldBe` Right Nothing
1290 describe "Source filtering" $ do
1292 it "doesn't include source when sources are disabled" $ withTestEnv $ do
1295 (Left (EsError 500 "Source was missing"))
1297 it "includes a source" $ withTestEnv $ do
1299 (SourcePatterns (PopPattern (Pattern "message")))
1300 (Right (Object (HM.fromList [("message", String "Use haskell!")])))
1302 it "includes sources" $ withTestEnv $ do
1304 (SourcePatterns (PopPatterns [Pattern "user", Pattern "message"]))
1305 (Right (Object (HM.fromList [("user",String "bitemyapp"),("message", String "Use haskell!")])))
1307 it "includes source patterns" $ withTestEnv $ do
1309 (SourcePatterns (PopPattern (Pattern "*ge")))
1310 (Right (Object (HM.fromList [("age", Number 10000),("message", String "Use haskell!")])))
1312 it "excludes source patterns" $ withTestEnv $ do
1314 (SourceIncludeExclude (Include [])
1315 (Exclude [Pattern "l*", Pattern "*ge", Pattern "postDate", Pattern "extra"]))
1316 (Right (Object (HM.fromList [("user",String "bitemyapp")])))
1318 describe "ToJSON RegexpFlags" $ do
1319 it "generates the correct JSON for AllRegexpFlags" $
1320 toJSON AllRegexpFlags `shouldBe` String "ALL"
1322 it "generates the correct JSON for NoRegexpFlags" $
1323 toJSON NoRegexpFlags `shouldBe` String "NONE"
1325 it "generates the correct JSON for SomeRegexpFlags" $
1326 let flags = AnyString :| [ Automaton
1331 in toJSON (SomeRegexpFlags flags) `shouldBe` String "ANYSTRING|AUTOMATON|COMPLEMENT|EMPTY|INTERSECTION|INTERVAL"
1333 prop "removes duplicates from flags" $ \(flags :: RegexpFlags) ->
1334 let String str = toJSON flags
1335 flagStrs = T.splitOn "|" str
1336 in noDuplicates flagStrs
1338 describe "omitNulls" $ do
1339 it "checks that omitNulls drops list elements when it should" $
1340 let dropped = omitNulls $ [ "test1" .= (toJSON ([] :: [Int]))
1341 , "test2" .= (toJSON ("some value" :: Text))]
1342 in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
1344 it "checks that omitNulls doesn't drop list elements when it shouldn't" $
1345 let notDropped = omitNulls $ [ "test1" .= (toJSON ([1] :: [Int]))
1346 , "test2" .= (toJSON ("some value" :: Text))]
1347 in notDropped `shouldBe` Object (HM.fromList [ ("test1", Array (V.fromList [Number 1.0]))
1348 , ("test2", String "some value")])
1349 it "checks that omitNulls drops non list elements when it should" $
1350 let dropped = omitNulls $ [ "test1" .= (toJSON Null)
1351 , "test2" .= (toJSON ("some value" :: Text))]
1352 in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
1353 it "checks that omitNulls doesn't drop non list elements when it shouldn't" $
1354 let notDropped = omitNulls $ [ "test1" .= (toJSON (1 :: Int))
1355 , "test2" .= (toJSON ("some value" :: Text))]
1356 in notDropped `shouldBe` Object (HM.fromList [ ("test1", Number 1.0)
1357 , ("test2", String "some value")])
1358 describe "Monoid (SearchHits a)" $ do
1359 prop "abides the monoid laws" $ eq $
1360 prop_Monoid (T :: T (SearchHits ()))
1362 describe "mkDocVersion" $ do
1363 prop "can never construct an out of range docVersion" $ \i ->
1364 let res = mkDocVersion i
1366 Nothing -> property True
1367 Just dv -> (dv >= minBound) .&&.
1368 (dv <= maxBound) .&&.
1369 docVersionNumber dv === i
1371 describe "FsSnapshotRepo" $ do
1372 prop "SnapshotRepo laws" $ \fsr ->
1373 fromGSnapshotRepo (toGSnapshotRepo fsr) === Right (fsr :: FsSnapshotRepo)
1375 describe "snapshot repos" $ do
1376 it "always parses all snapshots API" $ withTestEnv $ do
1377 res <- getSnapshotRepos AllSnapshotRepos
1378 liftIO $ case res of
1379 Left e -> expectationFailure ("Expected a right but got Left " <> show e)
1380 Right _ -> return ()
1382 describe "Enum DocVersion" $ do
1383 it "follows the laws of Enum, Bounded" $ do
1384 evaluate (succ maxBound :: DocVersion) `shouldThrow` anyErrorCall
1385 evaluate (pred minBound :: DocVersion) `shouldThrow` anyErrorCall
1386 evaluate (toEnum 0 :: DocVersion) `shouldThrow` anyErrorCall
1387 evaluate (toEnum 9200000000000000001 :: DocVersion) `shouldThrow` anyErrorCall
1388 enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
1389 enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
1390 enumFromThen minBound (pred maxBound :: DocVersion) `shouldBe` [minBound, pred maxBound]
1392 describe "scan&scroll API" $ do
1393 it "returns documents using the scan&scroll API" $ withTestEnv $ do
1396 let search = (mkSearch (Just $ MatchAllQuery Nothing) Nothing) { size = (Size 1) }
1397 regular_search <- searchTweet search
1398 scan_search' <- scanSearch testIndex testMapping search :: BH IO [Hit Tweet]
1399 let scan_search = map hitSource scan_search'
1401 regular_search `shouldBe` Right exampleTweet -- Check that the size restrtiction is being honored
1403 scan_search `shouldMatchList` [Just exampleTweet, Just otherTweet]
1405 describe "index aliases" $ do
1406 it "handles the simple case of aliasing an existing index" $ do
1407 let alias = IndexAlias (testIndex) (IndexAliasName (IndexName "bloodhound-tests-twitter-1-alias"))
1408 let create = IndexAliasCreate Nothing Nothing
1409 let action = AddAlias alias create
1413 resp <- updateIndexAliases (action :| [])
1414 liftIO $ validateStatus resp 200
1415 let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
1416 (do aliases <- withTestEnv getIndexAliases
1417 let expected = IndexAliasSummary alias create
1419 Right (IndexAliasesSummary summs) ->
1420 L.find ((== alias) . indexAliasSummaryAlias) summs `shouldBe` Just expected
1421 Left e -> expectationFailure ("Expected an IndexAliasesSummary but got " <> show e)) `finally` cleanup
1423 it "handles an alias with routing and a filter" $ do
1424 let alias = IndexAlias (testIndex) (IndexAliasName (IndexName "bloodhound-tests-twitter-1-alias"))
1425 let sar = SearchAliasRouting (RoutingValue "search val" :| [])
1426 let iar = IndexAliasRouting (RoutingValue "index val")
1427 let routing = GranularAliasRouting (Just sar) (Just iar)
1428 let filter = LimitFilter 42
1429 let create = IndexAliasCreate (Just routing) (Just filter)
1430 let action = AddAlias alias create
1434 resp <- updateIndexAliases (action :| [])
1435 liftIO $ validateStatus resp 200
1436 let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
1437 (do aliases <- withTestEnv getIndexAliases
1438 let expected = IndexAliasSummary alias create
1440 Right (IndexAliasesSummary summs) ->
1441 L.find ((== alias) . indexAliasSummaryAlias) summs `shouldBe` Just expected
1442 Left e -> expectationFailure ("Expected an IndexAliasesSummary but got " <> show e)) `finally` cleanup
1444 describe "Index Listing" $ do
1445 it "returns a list of index names" $ withTestEnv $ do
1446 _ <- createExampleIndex
1448 liftIO (ixns `shouldContain` [testIndex])
1450 describe "Index Settings" $ do
1451 it "persists settings" $ withTestEnv $ do
1452 _ <- deleteExampleIndex
1453 _ <- createExampleIndex
1454 let updates = BlocksWrite False :| []
1455 updateResp <- updateIndexSettings updates testIndex
1456 liftIO $ validateStatus updateResp 200
1457 getResp <- getIndexSettings testIndex
1459 getResp `shouldBe` Right (IndexSettingsSummary
1461 (IndexSettings (ShardCount 1) (ReplicaCount 0))
1462 (NE.toList updates))
1464 describe "Index Optimization" $ do
1465 it "returns a successful response upon completion" $ withTestEnv $ do
1466 _ <- createExampleIndex
1467 resp <- optimizeIndex (IndexList (testIndex :| [])) defaultIndexOptimizationSettings
1468 liftIO $ validateStatus resp 200
1470 describe "JSON instances" $ do
1471 propJSON (Proxy :: Proxy Version)
1472 propJSON (Proxy :: Proxy IndexName)
1473 propJSON (Proxy :: Proxy MappingName)
1474 propJSON (Proxy :: Proxy DocId)
1475 propJSON (Proxy :: Proxy IndexAliasRouting)
1476 propJSON (Proxy :: Proxy RoutingValue)
1477 propJSON (Proxy :: Proxy ShardCount)
1478 propJSON (Proxy :: Proxy ReplicaCount)
1479 propJSON (Proxy :: Proxy TemplateName)
1480 propJSON (Proxy :: Proxy TemplatePattern)
1481 propJSON (Proxy :: Proxy QueryString)
1482 propJSON (Proxy :: Proxy FieldName)
1483 propJSON (Proxy :: Proxy CacheName)
1484 propJSON (Proxy :: Proxy CacheKey)
1485 propJSON (Proxy :: Proxy Existence)
1486 propJSON (Proxy :: Proxy CutoffFrequency)
1487 propJSON (Proxy :: Proxy Analyzer)
1488 propJSON (Proxy :: Proxy MaxExpansions)
1489 propJSON (Proxy :: Proxy Lenient)
1490 propJSON (Proxy :: Proxy Tiebreaker)
1491 propJSON (Proxy :: Proxy Boost)
1492 propJSON (Proxy :: Proxy BoostTerms)
1493 propJSON (Proxy :: Proxy MinimumMatch)
1494 propJSON (Proxy :: Proxy DisableCoord)
1495 propJSON (Proxy :: Proxy IgnoreTermFrequency)
1496 propJSON (Proxy :: Proxy MinimumTermFrequency)
1497 propJSON (Proxy :: Proxy MaxQueryTerms)
1498 propJSON (Proxy :: Proxy Fuzziness)
1499 propJSON (Proxy :: Proxy PrefixLength)
1500 propJSON (Proxy :: Proxy TypeName)
1501 propJSON (Proxy :: Proxy PercentMatch)
1502 propJSON (Proxy :: Proxy StopWord)
1503 propJSON (Proxy :: Proxy QueryPath)
1504 propJSON (Proxy :: Proxy AllowLeadingWildcard)
1505 propJSON (Proxy :: Proxy LowercaseExpanded)
1506 propJSON (Proxy :: Proxy EnablePositionIncrements)
1507 propJSON (Proxy :: Proxy AnalyzeWildcard)
1508 propJSON (Proxy :: Proxy GeneratePhraseQueries)
1509 propJSON (Proxy :: Proxy Locale)
1510 propJSON (Proxy :: Proxy MaxWordLength)
1511 propJSON (Proxy :: Proxy MinWordLength)
1512 propJSON (Proxy :: Proxy PhraseSlop)
1513 propJSON (Proxy :: Proxy MinDocFrequency)
1514 propJSON (Proxy :: Proxy MaxDocFrequency)
1515 propJSON (Proxy :: Proxy Filter)
1516 propJSON (Proxy :: Proxy Query)
1517 propJSON (Proxy :: Proxy SimpleQueryStringQuery)
1518 propJSON (Proxy :: Proxy FieldOrFields)
1519 propJSON (Proxy :: Proxy SimpleQueryFlag)
1520 propJSON (Proxy :: Proxy RegexpQuery)
1521 propJSON (Proxy :: Proxy QueryStringQuery)
1522 propJSON (Proxy :: Proxy RangeQuery)
1523 propJSON (Proxy :: Proxy PrefixQuery)
1524 propJSON (Proxy :: Proxy NestedQuery)
1525 propJSON (Proxy :: Proxy MoreLikeThisFieldQuery)
1526 propJSON (Proxy :: Proxy MoreLikeThisQuery)
1527 propJSON (Proxy :: Proxy IndicesQuery)
1528 propJSON (Proxy :: Proxy HasParentQuery)
1529 propJSON (Proxy :: Proxy HasChildQuery)
1530 propJSON (Proxy :: Proxy FuzzyQuery)
1531 propJSON (Proxy :: Proxy FuzzyLikeFieldQuery)
1532 propJSON (Proxy :: Proxy FuzzyLikeThisQuery)
1533 propJSON (Proxy :: Proxy FilteredQuery)
1534 propJSON (Proxy :: Proxy DisMaxQuery)
1535 propJSON (Proxy :: Proxy CommonTermsQuery)
1536 propJSON (Proxy :: Proxy CommonMinimumMatch)
1537 propJSON (Proxy :: Proxy BoostingQuery)
1538 propJSON (Proxy :: Proxy BoolQuery)
1539 propJSON (Proxy :: Proxy MatchQuery)
1540 propJSON (Proxy :: Proxy MultiMatchQueryType)
1541 propJSON (Proxy :: Proxy BooleanOperator)
1542 propJSON (Proxy :: Proxy ZeroTermsQuery)
1543 propJSON (Proxy :: Proxy MatchQueryType)
1544 propJSON (Proxy :: Proxy AliasRouting)
1545 propJSON (Proxy :: Proxy IndexAliasCreate)
1546 propJSON (Proxy :: Proxy SearchAliasRouting)
1547 propJSON (Proxy :: Proxy ScoreType)
1548 propJSON (Proxy :: Proxy Distance)
1549 propJSON (Proxy :: Proxy DistanceUnit)
1550 propJSON (Proxy :: Proxy DistanceType)
1551 propJSON (Proxy :: Proxy OptimizeBbox)
1552 propJSON (Proxy :: Proxy GeoBoundingBoxConstraint)
1553 propJSON (Proxy :: Proxy GeoFilterType)
1554 propJSON (Proxy :: Proxy GeoBoundingBox)
1555 propJSON (Proxy :: Proxy LatLon)
1556 propJSON (Proxy :: Proxy RangeExecution)
1557 prop "RegexpFlags FromJSON/ToJSON roundtrips, removing dups " $ \rfs ->
1558 let expected = case rfs of
1559 SomeRegexpFlags fs -> SomeRegexpFlags (NE.fromList (nub (NE.toList fs)))
1561 in parseEither parseJSON (toJSON rfs) === Right expected
1562 propJSON (Proxy :: Proxy BoolMatch)
1563 propJSON (Proxy :: Proxy Term)
1564 propJSON (Proxy :: Proxy MultiMatchQuery)
1565 propJSON (Proxy :: Proxy IndexSettings)
1566 propJSON (Proxy :: Proxy UpdatableIndexSetting)
1567 propJSON (Proxy :: Proxy ReplicaBounds)
1568 propJSON (Proxy :: Proxy Bytes)
1569 propJSON (Proxy :: Proxy AllocationPolicy)
1570 propJSON (Proxy :: Proxy InitialShardCount)
1571 propJSON (Proxy :: Proxy FSType)
1572 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