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