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