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)
70es14 = ServerVersion 1 4 0
73es13 = ServerVersion 1 3 0
76es12 = ServerVersion 1 2 0
79es11 = ServerVersion 1 1 0
82es10 = ServerVersion 1 0 0
84serverBranch :: ServerVersion -> ServerVersion
85serverBranch (ServerVersion majorVer minorVer patchVer) =
86 ServerVersion majorVer minorVer patchVer
88mkServerVersion :: [Int] -> Maybe ServerVersion
89mkServerVersion [majorVer, minorVer, patchVer] =
90 Just (ServerVersion majorVer minorVer patchVer)
91mkServerVersion _ = Nothing
93getServerVersion :: IO (Maybe ServerVersion)
94getServerVersion = liftM extractVersion (withTestEnv getStatus)
96 version' = T.splitOn "." . number . version
97 toInt = read . T.unpack
98 parseVersion v = map toInt (version' v)
99 extractVersion = join . liftM (mkServerVersion . parseVersion)
101testServerBranch :: IO (Maybe ServerVersion)
102testServerBranch = getServerVersion >>= \v -> return $ liftM serverBranch v
104atleast :: ServerVersion -> IO Bool
105atleast v = testServerBranch >>= \x -> return $ x >= Just (serverBranch v)
107atmost :: ServerVersion -> IO Bool
108atmost v = testServerBranch >>= \x -> return $ x <= Just (serverBranch v)
110is :: ServerVersion -> IO Bool
111is v = testServerBranch >>= \x -> return $ x == Just (serverBranch v)
113when' :: Monad m => m Bool -> m () -> m ()
114when' b f = b >>= \x -> when x f
116(==~) :: (ApproxEq a, Show a) => a -> a -> Property
117a ==~ b = counterexample (show a ++ " !=~ " ++ show b) (a =~ b)
119propJSON :: forall a. (Arbitrary a, ToJSON a, FromJSON a, Show a, ApproxEq a, Typeable a) => Proxy a -> Spec
120propJSON _ = prop testName $ \(a :: a) ->
121 let jsonStr = "via " <> BL8.unpack (encode a)
122 in counterexample jsonStr (parseEither parseJSON (toJSON a) ==~ Right a)
123 where testName = show ty <> " FromJSON/ToJSON roundtrips"
124 ty = typeOf (undefined :: a)
126data Location = Location { lat :: Double
127 , lon :: Double } deriving (Eq, Generic, Show)
129data Tweet = Tweet { user :: Text
130 , postDate :: UTCTime
133 , location :: Location }
134 deriving (Eq, Generic, Show)
136instance ToJSON Tweet where
137 toJSON = genericToJSON defaultOptions
138instance FromJSON Tweet where
139 parseJSON = genericParseJSON defaultOptions
140instance ToJSON Location where
141 toJSON = genericToJSON defaultOptions
142instance FromJSON Location where
143 parseJSON = genericParseJSON defaultOptions
145data ParentMapping = ParentMapping deriving (Eq, Show)
147instance ToJSON ParentMapping where
148 toJSON ParentMapping =
149 object ["parent" .= Null ]
151data ChildMapping = ChildMapping deriving (Eq, Show)
153instance ToJSON ChildMapping where
154 toJSON ChildMapping =
156 object ["_parent" .= object ["type" .= ("parent" :: Text)]]
159data TweetMapping = TweetMapping deriving (Eq, Show)
161instance ToJSON TweetMapping where
162 toJSON TweetMapping =
164 object ["properties" .=
165 object [ "user" .= object ["type" .= ("string" :: Text)]
166 -- Serializing the date as a date is breaking other tests, mysteriously.
167 -- , "postDate" .= object [ "type" .= ("date" :: Text)
168 -- , "format" .= ("YYYY-MM-dd`T`HH:mm:ss.SSSZZ" :: Text)]
169 , "message" .= object ["type" .= ("string" :: Text)]
170 , "age" .= object ["type" .= ("integer" :: Text)]
171 , "location" .= object ["type" .= ("geo_point" :: Text)]
175exampleTweet = Tweet { user = "bitemyapp"
177 (ModifiedJulianDay 55000)
178 (secondsToDiffTime 10)
179 , message = "Use haskell!"
181 , location = Location 40.12 (-71.34) }
187newUser = "someotherapp"
191 object [ "age" .= newAge
196patchedTweet = exampleTweet{age = newAge, user = newUser}
199otherTweet = Tweet { user = "notmyapp"
201 (ModifiedJulianDay 55000)
202 (secondsToDiffTime 11)
203 , message = "Use haskell!"
205 , location = Location 40.12 (-71.34) }
207resetIndex :: BH IO ()
209 _ <- deleteExampleIndex
210 _ <- createExampleIndex
211 _ <- putMapping testIndex testMapping TweetMapping
214insertData :: BH IO Reply
217 insertData' defaultIndexDocumentSettings
219insertData' :: IndexDocumentSettings -> BH IO Reply
221 r <- indexDocument testIndex testMapping ids exampleTweet (DocId "1")
222 _ <- refreshIndex testIndex
225updateData :: BH IO Reply
227 r <- updateDocument testIndex testMapping defaultIndexDocumentSettings tweetPatch (DocId "1")
228 _ <- refreshIndex testIndex
231insertOther :: BH IO ()
233 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings otherTweet (DocId "2")
234 _ <- refreshIndex testIndex
237insertWithSpaceInId :: BH IO ()
238insertWithSpaceInId = do
239 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "Hello World")
240 _ <- refreshIndex testIndex
243searchTweet :: Search -> BH IO (Either EsError Tweet)
244searchTweet search = do
245 result <- searchTweets search
246 let myTweet :: Either EsError Tweet
247 myTweet = grabFirst result
250searchTweets :: Search -> BH IO (Either EsError (SearchResult Tweet))
251searchTweets search = parseEsResponse =<< searchByIndex testIndex search
253searchExpectNoResults :: Search -> BH IO ()
254searchExpectNoResults search = do
255 result <- searchTweets search
256 let emptyHits = fmap (hits . searchHits) result
258 emptyHits `shouldBe` Right []
260searchExpectAggs :: Search -> BH IO ()
261searchExpectAggs search = do
262 reply <- searchByIndex testIndex search
263 let isEmpty x = return (M.null x)
264 let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
266 (result >>= aggregations >>= isEmpty) `shouldBe` Just False
268searchValidBucketAgg :: (BucketAggregation a, FromJSON a, Show a) =>
269 Search -> Text -> (Text -> AggregationResults -> Maybe (Bucket a)) -> BH IO ()
270searchValidBucketAgg search aggKey extractor = do
271 reply <- searchByIndex testIndex search
272 let bucketDocs = docCount . head . buckets
273 let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
274 let count = result >>= aggregations >>= extractor aggKey >>= \x -> return (bucketDocs x)
276 count `shouldBe` Just 1
278searchTermsAggHint :: [ExecutionHint] -> BH IO ()
279searchTermsAggHint hints = do
280 let terms hint = TermsAgg $ (mkTermsAggregation "user") { termExecutionHint = Just hint }
281 let search hint = mkAggregateSearch Nothing $ mkAggregations "users" $ terms hint
282 forM_ hints $ searchExpectAggs . search
283 forM_ hints (\x -> searchValidBucketAgg (search x) "users" toTerms)
285searchTweetHighlight :: Search -> BH IO (Either EsError (Maybe HitHighlight))
286searchTweetHighlight search = do
287 result <- searchTweets search
288 let myHighlight = fmap (hitHighlight . head . hits . searchHits) result
291searchExpectSource :: Source -> Either EsError Value -> BH IO ()
292searchExpectSource src expected = do
294 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
295 let search = (mkSearch (Just query) Nothing) { source = Just src }
296 reply <- searchByIndex testIndex search
297 result <- parseEsResponse reply
298 let value = grabFirst result
300 value `shouldBe` expected
302data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
303instance FromJSON BulkTest where
304 parseJSON = genericParseJSON defaultOptions
305instance ToJSON BulkTest where
306 toJSON = genericToJSON defaultOptions
308class GApproxEq f where
309 gApproxEq :: f a -> f a -> Bool
312instance GApproxEq U1 where
313 gApproxEq U1 U1 = True
315-- | Sum type, ensure same constructors, recurse
316instance (GApproxEq a, GApproxEq b) => GApproxEq (a :+: b) where
317 gApproxEq (L1 a) (L1 b) = gApproxEq a b
318 gApproxEq (R1 a) (R1 b) = gApproxEq a b
319 gApproxEq _ _ = False
321-- | Product type, ensure each field is approx eq
322instance (GApproxEq a, GApproxEq b) => GApproxEq (a :*: b) where
323 gApproxEq (a1 :*: b1) (a2 :*: b2) = gApproxEq a1 a2 && gApproxEq b1 b2
325-- | Value type, actually check the values for approx equality
326instance (ApproxEq a) => GApproxEq (K1 i a) where
327 gApproxEq (K1 a) (K1 b) = a =~ b
329instance (GApproxEq f) => GApproxEq (M1 i t f) where
330 gApproxEq (M1 a) (M1 b) = gApproxEq a b
332-- | Typeclass for "equal where it matters". Use this to specify
333-- less-strict equivalence for things such as lists that can wind up
334-- in an unpredictable order
335class ApproxEq a where
336 (=~) :: a -> a -> Bool
337 default (=~) :: (Generic a, GApproxEq (Rep a)) => a -> a -> Bool
338 a =~ b = gApproxEq (G.from a) (G.from b)
340instance ApproxEq NominalDiffTime where (=~) = (==)
341instance ApproxEq UTCTime where (=~) = (==)
342instance ApproxEq Text where (=~) = (==)
343instance ApproxEq Bool where (=~) = (==)
344instance ApproxEq Int where (=~) = (==)
345instance ApproxEq Double where (=~) = (==)
346instance ApproxEq a => ApproxEq (NonEmpty a)
347instance ApproxEq a => ApproxEq (Maybe a)
348instance ApproxEq GeoPoint
349instance ApproxEq Regexp
350instance ApproxEq RangeValue
351instance ApproxEq LessThan
352instance ApproxEq LessThanEq
353instance ApproxEq LessThanD
354instance ApproxEq LessThanEqD
355instance ApproxEq GreaterThan
356instance ApproxEq GreaterThanEq
357instance ApproxEq GreaterThanD
358instance ApproxEq GreaterThanEqD
359instance ApproxEq MinimumMatchHighLow
360instance ApproxEq RegexpFlag
361instance ApproxEq RegexpFlags
362instance ApproxEq NullValue
363instance ApproxEq Version
364instance ApproxEq DistanceRange
365instance ApproxEq IndexName
366instance ApproxEq MappingName
367instance ApproxEq DocId
368instance ApproxEq IndexAliasRouting
369instance ApproxEq RoutingValue
370instance ApproxEq ShardCount
371instance ApproxEq ReplicaCount
372instance ApproxEq TemplateName
373instance ApproxEq TemplatePattern
374instance ApproxEq QueryString
375instance ApproxEq FieldName
376instance ApproxEq CacheName
377instance ApproxEq CacheKey
378instance ApproxEq Existence
379instance ApproxEq CutoffFrequency
380instance ApproxEq Analyzer
381instance ApproxEq Lenient
382instance ApproxEq Tiebreaker
383instance ApproxEq Boost
384instance ApproxEq BoostTerms
385instance ApproxEq MaxExpansions
386instance ApproxEq MinimumMatch
387instance ApproxEq DisableCoord
388instance ApproxEq IgnoreTermFrequency
389instance ApproxEq MinimumTermFrequency
390instance ApproxEq MaxQueryTerms
391instance ApproxEq Fuzziness
392instance ApproxEq PrefixLength
393instance ApproxEq TypeName
394instance ApproxEq PercentMatch
395instance ApproxEq StopWord
396instance ApproxEq QueryPath
397instance ApproxEq AllowLeadingWildcard
398instance ApproxEq LowercaseExpanded
399instance ApproxEq EnablePositionIncrements
400instance ApproxEq AnalyzeWildcard
401instance ApproxEq GeneratePhraseQueries
402instance ApproxEq Locale
403instance ApproxEq MaxWordLength
404instance ApproxEq MinWordLength
405instance ApproxEq PhraseSlop
406instance ApproxEq MinDocFrequency
407instance ApproxEq MaxDocFrequency
408instance ApproxEq Filter
409instance ApproxEq Query
410instance ApproxEq SimpleQueryStringQuery
411instance ApproxEq FieldOrFields
412instance ApproxEq SimpleQueryFlag
413instance ApproxEq RegexpQuery
414instance ApproxEq QueryStringQuery
415instance ApproxEq RangeQuery
416instance ApproxEq PrefixQuery
417instance ApproxEq NestedQuery
418instance ApproxEq MoreLikeThisFieldQuery
419instance ApproxEq MoreLikeThisQuery
420instance ApproxEq IndicesQuery
421instance ApproxEq HasParentQuery
422instance ApproxEq HasChildQuery
423instance ApproxEq FuzzyQuery
424instance ApproxEq FuzzyLikeFieldQuery
425instance ApproxEq FuzzyLikeThisQuery
426instance ApproxEq FilteredQuery
427instance ApproxEq DisMaxQuery
428instance ApproxEq CommonTermsQuery
429instance ApproxEq CommonMinimumMatch
430instance ApproxEq BoostingQuery
431instance ApproxEq BoolQuery
432instance ApproxEq MatchQuery
433instance ApproxEq MultiMatchQueryType
434instance ApproxEq BooleanOperator
435instance ApproxEq ZeroTermsQuery
436instance ApproxEq MatchQueryType
437instance ApproxEq AliasRouting
438instance ApproxEq IndexAliasCreate
439instance ApproxEq SearchAliasRouting
440instance ApproxEq ScoreType
441instance ApproxEq Distance
442instance ApproxEq DistanceUnit
443instance ApproxEq DistanceType
444instance ApproxEq OptimizeBbox
445instance ApproxEq GeoBoundingBoxConstraint
446instance ApproxEq GeoFilterType
447instance ApproxEq GeoBoundingBox
448instance ApproxEq LatLon
449instance ApproxEq RangeExecution
450instance ApproxEq FSType
451instance ApproxEq CompoundFormat
452instance ApproxEq InitialShardCount
453instance ApproxEq Bytes
454instance ApproxEq ReplicaBounds
455instance ApproxEq Term
456instance ApproxEq BoolMatch
457instance ApproxEq MultiMatchQuery
458instance ApproxEq IndexSettings
459instance ApproxEq AllocationPolicy
460instance ApproxEq Char
461instance ApproxEq a => ApproxEq [a] where
462 as =~ bs = and (zipWith (=~) as bs)
463instance (ApproxEq l, ApproxEq r) => ApproxEq (Either l r) where
464 Left a =~ Left b = a =~ b
465 Right a =~ Right b = a =~ b
467instance ApproxEq NodeAttrFilter
468instance ApproxEq NodeAttrName
470-- | Due to the way nodeattrfilters get serialized here, they may come
471-- out in a different order, but they are morally equivalent
472instance ApproxEq UpdatableIndexSetting where
473 RoutingAllocationInclude a =~ RoutingAllocationInclude b =
474 NE.sort a =~ NE.sort b
475 RoutingAllocationExclude a =~ RoutingAllocationExclude b =
476 NE.sort a =~ NE.sort b
477 RoutingAllocationRequire a =~ RoutingAllocationRequire b =
478 NE.sort a =~ NE.sort b
482noDuplicates :: Eq a => [a] -> Bool
483noDuplicates xs = nub xs == xs
485instance Arbitrary NominalDiffTime where
486 arbitrary = fromInteger <$> arbitrary
488instance (Arbitrary k, Ord k, Arbitrary v) => Arbitrary (M.Map k v) where
489 arbitrary = M.fromList <$> arbitrary
491instance Arbitrary Text where
492 arbitrary = T.pack <$> arbitrary
494instance Arbitrary UTCTime where
497 <*> (fromRational . toRational <$> choose (0::Double, 86400))
499instance Arbitrary Day where
500 arbitrary = ModifiedJulianDay <$> (2000 +) <$> arbitrary
501 shrink = (ModifiedJulianDay <$>) . shrink . toModifiedJulianDay
503instance Arbitrary a => Arbitrary (NonEmpty a) where
504 arbitrary = liftA2 (:|) arbitrary arbitrary
506arbitraryScore :: Gen Score
507arbitraryScore = fmap getPositive <$> arbitrary
509instance (Arbitrary a, Typeable a) => Arbitrary (Hit a) where
510 arbitrary = Hit <$> arbitrary
516 shrink = genericShrink
519instance (Arbitrary a, Typeable a) => Arbitrary (SearchHits a) where
520 arbitrary = reduceSize $ do
521 tot <- getPositive <$> arbitrary
522 score <- arbitraryScore
524 return $ SearchHits tot score hs
525 shrink = genericShrink
527reduceSize :: Gen a -> Gen a
528reduceSize f = sized $ \n -> resize (n `div` 2) f
530getSource :: EsResult a -> Maybe a
531getSource = fmap _source . foundResult
533grabFirst :: Either EsError (SearchResult a) -> Either EsError a
535 case fmap (hitSource . head . hits . searchHits) r of
537 (Right Nothing) -> Left (EsError 500 "Source was missing")
538 (Right (Just x)) -> Right x
540-------------------------------------------------------------------------------
541arbitraryAlphaNum :: Gen Char
542arbitraryAlphaNum = oneof [choose ('a', 'z')
546instance Arbitrary RoutingValue where
547 arbitrary = RoutingValue . T.pack <$> listOf1 arbitraryAlphaNum
549instance Arbitrary AliasRouting where
550 arbitrary = oneof [allAlias
554 where one = GranularAliasRouting
555 <$> (Just <$> arbitrary)
557 theOther = GranularAliasRouting Nothing
558 <$> (Just <$> arbitrary)
559 both = GranularAliasRouting
560 <$> (Just <$> arbitrary)
561 <*> (Just <$> arbitrary)
562 allAlias = AllAliasRouting <$> arbitrary
563 shrink = genericShrink
566instance Arbitrary FieldName where
567 arbitrary = FieldName . T.pack <$> listOf1 arbitraryAlphaNum
568 shrink = genericShrink
571instance Arbitrary RegexpFlags where
572 arbitrary = oneof [ pure AllRegexpFlags
574 , SomeRegexpFlags <$> genUniqueFlags
576 where genUniqueFlags = NE.fromList . nub <$> listOf1 arbitrary
577 shrink = genericShrink
580instance Arbitrary IndexAliasCreate where
581 arbitrary = IndexAliasCreate <$> arbitrary <*> reduceSize arbitrary
582 shrink = genericShrink
584instance Arbitrary Query where
585 arbitrary = reduceSize $ oneof [ TermQuery <$> arbitrary <*> arbitrary
586 , TermsQuery <$> arbitrary <*> arbitrary
587 , QueryMatchQuery <$> arbitrary
588 , QueryMultiMatchQuery <$> arbitrary
589 , QueryBoolQuery <$> arbitrary
590 , QueryBoostingQuery <$> arbitrary
591 , QueryCommonTermsQuery <$> arbitrary
592 , ConstantScoreFilter <$> arbitrary <*> arbitrary
593 , ConstantScoreQuery <$> arbitrary <*> arbitrary
594 , QueryDisMaxQuery <$> arbitrary
595 , QueryFilteredQuery <$> arbitrary
596 , QueryFuzzyLikeThisQuery <$> arbitrary
597 , QueryFuzzyLikeFieldQuery <$> arbitrary
598 , QueryFuzzyQuery <$> arbitrary
599 , QueryHasChildQuery <$> arbitrary
600 , QueryHasParentQuery <$> arbitrary
601 , IdsQuery <$> arbitrary <*> arbitrary
602 , QueryIndicesQuery <$> arbitrary
603 , MatchAllQuery <$> arbitrary
604 , QueryMoreLikeThisQuery <$> arbitrary
605 , QueryMoreLikeThisFieldQuery <$> arbitrary
606 , QueryNestedQuery <$> arbitrary
607 , QueryPrefixQuery <$> arbitrary
608 , QueryQueryStringQuery <$> arbitrary
609 , QuerySimpleQueryStringQuery <$> arbitrary
610 , QueryRangeQuery <$> arbitrary
611 , QueryRegexpQuery <$> arbitrary
613 shrink = genericShrink
615instance Arbitrary Filter where
616 arbitrary = reduceSize $ oneof [ AndFilter <$> arbitrary <*> arbitrary
617 , OrFilter <$> arbitrary <*> arbitrary
618 , NotFilter <$> arbitrary <*> arbitrary
619 , pure IdentityFilter
620 , BoolFilter <$> arbitrary
621 , ExistsFilter <$> arbitrary
622 , GeoBoundingBoxFilter <$> arbitrary
623 , GeoDistanceFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
624 , GeoDistanceRangeFilter <$> arbitrary <*> arbitrary
625 , GeoPolygonFilter <$> arbitrary <*> arbitrary
626 , IdsFilter <$> arbitrary <*> arbitrary
627 , LimitFilter <$> arbitrary
628 , MissingFilter <$> arbitrary <*> arbitrary <*> arbitrary
629 , PrefixFilter <$> arbitrary <*> arbitrary <*> arbitrary
630 , QueryFilter <$> arbitrary <*> arbitrary
631 , RangeFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
632 , RegexpFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
633 , TermFilter <$> arbitrary <*> arbitrary]
634 shrink = genericShrink
636instance Arbitrary ReplicaBounds where
637 arbitrary = oneof [ replicasBounded
638 , replicasLowerBounded
639 , pure ReplicasUnbounded
641 where replicasBounded = do Positive a <- arbitrary
642 Positive b <- arbitrary
643 return (ReplicasBounded a b)
644 replicasLowerBounded = do Positive a <- arbitrary
645 return (ReplicasLowerBounded a)
647instance Arbitrary NodeAttrName where
648 arbitrary = NodeAttrName . T.pack . getNonEmpty <$> arbitrary
651instance Arbitrary NodeAttrFilter where
654 s:ss <- listOf1 (listOf1 arbitraryAlphaNum)
655 let ts = T.pack <$> s :| ss
656 return (NodeAttrFilter n ts)
658$(derive makeArbitrary ''IndexName)
659$(derive makeArbitrary ''MappingName)
660$(derive makeArbitrary ''DocId)
661$(derive makeArbitrary ''Version)
662$(derive makeArbitrary ''IndexAliasRouting)
663$(derive makeArbitrary ''ShardCount)
664$(derive makeArbitrary ''ReplicaCount)
665$(derive makeArbitrary ''TemplateName)
666$(derive makeArbitrary ''TemplatePattern)
667$(derive makeArbitrary ''QueryString)
668$(derive makeArbitrary ''CacheName)
669$(derive makeArbitrary ''CacheKey)
670$(derive makeArbitrary ''Existence)
671$(derive makeArbitrary ''CutoffFrequency)
672$(derive makeArbitrary ''Analyzer)
673$(derive makeArbitrary ''MaxExpansions)
674$(derive makeArbitrary ''Lenient)
675$(derive makeArbitrary ''Tiebreaker)
676$(derive makeArbitrary ''Boost)
677$(derive makeArbitrary ''BoostTerms)
678$(derive makeArbitrary ''MinimumMatch)
679$(derive makeArbitrary ''DisableCoord)
680$(derive makeArbitrary ''IgnoreTermFrequency)
681$(derive makeArbitrary ''MinimumTermFrequency)
682$(derive makeArbitrary ''MaxQueryTerms)
683$(derive makeArbitrary ''Fuzziness)
684$(derive makeArbitrary ''PrefixLength)
685$(derive makeArbitrary ''TypeName)
686$(derive makeArbitrary ''PercentMatch)
687$(derive makeArbitrary ''StopWord)
688$(derive makeArbitrary ''QueryPath)
689$(derive makeArbitrary ''AllowLeadingWildcard)
690$(derive makeArbitrary ''LowercaseExpanded)
691$(derive makeArbitrary ''EnablePositionIncrements)
692$(derive makeArbitrary ''AnalyzeWildcard)
693$(derive makeArbitrary ''GeneratePhraseQueries)
694$(derive makeArbitrary ''Locale)
695$(derive makeArbitrary ''MaxWordLength)
696$(derive makeArbitrary ''MinWordLength)
697$(derive makeArbitrary ''PhraseSlop)
698$(derive makeArbitrary ''MinDocFrequency)
699$(derive makeArbitrary ''MaxDocFrequency)
700$(derive makeArbitrary ''Regexp)
701$(derive makeArbitrary ''SimpleQueryStringQuery)
702$(derive makeArbitrary ''FieldOrFields)
703$(derive makeArbitrary ''SimpleQueryFlag)
704$(derive makeArbitrary ''RegexpQuery)
705$(derive makeArbitrary ''QueryStringQuery)
706$(derive makeArbitrary ''RangeQuery)
707$(derive makeArbitrary ''RangeValue)
708$(derive makeArbitrary ''PrefixQuery)
709$(derive makeArbitrary ''NestedQuery)
710$(derive makeArbitrary ''MoreLikeThisFieldQuery)
711$(derive makeArbitrary ''MoreLikeThisQuery)
712$(derive makeArbitrary ''IndicesQuery)
713$(derive makeArbitrary ''HasParentQuery)
714$(derive makeArbitrary ''HasChildQuery)
715$(derive makeArbitrary ''FuzzyQuery)
716$(derive makeArbitrary ''FuzzyLikeFieldQuery)
717$(derive makeArbitrary ''FuzzyLikeThisQuery)
718$(derive makeArbitrary ''FilteredQuery)
719$(derive makeArbitrary ''DisMaxQuery)
720$(derive makeArbitrary ''CommonTermsQuery)
721$(derive makeArbitrary ''DistanceRange)
722$(derive makeArbitrary ''MultiMatchQuery)
723$(derive makeArbitrary ''LessThanD)
724$(derive makeArbitrary ''LessThanEqD)
725$(derive makeArbitrary ''GreaterThanD)
726$(derive makeArbitrary ''GreaterThanEqD)
727$(derive makeArbitrary ''LessThan)
728$(derive makeArbitrary ''LessThanEq)
729$(derive makeArbitrary ''GreaterThan)
730$(derive makeArbitrary ''GreaterThanEq)
731$(derive makeArbitrary ''GeoPoint)
732$(derive makeArbitrary ''NullValue)
733$(derive makeArbitrary ''MinimumMatchHighLow)
734$(derive makeArbitrary ''CommonMinimumMatch)
735$(derive makeArbitrary ''BoostingQuery)
736$(derive makeArbitrary ''BoolQuery)
737$(derive makeArbitrary ''MatchQuery)
738$(derive makeArbitrary ''MultiMatchQueryType)
739$(derive makeArbitrary ''BooleanOperator)
740$(derive makeArbitrary ''ZeroTermsQuery)
741$(derive makeArbitrary ''MatchQueryType)
742$(derive makeArbitrary ''SearchAliasRouting)
743$(derive makeArbitrary ''ScoreType)
744$(derive makeArbitrary ''Distance)
745$(derive makeArbitrary ''DistanceUnit)
746$(derive makeArbitrary ''DistanceType)
747$(derive makeArbitrary ''OptimizeBbox)
748$(derive makeArbitrary ''GeoBoundingBoxConstraint)
749$(derive makeArbitrary ''GeoFilterType)
750$(derive makeArbitrary ''GeoBoundingBox)
751$(derive makeArbitrary ''LatLon)
752$(derive makeArbitrary ''RangeExecution)
753$(derive makeArbitrary ''RegexpFlag)
754$(derive makeArbitrary ''BoolMatch)
755$(derive makeArbitrary ''Term)
756$(derive makeArbitrary ''IndexSettings)
757$(derive makeArbitrary ''UpdatableIndexSetting)
758$(derive makeArbitrary ''Bytes)
759$(derive makeArbitrary ''AllocationPolicy)
760$(derive makeArbitrary ''InitialShardCount)
761$(derive makeArbitrary ''FSType)
762$(derive makeArbitrary ''CompoundFormat)
768 describe "index create/delete API" $ do
769 it "creates and then deletes the requested index" $ withTestEnv $ do
771 _ <- deleteExampleIndex
772 resp <- createExampleIndex
773 deleteResp <- deleteExampleIndex
775 validateStatus resp 200
776 validateStatus deleteResp 200
778 describe "error parsing" $ do
779 it "can parse EsErrors" $ withTestEnv $ do
780 res <- getDocument (IndexName "bogus") (MappingName "also_bogus") (DocId "bogus_as_well")
781 let errorResp = eitherDecode (responseBody res)
782 liftIO (errorResp `shouldBe` Right (EsError 404 "IndexMissingException[[bogus] missing]"))
784 describe "document API" $ do
785 it "indexes, updates, gets, and then deletes the generated document" $ withTestEnv $ do
788 docInserted <- getDocument testIndex testMapping (DocId "1")
789 let newTweet = eitherDecode
790 (responseBody docInserted) :: Either String (EsResult Tweet)
791 liftIO $ (fmap getSource newTweet `shouldBe` Right (Just patchedTweet))
793 it "indexes, gets, and then deletes the generated document with a DocId containing a space" $ withTestEnv $ do
794 _ <- insertWithSpaceInId
795 docInserted <- getDocument testIndex testMapping (DocId "Hello World")
796 let newTweet = eitherDecode
797 (responseBody docInserted) :: Either String (EsResult Tweet)
798 liftIO $ (fmap getSource newTweet `shouldBe` Right (Just exampleTweet))
800 it "produces a parseable result when looking up a bogus document" $ withTestEnv $ do
801 doc <- getDocument testIndex testMapping (DocId "bogus")
802 let noTweet = eitherDecode
803 (responseBody doc) :: Either String (EsResult Tweet)
804 liftIO $ fmap foundResult noTweet `shouldBe` Right Nothing
806 it "can use optimistic concurrency control" $ withTestEnv $ do
807 let ev = ExternalDocVersion minBound
808 let cfg = defaultIndexDocumentSettings { idsVersionControl = ExternalGT ev }
810 res <- insertData' cfg
811 liftIO $ isCreated res `shouldBe` True
812 res' <- insertData' cfg
813 liftIO $ isVersionConflict res' `shouldBe` True
815 it "indexes two documents in a parent/child relationship and checks that the child exists" $ withTestEnv $ do
817 _ <- putMapping testIndex (MappingName "parent") ParentMapping
818 _ <- putMapping testIndex (MappingName "child") ChildMapping
819 _ <- indexDocument testIndex (MappingName "parent") defaultIndexDocumentSettings exampleTweet (DocId "1")
820 let parent = (Just . DocumentParent . DocId) "1"
821 ids = IndexDocumentSettings NoVersionControl parent
822 _ <- indexDocument testIndex (MappingName "child") ids otherTweet (DocId "2")
823 _ <- refreshIndex testIndex
824 exists <- documentExists testIndex (MappingName "child") parent (DocId "2")
825 liftIO $ exists `shouldBe` True
827 describe "template API" $ do
828 it "can create a template" $ withTestEnv $ do
829 let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
830 resp <- putTemplate idxTpl (TemplateName "tweet-tpl")
831 liftIO $ validateStatus resp 200
833 it "can detect if a template exists" $ withTestEnv $ do
834 exists <- templateExists (TemplateName "tweet-tpl")
835 liftIO $ exists `shouldBe` True
837 it "can delete a template" $ withTestEnv $ do
838 resp <- deleteTemplate (TemplateName "tweet-tpl")
839 liftIO $ validateStatus resp 200
841 it "can detect if a template doesn't exist" $ withTestEnv $ do
842 exists <- templateExists (TemplateName "tweet-tpl")
843 liftIO $ exists `shouldBe` False
845 describe "bulk API" $ do
846 it "inserts all documents we request" $ withTestEnv $ do
848 let firstTest = BulkTest "blah"
849 let secondTest = BulkTest "bloo"
850 let firstDoc = BulkIndex testIndex
851 testMapping (DocId "2") (toJSON firstTest)
852 let secondDoc = BulkCreate testIndex
853 testMapping (DocId "3") (toJSON secondTest)
854 let stream = V.fromList [firstDoc, secondDoc]
856 _ <- refreshIndex testIndex
857 fDoc <- getDocument testIndex testMapping (DocId "2")
858 sDoc <- getDocument testIndex testMapping (DocId "3")
859 let maybeFirst = eitherDecode $ responseBody fDoc :: Either String (EsResult BulkTest)
860 let maybeSecond = eitherDecode $ responseBody sDoc :: Either String (EsResult BulkTest)
862 fmap getSource maybeFirst `shouldBe` Right (Just firstTest)
863 fmap getSource maybeSecond `shouldBe` Right (Just secondTest)
866 describe "query API" $ do
867 it "returns document for term query and identity filter" $ withTestEnv $ do
869 let query = TermQuery (Term "user" "bitemyapp") Nothing
870 let filter = IdentityFilter <&&> IdentityFilter
871 let search = mkSearch (Just query) (Just filter)
872 myTweet <- searchTweet search
874 myTweet `shouldBe` Right exampleTweet
876 it "handles constant score queries" $ withTestEnv $ do
878 let query = TermsQuery "user" ("bitemyapp" :| [])
879 let cfQuery = ConstantScoreQuery query (Boost 1.0)
880 let filter = IdentityFilter
881 let search = mkSearch (Just cfQuery) (Just filter)
882 myTweet <- searchTweet search
884 myTweet `shouldBe` Right exampleTweet
885 it "handles constant score filters" $ withTestEnv $ do
887 let query = TermsQuery "user" ("bitemyapp" :| [])
888 let cfFilter = ConstantScoreFilter IdentityFilter (Boost 1.0)
889 let boolQuery = mkBoolQuery [query, cfFilter] [] []
890 let search = mkSearch (Just (QueryBoolQuery boolQuery)) Nothing
891 myTweet <- searchTweet search
893 myTweet `shouldBe` Right exampleTweet
896 it "returns document for terms query and identity filter" $ withTestEnv $ do
898 let query = TermsQuery "user" ("bitemyapp" :| [])
899 let filter = IdentityFilter <&&> IdentityFilter
900 let search = mkSearch (Just query) (Just filter)
901 myTweet <- searchTweet search
903 myTweet `shouldBe` Right exampleTweet
905 it "returns document for match query" $ withTestEnv $ do
907 let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
908 let search = mkSearch (Just query) Nothing
909 myTweet <- searchTweet search
911 myTweet `shouldBe` Right exampleTweet
913 it "returns document for multi-match query" $ withTestEnv $ do
915 let flds = [FieldName "user", FieldName "message"]
916 let query = QueryMultiMatchQuery $ mkMultiMatchQuery flds (QueryString "bitemyapp")
917 let search = mkSearch (Just query) Nothing
918 myTweet <- searchTweet search
920 myTweet `shouldBe` Right exampleTweet
922 it "returns document for bool query" $ withTestEnv $ do
924 let innerQuery = QueryMatchQuery $
925 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
926 let query = QueryBoolQuery $
927 mkBoolQuery [innerQuery] [] []
928 let search = mkSearch (Just query) Nothing
929 myTweet <- searchTweet search
931 myTweet `shouldBe` Right exampleTweet
933 it "returns document for boosting query" $ withTestEnv $ do
935 let posQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
936 let negQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "notmyapp")
937 let query = QueryBoostingQuery $ BoostingQuery posQuery negQuery (Boost 0.2)
938 let search = mkSearch (Just query) Nothing
939 myTweet <- searchTweet search
941 myTweet `shouldBe` Right exampleTweet
943 it "returns document for common terms query" $ withTestEnv $ do
945 let query = QueryCommonTermsQuery $
946 CommonTermsQuery (FieldName "user")
947 (QueryString "bitemyapp")
948 (CutoffFrequency 0.0001)
949 Or Or Nothing Nothing Nothing Nothing
950 let search = mkSearch (Just query) Nothing
951 myTweet <- searchTweet search
953 myTweet `shouldBe` Right exampleTweet
956 describe "sorting" $ do
957 it "returns documents in the right order" $ withTestEnv $ do
960 let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
961 let search = Search Nothing
962 (Just IdentityFilter) (Just [sortSpec]) Nothing Nothing
963 False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
964 result <- searchTweets search
965 let myTweet = grabFirst result
967 myTweet `shouldBe` Right otherTweet
970 describe "filtering API" $ do
971 it "returns document for composed boolmatch and identity" $ withTestEnv $ do
973 let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
975 let search = mkSearch Nothing (Just queryFilter)
976 myTweet <- searchTweet search
978 myTweet `shouldBe` Right exampleTweet
980 it "returns document for term filter" $ withTestEnv $ do
982 let termFilter = TermFilter (Term "user" "bitemyapp") False
983 let search = mkSearch Nothing (Just termFilter)
984 myTweet <- searchTweet search
986 myTweet `shouldBe` Right exampleTweet
988 it "returns document for existential filter" $ withTestEnv $ do
990 let search = mkSearch Nothing (Just (ExistsFilter (FieldName "user")))
991 myTweet <- searchTweet search
993 myTweet `shouldBe` Right exampleTweet
995 it "returns document for geo boundingbox filter" $ withTestEnv $ do
997 let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
998 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
999 let geoFilter = GeoBoundingBoxFilter bbConstraint
1000 let search = mkSearch Nothing (Just geoFilter)
1001 myTweet <- searchTweet search
1003 myTweet `shouldBe` Right exampleTweet
1005 it "doesn't return document for nonsensical boundingbox filter" $ withTestEnv $ do
1007 let box = GeoBoundingBox (LatLon 0.73 (-4.1)) (LatLon 0.10 (-1.12))
1008 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
1009 let geoFilter = GeoBoundingBoxFilter bbConstraint
1010 let search = mkSearch Nothing (Just geoFilter)
1011 searchExpectNoResults search
1013 it "returns document for geo distance filter" $ withTestEnv $ do
1015 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1016 let distance = Distance 10.0 Miles
1017 let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
1018 let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
1019 let search = mkSearch Nothing (Just geoFilter)
1020 myTweet <- searchTweet search
1022 myTweet `shouldBe` Right exampleTweet
1024 it "returns document for geo distance range filter" $ withTestEnv $ do
1026 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1027 let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
1028 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
1029 let search = mkSearch Nothing (Just geoFilter)
1030 myTweet <- searchTweet search
1032 myTweet `shouldBe` Right exampleTweet
1034 it "doesn't return document for wild geo distance range filter" $ withTestEnv $ do
1036 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1037 let distanceRange = DistanceRange (Distance 100.0 Miles) (Distance 1000.0 Miles)
1038 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
1039 let search = mkSearch Nothing (Just geoFilter)
1040 searchExpectNoResults search
1042 it "returns document for geo polygon filter" $ withTestEnv $ do
1044 let points = [LatLon 40.0 (-70.00),
1045 LatLon 40.0 (-72.00),
1046 LatLon 41.0 (-70.00),
1047 LatLon 41.0 (-72.00)]
1048 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
1049 let search = mkSearch Nothing (Just geoFilter)
1050 myTweet <- searchTweet search
1052 myTweet `shouldBe` Right exampleTweet
1054 it "doesn't return document for bad geo polygon filter" $ withTestEnv $ do
1056 let points = [LatLon 40.0 (-70.00),
1057 LatLon 40.0 (-71.00),
1058 LatLon 41.0 (-70.00),
1059 LatLon 41.0 (-71.00)]
1060 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
1061 let search = mkSearch Nothing (Just geoFilter)
1062 searchExpectNoResults search
1064 it "returns document for ids filter" $ withTestEnv $ do
1066 let filter = IdsFilter (MappingName "tweet") [DocId "1"]
1067 let search = mkSearch Nothing (Just filter)
1068 myTweet <- searchTweet search
1070 myTweet `shouldBe` Right exampleTweet
1072 it "returns document for Double range filter" $ withTestEnv $ do
1074 let filter = RangeFilter (FieldName "age")
1075 (RangeDoubleGtLt (GreaterThan 1000.0) (LessThan 100000.0))
1076 RangeExecutionIndex False
1077 let search = mkSearch Nothing (Just filter)
1078 myTweet <- searchTweet search
1080 myTweet `shouldBe` Right exampleTweet
1082 it "returns document for UTCTime date filter" $ withTestEnv $ do
1084 let filter = RangeFilter (FieldName "postDate")
1086 (GreaterThanD (UTCTime
1087 (ModifiedJulianDay 54000)
1088 (secondsToDiffTime 0)))
1090 (ModifiedJulianDay 55000)
1091 (secondsToDiffTime 11))))
1092 RangeExecutionIndex False
1093 let search = mkSearch Nothing (Just filter)
1094 myTweet <- searchTweet search
1096 myTweet `shouldBe` Right exampleTweet
1098 it "returns document for regexp filter" $ withTestEnv $ do
1100 let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
1101 AllRegexpFlags (CacheName "test") False (CacheKey "key")
1102 let search = mkSearch Nothing (Just filter)
1103 myTweet <- searchTweet search
1105 myTweet `shouldBe` Right exampleTweet
1107 it "doesn't return document for non-matching regexp filter" $ withTestEnv $ do
1109 let filter = RegexpFilter (FieldName "user")
1110 (Regexp "boy") AllRegexpFlags
1111 (CacheName "test") False (CacheKey "key")
1112 let search = mkSearch Nothing (Just filter)
1113 searchExpectNoResults search
1115 it "returns document for query filter, uncached" $ withTestEnv $ do
1117 let filter = QueryFilter (TermQuery (Term "user" "bitemyapp") Nothing) True
1118 search = mkSearch Nothing (Just filter)
1119 myTweet <- searchTweet search
1120 liftIO $ myTweet `shouldBe` Right exampleTweet
1122 it "returns document for query filter, cached" $ withTestEnv $ do
1124 let filter = QueryFilter (TermQuery (Term "user" "bitemyapp") Nothing) False
1125 search = mkSearch Nothing (Just filter)
1126 myTweet <- searchTweet search
1127 liftIO $ myTweet `shouldBe` Right exampleTweet
1129 describe "Aggregation API" $ do
1130 it "returns term aggregation results" $ withTestEnv $ do
1132 let terms = TermsAgg $ mkTermsAggregation "user"
1133 let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
1134 searchExpectAggs search
1135 searchValidBucketAgg search "users" toTerms
1137 it "can give collection hint parameters to term aggregations" $ when' (atleast es13) $ withTestEnv $ do
1139 let terms = TermsAgg $ (mkTermsAggregation "user") { termCollectMode = Just BreadthFirst }
1140 let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
1141 searchExpectAggs search
1142 searchValidBucketAgg search "users" toTerms
1144 it "can give execution hint parameters to term aggregations" $ when' (atmost es11) $ withTestEnv $ do
1146 searchTermsAggHint [Map, Ordinals]
1148 it "can give execution hint parameters to term aggregations" $ when' (is es12) $ withTestEnv $ do
1150 searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map, Ordinals]
1152 it "can give execution hint parameters to term aggregations" $ when' (atleast es12) $ withTestEnv $ do
1154 searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map]
1157 it "can execute value_count aggregations" $ withTestEnv $ do
1160 let ags = mkAggregations "user_count" (ValueCountAgg (FieldValueCount (FieldName "user"))) <>
1161 mkAggregations "bogus_count" (ValueCountAgg (FieldValueCount (FieldName "bogus")))
1162 let search = mkAggregateSearch Nothing ags
1163 let docCountPair k n = (k, object ["value" .= Number n])
1164 res <- searchTweets search
1166 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "user_count" 2
1167 , docCountPair "bogus_count" 0
1170 it "can execute filter aggregations" $ withTestEnv $ do
1173 let ags = mkAggregations "bitemyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "bitemyapp") defaultCache) Nothing)) <>
1174 mkAggregations "notmyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "notmyapp") defaultCache) Nothing))
1175 let search = mkAggregateSearch Nothing ags
1176 let docCountPair k n = (k, object ["doc_count" .= Number n])
1177 res <- searchTweets search
1179 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "bitemyapps" 1
1180 , docCountPair "notmyapps" 1
1182 it "can execute date_range aggregations" $ withTestEnv $ do
1183 let now = fromGregorian 2015 3 14
1184 let ltAMonthAgo = UTCTime (fromGregorian 2015 3 1) 0
1185 let ltAWeekAgo = UTCTime (fromGregorian 2015 3 10) 0
1186 let oldDoc = exampleTweet { postDate = ltAMonthAgo }
1187 let newDoc = exampleTweet { postDate = ltAWeekAgo }
1188 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings oldDoc (DocId "1")
1189 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings newDoc (DocId "2")
1190 _ <- refreshIndex testIndex
1191 let thisMonth = DateRangeFrom (DateMathExpr (DMDate now) [SubtractTime 1 DMMonth])
1192 let thisWeek = DateRangeFrom (DateMathExpr (DMDate now) [SubtractTime 1 DMWeek])
1193 let agg = DateRangeAggregation (FieldName "postDate") Nothing (thisMonth :| [thisWeek])
1194 let ags = mkAggregations "date_ranges" (DateRangeAgg agg)
1195 let search = mkAggregateSearch Nothing ags
1196 res <- searchTweets search
1197 liftIO $ hitsTotal . searchHits <$> res `shouldBe` Right 2
1198 let bucks = do magrs <- fmapL show (aggregations <$> res)
1199 agrs <- note "no aggregations returned" magrs
1200 rawBucks <- note "no date_ranges aggregation" $ M.lookup "date_ranges" agrs
1201 parseEither parseJSON rawBucks
1202 let fromMonthT = UTCTime (fromGregorian 2015 2 14) 0
1203 let fromWeekT = UTCTime (fromGregorian 2015 3 7) 0
1204 liftIO $ buckets <$> bucks `shouldBe` Right [ DateRangeResult "2015-02-14T00:00:00.000Z-*"
1206 (Just "2015-02-14T00:00:00.000Z")
1211 , DateRangeResult "2015-03-07T00:00:00.000Z-*"
1213 (Just "2015-03-07T00:00:00.000Z")
1220 it "returns date histogram aggregation results" $ withTestEnv $ do
1222 let histogram = DateHistogramAgg $ mkDateHistogram (FieldName "postDate") Minute
1223 let search = mkAggregateSearch Nothing (mkAggregations "byDate" histogram)
1224 searchExpectAggs search
1225 searchValidBucketAgg search "byDate" toDateHistogram
1227 it "returns date histogram using fractional date" $ withTestEnv $ do
1229 let periods = [Year, Quarter, Month, Week, Day, Hour, Minute, Second]
1230 let fractionals = map (FractionalInterval 1.5) [Weeks, Days, Hours, Minutes, Seconds]
1231 let intervals = periods ++ fractionals
1232 let histogram = mkDateHistogram (FieldName "postDate")
1233 let search interval = mkAggregateSearch Nothing $ mkAggregations "byDate" $ DateHistogramAgg (histogram interval)
1234 let expect interval = searchExpectAggs (search interval)
1235 let valid interval = searchValidBucketAgg (search interval) "byDate" toDateHistogram
1236 forM_ intervals expect
1237 forM_ intervals valid
1239 describe "Highlights API" $ do
1241 it "returns highlight from query when there should be one" $ withTestEnv $ do
1244 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
1245 let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing]
1247 let search = mkHighlightSearch (Just query) testHighlight
1248 myHighlight <- searchTweetHighlight search
1250 myHighlight `shouldBe` Right (Just (M.fromList [("message",["Use <em>haskell</em>!"])]))
1252 it "doesn't return highlight from a query when it shouldn't" $ withTestEnv $ do
1255 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
1256 let testHighlight = Highlights Nothing [FieldHighlight (FieldName "user") Nothing]
1258 let search = mkHighlightSearch (Just query) testHighlight
1259 myHighlight <- searchTweetHighlight search
1261 myHighlight `shouldBe` Right Nothing
1263 describe "Source filtering" $ do
1265 it "doesn't include source when sources are disabled" $ withTestEnv $ do
1268 (Left (EsError 500 "Source was missing"))
1270 it "includes a source" $ withTestEnv $ do
1272 (SourcePatterns (PopPattern (Pattern "message")))
1273 (Right (Object (HM.fromList [("message", String "Use haskell!")])))
1275 it "includes sources" $ withTestEnv $ do
1277 (SourcePatterns (PopPatterns [Pattern "user", Pattern "message"]))
1278 (Right (Object (HM.fromList [("user",String "bitemyapp"),("message", String "Use haskell!")])))
1280 it "includes source patterns" $ withTestEnv $ do
1282 (SourcePatterns (PopPattern (Pattern "*ge")))
1283 (Right (Object (HM.fromList [("age", Number 10000),("message", String "Use haskell!")])))
1285 it "excludes source patterns" $ withTestEnv $ do
1287 (SourceIncludeExclude (Include []) (Exclude [Pattern "l*", Pattern "*ge", Pattern "postDate"]))
1288 (Right (Object (HM.fromList [("user",String "bitemyapp")])))
1290 describe "ToJSON RegexpFlags" $ do
1291 it "generates the correct JSON for AllRegexpFlags" $
1292 toJSON AllRegexpFlags `shouldBe` String "ALL"
1294 it "generates the correct JSON for NoRegexpFlags" $
1295 toJSON NoRegexpFlags `shouldBe` String "NONE"
1297 it "generates the correct JSON for SomeRegexpFlags" $
1298 let flags = AnyString :| [ Automaton
1303 in toJSON (SomeRegexpFlags flags) `shouldBe` String "ANYSTRING|AUTOMATON|COMPLEMENT|EMPTY|INTERSECTION|INTERVAL"
1305 prop "removes duplicates from flags" $ \(flags :: RegexpFlags) ->
1306 let String str = toJSON flags
1307 flagStrs = T.splitOn "|" str
1308 in noDuplicates flagStrs
1310 describe "omitNulls" $ do
1311 it "checks that omitNulls drops list elements when it should" $
1312 let dropped = omitNulls $ [ "test1" .= (toJSON ([] :: [Int]))
1313 , "test2" .= (toJSON ("some value" :: Text))]
1314 in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
1316 it "checks that omitNulls doesn't drop list elements when it shouldn't" $
1317 let notDropped = omitNulls $ [ "test1" .= (toJSON ([1] :: [Int]))
1318 , "test2" .= (toJSON ("some value" :: Text))]
1319 in notDropped `shouldBe` Object (HM.fromList [ ("test1", Array (V.fromList [Number 1.0]))
1320 , ("test2", String "some value")])
1321 it "checks that omitNulls drops non list elements when it should" $
1322 let dropped = omitNulls $ [ "test1" .= (toJSON Null)
1323 , "test2" .= (toJSON ("some value" :: Text))]
1324 in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
1325 it "checks that omitNulls doesn't drop non list elements when it shouldn't" $
1326 let notDropped = omitNulls $ [ "test1" .= (toJSON (1 :: Int))
1327 , "test2" .= (toJSON ("some value" :: Text))]
1328 in notDropped `shouldBe` Object (HM.fromList [ ("test1", Number 1.0)
1329 , ("test2", String "some value")])
1330 describe "Monoid (SearchHits a)" $ do
1331 prop "abides the monoid laws" $ eq $
1332 prop_Monoid (T :: T (SearchHits ()))
1334 describe "mkDocVersion" $ do
1335 prop "can never construct an out of range docVersion" $ \i ->
1336 let res = mkDocVersion i
1338 Nothing -> property True
1339 Just dv -> (dv >= minBound) .&&.
1340 (dv <= maxBound) .&&.
1341 docVersionNumber dv === i
1343 describe "Enum DocVersion" $ do
1344 it "follows the laws of Enum, Bounded" $ do
1345 evaluate (succ maxBound :: DocVersion) `shouldThrow` anyErrorCall
1346 evaluate (pred minBound :: DocVersion) `shouldThrow` anyErrorCall
1347 evaluate (toEnum 0 :: DocVersion) `shouldThrow` anyErrorCall
1348 evaluate (toEnum 9200000000000000001 :: DocVersion) `shouldThrow` anyErrorCall
1349 enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
1350 enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
1351 enumFromThen minBound (pred maxBound :: DocVersion) `shouldBe` [minBound, pred maxBound]
1353 describe "scan&scroll API" $ do
1354 it "returns documents using the scan&scroll API" $ withTestEnv $ do
1357 let search = (mkSearch (Just $ MatchAllQuery Nothing) Nothing) { size = (Size 1) }
1358 regular_search <- searchTweet search
1359 scan_search' <- scanSearch testIndex testMapping search :: BH IO [Hit Tweet]
1360 let scan_search = map hitSource scan_search'
1362 regular_search `shouldBe` Right exampleTweet -- Check that the size restrtiction is being honored
1364 scan_search `shouldMatchList` [Just exampleTweet, Just otherTweet]
1366 describe "index aliases" $ do
1367 it "handles the simple case of aliasing an existing index" $ do
1368 let alias = IndexAlias (testIndex) (IndexAliasName (IndexName "bloodhound-tests-twitter-1-alias"))
1369 let create = IndexAliasCreate Nothing Nothing
1370 let action = AddAlias alias create
1374 resp <- updateIndexAliases (action :| [])
1375 liftIO $ validateStatus resp 200
1376 let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
1377 (do aliases <- withTestEnv getIndexAliases
1378 let expected = IndexAliasSummary alias create
1380 Right (IndexAliasesSummary summs) ->
1381 L.find ((== alias) . indexAliasSummaryAlias) summs `shouldBe` Just expected
1382 Left e -> expectationFailure ("Expected an IndexAliasesSummary but got " <> show e)) `finally` cleanup
1384 it "handles an alias with routing and a filter" $ do
1385 let alias = IndexAlias (testIndex) (IndexAliasName (IndexName "bloodhound-tests-twitter-1-alias"))
1386 let sar = SearchAliasRouting (RoutingValue "search val" :| [])
1387 let iar = IndexAliasRouting (RoutingValue "index val")
1388 let routing = GranularAliasRouting (Just sar) (Just iar)
1389 let filter = LimitFilter 42
1390 let create = IndexAliasCreate (Just routing) (Just filter)
1391 let action = AddAlias alias create
1395 resp <- updateIndexAliases (action :| [])
1396 liftIO $ validateStatus resp 200
1397 let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
1398 (do aliases <- withTestEnv getIndexAliases
1399 let expected = IndexAliasSummary alias create
1401 Right (IndexAliasesSummary summs) ->
1402 L.find ((== alias) . indexAliasSummaryAlias) summs `shouldBe` Just expected
1403 Left e -> expectationFailure ("Expected an IndexAliasesSummary but got " <> show e)) `finally` cleanup
1405 describe "Index Settings" $ do
1406 it "persists settings" $ withTestEnv $ do
1407 _ <- deleteExampleIndex
1408 _ <- createExampleIndex
1409 let updates = BlocksWrite False :| []
1410 updateResp <- updateIndexSettings updates testIndex
1411 liftIO $ validateStatus updateResp 200
1412 getResp <- getIndexSettings testIndex
1414 getResp `shouldBe` Right (IndexSettingsSummary
1416 (IndexSettings (ShardCount 1) (ReplicaCount 0))
1417 (NE.toList updates))
1419 describe "JSON instances" $ do
1420 propJSON (Proxy :: Proxy Version)
1421 propJSON (Proxy :: Proxy IndexName)
1422 propJSON (Proxy :: Proxy MappingName)
1423 propJSON (Proxy :: Proxy DocId)
1424 propJSON (Proxy :: Proxy IndexAliasRouting)
1425 propJSON (Proxy :: Proxy RoutingValue)
1426 propJSON (Proxy :: Proxy ShardCount)
1427 propJSON (Proxy :: Proxy ReplicaCount)
1428 propJSON (Proxy :: Proxy TemplateName)
1429 propJSON (Proxy :: Proxy TemplatePattern)
1430 propJSON (Proxy :: Proxy QueryString)
1431 propJSON (Proxy :: Proxy FieldName)
1432 propJSON (Proxy :: Proxy CacheName)
1433 propJSON (Proxy :: Proxy CacheKey)
1434 propJSON (Proxy :: Proxy Existence)
1435 propJSON (Proxy :: Proxy CutoffFrequency)
1436 propJSON (Proxy :: Proxy Analyzer)
1437 propJSON (Proxy :: Proxy MaxExpansions)
1438 propJSON (Proxy :: Proxy Lenient)
1439 propJSON (Proxy :: Proxy Tiebreaker)
1440 propJSON (Proxy :: Proxy Boost)
1441 propJSON (Proxy :: Proxy BoostTerms)
1442 propJSON (Proxy :: Proxy MinimumMatch)
1443 propJSON (Proxy :: Proxy DisableCoord)
1444 propJSON (Proxy :: Proxy IgnoreTermFrequency)
1445 propJSON (Proxy :: Proxy MinimumTermFrequency)
1446 propJSON (Proxy :: Proxy MaxQueryTerms)
1447 propJSON (Proxy :: Proxy Fuzziness)
1448 propJSON (Proxy :: Proxy PrefixLength)
1449 propJSON (Proxy :: Proxy TypeName)
1450 propJSON (Proxy :: Proxy PercentMatch)
1451 propJSON (Proxy :: Proxy StopWord)
1452 propJSON (Proxy :: Proxy QueryPath)
1453 propJSON (Proxy :: Proxy AllowLeadingWildcard)
1454 propJSON (Proxy :: Proxy LowercaseExpanded)
1455 propJSON (Proxy :: Proxy EnablePositionIncrements)
1456 propJSON (Proxy :: Proxy AnalyzeWildcard)
1457 propJSON (Proxy :: Proxy GeneratePhraseQueries)
1458 propJSON (Proxy :: Proxy Locale)
1459 propJSON (Proxy :: Proxy MaxWordLength)
1460 propJSON (Proxy :: Proxy MinWordLength)
1461 propJSON (Proxy :: Proxy PhraseSlop)
1462 propJSON (Proxy :: Proxy MinDocFrequency)
1463 propJSON (Proxy :: Proxy MaxDocFrequency)
1464 propJSON (Proxy :: Proxy Filter)
1465 propJSON (Proxy :: Proxy Query)
1466 propJSON (Proxy :: Proxy SimpleQueryStringQuery)
1467 propJSON (Proxy :: Proxy FieldOrFields)
1468 propJSON (Proxy :: Proxy SimpleQueryFlag)
1469 propJSON (Proxy :: Proxy RegexpQuery)
1470 propJSON (Proxy :: Proxy QueryStringQuery)
1471 propJSON (Proxy :: Proxy RangeQuery)
1472 propJSON (Proxy :: Proxy PrefixQuery)
1473 propJSON (Proxy :: Proxy NestedQuery)
1474 propJSON (Proxy :: Proxy MoreLikeThisFieldQuery)
1475 propJSON (Proxy :: Proxy MoreLikeThisQuery)
1476 propJSON (Proxy :: Proxy IndicesQuery)
1477 propJSON (Proxy :: Proxy HasParentQuery)
1478 propJSON (Proxy :: Proxy HasChildQuery)
1479 propJSON (Proxy :: Proxy FuzzyQuery)
1480 propJSON (Proxy :: Proxy FuzzyLikeFieldQuery)
1481 propJSON (Proxy :: Proxy FuzzyLikeThisQuery)
1482 propJSON (Proxy :: Proxy FilteredQuery)
1483 propJSON (Proxy :: Proxy DisMaxQuery)
1484 propJSON (Proxy :: Proxy CommonTermsQuery)
1485 propJSON (Proxy :: Proxy CommonMinimumMatch)
1486 propJSON (Proxy :: Proxy BoostingQuery)
1487 propJSON (Proxy :: Proxy BoolQuery)
1488 propJSON (Proxy :: Proxy MatchQuery)
1489 propJSON (Proxy :: Proxy MultiMatchQueryType)
1490 propJSON (Proxy :: Proxy BooleanOperator)
1491 propJSON (Proxy :: Proxy ZeroTermsQuery)
1492 propJSON (Proxy :: Proxy MatchQueryType)
1493 propJSON (Proxy :: Proxy AliasRouting)
1494 propJSON (Proxy :: Proxy IndexAliasCreate)
1495 propJSON (Proxy :: Proxy SearchAliasRouting)
1496 propJSON (Proxy :: Proxy ScoreType)
1497 propJSON (Proxy :: Proxy Distance)
1498 propJSON (Proxy :: Proxy DistanceUnit)
1499 propJSON (Proxy :: Proxy DistanceType)
1500 propJSON (Proxy :: Proxy OptimizeBbox)
1501 propJSON (Proxy :: Proxy GeoBoundingBoxConstraint)
1502 propJSON (Proxy :: Proxy GeoFilterType)
1503 propJSON (Proxy :: Proxy GeoBoundingBox)
1504 propJSON (Proxy :: Proxy LatLon)
1505 propJSON (Proxy :: Proxy RangeExecution)
1506 prop "RegexpFlags FromJSON/ToJSON roundtrips, removing dups " $ \rfs ->
1507 let expected = case rfs of
1508 SomeRegexpFlags fs -> SomeRegexpFlags (NE.fromList (nub (NE.toList fs)))
1510 in parseEither parseJSON (toJSON rfs) === Right expected
1511 propJSON (Proxy :: Proxy BoolMatch)
1512 propJSON (Proxy :: Proxy Term)
1513 propJSON (Proxy :: Proxy MultiMatchQuery)
1514 propJSON (Proxy :: Proxy IndexSettings)
1515 propJSON (Proxy :: Proxy UpdatableIndexSetting)
1516 propJSON (Proxy :: Proxy ReplicaBounds)
1517 propJSON (Proxy :: Proxy Bytes)
1518 propJSON (Proxy :: Proxy AllocationPolicy)
1519 propJSON (Proxy :: Proxy InitialShardCount)
1520 propJSON (Proxy :: Proxy FSType)
1521 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