2014-04-10 09:25:17 +04:00
|
|
|
{-# LANGUAGE DeriveGeneric #-}
|
2014-04-07 03:29:46 +04:00
|
|
|
|
2014-04-05 07:55:53 +04:00
|
|
|
module Main where
|
|
|
|
|
|
|
|
import Database.Bloodhound.Client
|
2014-04-07 03:29:46 +04:00
|
|
|
import Data.Aeson
|
|
|
|
import Data.DeriveTH
|
2014-04-10 07:24:46 +04:00
|
|
|
import Data.Either (Either(..))
|
2014-04-07 03:29:46 +04:00
|
|
|
import Data.Maybe (fromJust)
|
2014-04-07 04:53:53 +04:00
|
|
|
import Data.Time.Calendar (Day(..))
|
|
|
|
import Data.Time.Clock (secondsToDiffTime, UTCTime(..))
|
2014-04-07 03:29:46 +04:00
|
|
|
import Data.Text (Text)
|
|
|
|
import GHC.Generics (Generic)
|
2014-04-05 07:55:53 +04:00
|
|
|
import Network.HTTP.Conduit
|
|
|
|
import qualified Network.HTTP.Types.Status as NHTS
|
|
|
|
import Test.Hspec
|
|
|
|
|
|
|
|
|
2014-04-11 01:39:10 +04:00
|
|
|
testServer = Server "http://localhost:9200"
|
|
|
|
testIndex = "twitter"
|
|
|
|
testMapping = "tweet"
|
2014-04-05 07:55:53 +04:00
|
|
|
validateStatus resp expected = (NHTS.statusCode $ responseStatus resp) `shouldBe` (expected :: Int)
|
2014-04-07 03:29:46 +04:00
|
|
|
createExampleIndex = createIndex testServer defaultIndexSettings testIndex
|
|
|
|
deleteExampleIndex = deleteIndex testServer testIndex
|
|
|
|
|
2014-04-11 01:39:10 +04:00
|
|
|
data Location = Location { lat :: Double
|
|
|
|
, lon :: Double } deriving (Eq, Generic, Show)
|
|
|
|
|
2014-04-10 09:25:17 +04:00
|
|
|
data Tweet = Tweet { user :: Text
|
2014-04-07 03:29:46 +04:00
|
|
|
, postDate :: UTCTime
|
2014-04-11 01:39:10 +04:00
|
|
|
, message :: Text
|
|
|
|
, location :: Location }
|
2014-04-07 03:29:46 +04:00
|
|
|
deriving (Eq, Generic, Show)
|
|
|
|
|
2014-04-11 01:39:10 +04:00
|
|
|
instance ToJSON Tweet
|
2014-04-07 03:29:46 +04:00
|
|
|
instance FromJSON Tweet
|
2014-04-11 01:39:10 +04:00
|
|
|
instance ToJSON Location
|
|
|
|
instance FromJSON Location
|
|
|
|
|
|
|
|
data TweetMapping = TweetMapping deriving (Eq, Show)
|
|
|
|
|
|
|
|
instance ToJSON TweetMapping where
|
|
|
|
toJSON TweetMapping =
|
|
|
|
object ["tweet" .=
|
|
|
|
object ["properties" .=
|
|
|
|
object ["location" .= object ["type" .= ("geo_point" :: Text)]]]]
|
2014-04-07 03:29:46 +04:00
|
|
|
|
2014-04-07 04:53:53 +04:00
|
|
|
exampleTweet = Tweet { user = "bitemyapp"
|
2014-04-07 07:51:30 +04:00
|
|
|
, postDate = UTCTime
|
|
|
|
(ModifiedJulianDay 55000)
|
2014-04-07 04:53:53 +04:00
|
|
|
(secondsToDiffTime 10)
|
2014-04-11 01:39:10 +04:00
|
|
|
, message = "Use haskell!"
|
|
|
|
, location = Location 40.12 (-71.34) }
|
2014-04-05 07:55:53 +04:00
|
|
|
|
2014-04-10 09:25:17 +04:00
|
|
|
insertData :: IO ()
|
|
|
|
insertData = do
|
|
|
|
let encoded = encode exampleTweet
|
|
|
|
_ <- deleteExampleIndex
|
|
|
|
created <- createExampleIndex
|
2014-04-11 01:39:10 +04:00
|
|
|
mappingCreated <- createMapping testServer testIndex testMapping TweetMapping
|
|
|
|
docCreated <- indexDocument testServer testIndex testMapping exampleTweet "1"
|
|
|
|
_ <- refreshIndex testServer testIndex
|
2014-04-10 09:25:17 +04:00
|
|
|
return ()
|
|
|
|
|
2014-04-11 01:39:10 +04:00
|
|
|
searchTweet :: Search -> IO (Either String Tweet)
|
|
|
|
searchTweet search = do
|
2014-04-10 09:25:17 +04:00
|
|
|
reply <- searchByIndex testServer "twitter" search
|
|
|
|
let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)
|
|
|
|
let myTweet = fmap (hitSource . head . hits . searchHits) result
|
|
|
|
return myTweet
|
|
|
|
|
2014-04-05 07:55:53 +04:00
|
|
|
main :: IO ()
|
|
|
|
main = hspec $ do
|
2014-04-10 10:41:21 +04:00
|
|
|
describe "index create/delete API" $ do
|
|
|
|
it "creates and then deletes the requested index" $ do
|
|
|
|
-- priming state.
|
|
|
|
_ <- deleteExampleIndex
|
|
|
|
resp <- createExampleIndex
|
|
|
|
deleteResp <- deleteExampleIndex
|
|
|
|
validateStatus resp 200
|
|
|
|
validateStatus deleteResp 200
|
2014-04-10 09:25:17 +04:00
|
|
|
|
2014-04-07 03:29:46 +04:00
|
|
|
describe "document API" $ do
|
|
|
|
it "indexes, gets, and then deletes the generated document" $ do
|
2014-04-10 10:41:21 +04:00
|
|
|
_ <- insertData
|
2014-04-10 07:24:46 +04:00
|
|
|
docInserted <- getDocument (Server "http://localhost:9200") "twitter" "tweet" "1"
|
|
|
|
let newTweet = eitherDecode (responseBody docInserted) :: Either String (EsResult Tweet)
|
2014-04-10 09:25:17 +04:00
|
|
|
fmap _source newTweet `shouldBe` Right exampleTweet
|
|
|
|
|
2014-04-11 01:39:10 +04:00
|
|
|
describe "filtering API" $ do
|
|
|
|
it "returns document for composed boolmatch and identity" $ do
|
|
|
|
_ <- insertData
|
|
|
|
let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
|
|
|
|
<&&> IdentityFilter
|
|
|
|
let search = Search Nothing (Just queryFilter)
|
|
|
|
myTweet <- searchTweet search
|
|
|
|
myTweet `shouldBe` Right exampleTweet
|
|
|
|
|
|
|
|
it "returns document for existential query" $ do
|
|
|
|
_ <- insertData
|
|
|
|
let search = Search Nothing (Just (ExistsFilter "user"))
|
|
|
|
myTweet <- searchTweet search
|
|
|
|
myTweet `shouldBe` Right exampleTweet
|
|
|
|
|
|
|
|
it "returns document for geo boundingbox query" $ do
|
2014-04-10 09:25:17 +04:00
|
|
|
_ <- insertData
|
2014-04-11 01:39:10 +04:00
|
|
|
let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
|
|
|
|
let bbConstraint = GeoBoundingBoxConstraint "tweet.location" box False
|
|
|
|
let geoFilter = GeoBoundingBoxFilter bbConstraint GeoFilterMemory
|
|
|
|
let search = Search Nothing (Just geoFilter)
|
|
|
|
myTweet <- searchTweet search
|
2014-04-10 09:25:17 +04:00
|
|
|
myTweet `shouldBe` Right exampleTweet
|