influxdb-0.7.0: tests/test-suite.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
import Control.Applicative
import Control.Exception as E
import Control.Monad.Trans
import Data.Int
import Data.List (find)
import Data.Monoid
import Data.Text (Text)
import Data.Unique
import Data.Word
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Vector as V
import Test.Tasty.HUnit
import Test.Tasty.TH
import Test.Tasty.QuickCheck hiding (reason)
import qualified Network.HTTP.Client as HC
import Database.InfluxDB
import Database.InfluxDB.TH
import qualified Database.InfluxDB.Stream as S
prop_fromValue_toValue_identity_Value :: Value -> Bool
prop_fromValue_toValue_identity_Value = fromValueToValueIdentity
prop_fromValue_toValue_identity_Bool :: Bool -> Bool
prop_fromValue_toValue_identity_Bool = fromValueToValueIdentity
prop_fromValue_toValue_identity_Int :: Int -> Bool
prop_fromValue_toValue_identity_Int = fromValueToValueIdentity
prop_fromValue_toValue_identity_Int8 :: Int8 -> Bool
prop_fromValue_toValue_identity_Int8 = fromValueToValueIdentity
prop_fromValue_toValue_identity_Int16 :: Int16 -> Bool
prop_fromValue_toValue_identity_Int16 = fromValueToValueIdentity
prop_fromValue_toValue_identity_Int32 :: Int32 -> Bool
prop_fromValue_toValue_identity_Int32 = fromValueToValueIdentity
prop_fromValue_toValue_identity_Int64 :: Int64 -> Bool
prop_fromValue_toValue_identity_Int64 = fromValueToValueIdentity
prop_fromValue_toValue_identity_Word8 :: Word8 -> Bool
prop_fromValue_toValue_identity_Word8 = fromValueToValueIdentity
prop_fromValue_toValue_identity_Word16 :: Word16 -> Bool
prop_fromValue_toValue_identity_Word16 = fromValueToValueIdentity
prop_fromValue_toValue_identity_Word32 :: Word32 -> Bool
prop_fromValue_toValue_identity_Word32 = fromValueToValueIdentity
prop_fromValue_toValue_identity_Double :: Double -> Bool
prop_fromValue_toValue_identity_Double = fromValueToValueIdentity
prop_fromValue_toValue_identity_Text :: T.Text -> Bool
prop_fromValue_toValue_identity_Text = fromValueToValueIdentity
prop_fromValue_toValue_identity_LazyText :: TL.Text -> Bool
prop_fromValue_toValue_identity_LazyText = fromValueToValueIdentity
prop_fromValue_toValue_identity_String :: String -> Bool
prop_fromValue_toValue_identity_String = fromValueToValueIdentity
prop_fromValue_toValue_identity_Maybe_Int :: Maybe Int -> Bool
prop_fromValue_toValue_identity_Maybe_Int = fromValueToValueIdentity
-------------------------------------------------
instance Arbitrary Value where
arbitrary = oneof
[ Int <$> arbitrary
, Float <$> arbitrary
, String <$> arbitrary
, Bool <$> arbitrary
, pure Null
]
instance Arbitrary T.Text where
arbitrary = T.pack <$> arbitrary
instance Arbitrary TL.Text where
arbitrary = TL.pack <$> arbitrary
fromValueToValueIdentity :: (Eq a, FromValue a, ToValue a) => a -> Bool
fromValueToValueIdentity a = fromValue (toValue a) == Right a
-------------------------------------------------
case_ping :: Assertion
case_ping = runTest $ \config -> do
Ping status <- ping config
status @?= "ok"
case_listInterfaces :: Assertion
case_listInterfaces = runTest $ \config -> do
ifaces <- listInterfaces config
ifaces @?= ["default"]
case_isInSync :: Assertion
case_isInSync = runTest $ \config -> do
inSync <- isInSync config
assertBool "The database is not in sync." inSync
case_post :: Assertion
case_post = runTest $ \config ->
withTestDatabase config $ \database -> do
name <- liftIO newName
post config database $
writeSeries name $ Val 42
ss <- query config database $ "select value from " <> name
case ss of
[series] -> fromSeriesData series @?= Right [Val 42]
_ -> assertFailure $ "Expect one series, but got: " ++ show ss
case_post_multi_series :: Assertion
case_post_multi_series = runTest $ \config ->
withTestDatabase config $ \database -> do
name <- liftIO newName
post config database $ do
writeSeries name $ Val 42
writeSeries name $ Val 42
writeSeries name $ Val 42
ss <- query config database $ "select value from " <> name
case ss of
[series] -> fromSeriesData series @?= Right [Val 42, Val 42, Val 42]
_ -> assertFailure $ "Expect one series, but got: " ++ show ss
case_post_multi_points :: Assertion
case_post_multi_points = runTest $ \config ->
withTestDatabase config $ \database -> do
name <- liftIO newName
post config database $ withSeries name $ do
writePoints $ Val 42
writePoints $ Val 42
writePoints $ Val 42
ss <- query config database $ "select value from " <> name
case ss of
[series] -> fromSeriesData series @?= Right [Val 42, Val 42, Val 42]
_ -> assertFailure $ "Expect one series, but got: " ++ show ss
case_query_nonexistent_series :: Assertion
case_query_nonexistent_series = runTest $ \config ->
withTestDatabase config $ \database -> do
name <- liftIO newName
assertStatusCodeException
(query config database $ "select * from " <> name :: IO [SeriesData])
case_query_empty_series :: Assertion
case_query_empty_series = runTest $ \config ->
withTestDatabase config $ \database -> do
name <- liftIO newName
post config database $
writeSeries name $ Val 42
ss1 <- query config database $ "delete from " <> name
ss1 @?= ([] :: [SeriesData])
ss2 <- query config database $ "select * from " <> name
ss2 @?= ([] :: [SeriesData])
case_queryChunked :: Assertion
case_queryChunked = runTest $ \config ->
withTestDatabase config $ \database -> do
name <- liftIO newName
post config database $ withSeries name $ do
writePoints $ Val 42
writePoints $ Val 42
writePoints $ Val 42
ss <- queryChunked config database ("select value from " <> name) $
S.fold step []
mapM fromSeriesData ss @?= Right [[Val 42], [Val 42], [Val 42]]
where
step xs series = case fromSeriesData series of
Left reason -> throwIO $ HUnitFailure reason
Right values -> return $ xs ++ values
case_post_with_precision :: Assertion
case_post_with_precision = runTest $ \config ->
withTestDatabase config $ \database -> do
name <- liftIO newName
postWithPrecision config database SecondsPrecision $
writeSeries name $ Val 42
ss <- query config database $ "select value from " <> name
case ss of
[series] -> fromSeriesData series @?= Right [Val 42]
_ -> assertFailure $ "Expect one series, but got: " ++ show ss
case_delete_series :: Assertion
case_delete_series = runTest $ \config ->
withTestDatabase config $ \database -> do
name <- liftIO newName
post config database $
writeSeries name $ Val 42
ss <- query config database $ "select value from " <> name
case ss of
[series] -> fromSeriesData series @?= Right [Val 42]
_ -> assertFailure $ "Expect one series, but got: " ++ show ss
deleteSeries config database name
assertStatusCodeException
(query config database $ "select value from " <> name :: IO [SeriesData])
case_listDatabases :: Assertion
case_listDatabases = runTest $ \config ->
withTestDatabase config $ \name -> do
databases <- listDatabases config
assertBool ("No such database: " ++ T.unpack name) $
any ((name ==) . databaseName) databases
case_configureDatabase :: Assertion
case_configureDatabase = runTest $ \config -> do
dbName <- newName
do
configureDatabase config dbName $ DatabaseRequest shardSpaces contQueries
listDatabases config >>= \databases ->
assertBool ("No such database: " ++ T.unpack dbName) $
any ((dbName ==) . databaseName) databases
listShardSpaces config >>= \spaces ->
assertBool "Missing shard space(s)" $
any ((`elem` spaceNames) . shardSpaceName) spaces
`finally`
dropDatabase config dbName
where
spaceNames = map shardSpaceRequestName shardSpaces
shardSpaces =
[ ShardSpaceRequest
{ shardSpaceRequestName = "everything_30d"
, shardSpaceRequestRetentionPolicy = "30d"
, shardSpaceRequestShardDuration = "7d"
, shardSpaceRequestRegex = "/.*/"
, shardSpaceRequestReplicationFactor = 1
, shardSpaceRequestSplit = 1
}
, ShardSpaceRequest
{ shardSpaceRequestName = "forever"
, shardSpaceRequestRetentionPolicy = "inf"
, shardSpaceRequestShardDuration = "7d"
, shardSpaceRequestRegex = "/^_.*/"
, shardSpaceRequestReplicationFactor = 1
, shardSpaceRequestSplit = 1
}
, ShardSpaceRequest
{ shardSpaceRequestName = "rollups"
, shardSpaceRequestRetentionPolicy = "365d"
, shardSpaceRequestShardDuration = "30d"
, shardSpaceRequestRegex = "/^\\d+.*/"
, shardSpaceRequestReplicationFactor = 1
, shardSpaceRequestSplit = 1
}
]
contQueries =
[ "select * from events into events.[id]"
, "select count(value) from events group by time(5m) into 5m.count.events"
]
case_shardSpaces :: Assertion
case_shardSpaces = runTest $ \config ->
withTestDatabase config $ \name -> do
spaceName <- newName
createShardSpace config name $ ShardSpaceRequest
{ shardSpaceRequestName = spaceName
, shardSpaceRequestRegex = "^[a-z].*"
, shardSpaceRequestRetentionPolicy = "7d"
, shardSpaceRequestShardDuration = "1d"
, shardSpaceRequestReplicationFactor = 1
, shardSpaceRequestSplit = 1
}
listShardSpaces config >>= \spaces ->
assertBool ("No such shard space: " ++ T.unpack spaceName) $
any ((spaceName ==) . shardSpaceName) spaces
dropShardSpace config name spaceName
listShardSpaces config >>= \spaces ->
assertBool ("Found a dropped shard space: " ++ T.unpack spaceName) $
all ((spaceName /=) . shardSpaceName) spaces
case_create_then_drop_database :: Assertion
case_create_then_drop_database = runTest $ \config -> do
name <- newName
dropDatabaseIfExists config name
createDatabase config name
listDatabases config >>= \databases ->
assertBool ("No such database: " ++ T.unpack name) $
any ((name ==) . databaseName) databases
dropDatabase config name
listDatabases config >>= \databases ->
assertBool ("Found a dropped database: " ++ T.unpack name) $
all ((name /=) . databaseName) databases
case_list_cluster_admins :: Assertion
case_list_cluster_admins = runTest $ \config -> do
admins <- listClusterAdmins config
assertBool "No root admin" $
any (("root" ==) . adminName) admins
case_authenticate_cluster_admin :: Assertion
case_authenticate_cluster_admin = runTest authenticateClusterAdmin
case_add_then_delete_cluster_admin :: Assertion
case_add_then_delete_cluster_admin = runTest $ \config -> do
name <- newName
admin <- addClusterAdmin config name "somePassword"
listClusterAdmins config >>= \admins ->
assertBool ("No such admin: " ++ T.unpack name) $
any ((name ==) . adminName) admins
deleteClusterAdmin config admin
listClusterAdmins config >>= \admins ->
assertBool ("Found a deleted admin: " ++ T.unpack name) $
all ((name /=) . adminName) admins
case_update_cluster_admin_password :: Assertion
case_update_cluster_admin_password = runTest $ \config -> do
let curPassword = "somePassword"
newPassword = "otherPassword"
name <- newName
deleteClusterAdminIfExists config name
admin <- addClusterAdmin config name curPassword
updateClusterAdminPassword config admin newPassword
let newCreds = Credentials name newPassword
newConfig = config { configCreds = newCreds }
name' <- newName
dropDatabaseIfExists config name'
createDatabase newConfig name'
listDatabases newConfig >>= \databases ->
assertBool ("No such database: " ++ T.unpack name') $
any ((name' ==) . databaseName) databases
dropDatabase newConfig name'
listDatabases newConfig >>= \databases ->
assertBool ("Found a dropped database: " ++ T.unpack name') $
all ((name' /=) . databaseName) databases
deleteClusterAdmin config admin
case_add_then_delete_database_users :: Assertion
case_add_then_delete_database_users = runTest $ \config ->
withTestDatabase config $ \name -> do
listDatabaseUsers config name >>= \users ->
assertBool "There shouldn't be any users" $ null users
newUserName <- newName
addDatabaseUser config name newUserName "somePassword"
let newCreds = rootCreds
{ credsUser = newUserName
, credsPassword = "somePassword" }
newConfig = config { configCreds = newCreds }
authenticateDatabaseUser newConfig name
listDatabaseUsers config name >>= \users ->
assertBool ("No such user: " <> T.unpack newUserName) $
any ((newUserName ==) . userName) users
deleteDatabaseUser config name newUserName
listDatabaseUsers config name >>= \users ->
assertBool ("Found a deleted user: " <> T.unpack newUserName) $
all ((newUserName /=) . userName) users
case_update_database_user_password :: Assertion
case_update_database_user_password = runTest $ \config ->
withTestDatabase config $ \name -> do
newUserName <- newName
addDatabaseUser config name newUserName "somePassword"
listDatabaseUsers config name >>= \users ->
assertBool ("No such user: " <> T.unpack newUserName) $
any ((newUserName ==) . userName) users
updateDatabaseUserPassword config name newUserName "otherPassword"
deleteDatabaseUser config name newUserName
case_grant_revoke_database_user :: Assertion
case_grant_revoke_database_user = runTest $ \config ->
withTestDatabase config $ \name -> do
newUserName <- newName
addDatabaseUser config name newUserName "somePassword"
listDatabaseUsers config name >>= \users ->
assertBool ("No such user: " <> T.unpack newUserName) $
any ((newUserName ==) . userName) users
grantAdminPrivilegeTo config name newUserName
listDatabaseUsers config name >>= \users ->
case find ((newUserName ==) . userName) users of
Nothing -> assertFailure $ "No such user: " <> T.unpack newUserName
Just user -> assertBool
("User is not privileged: " <> T.unpack newUserName)
(userIsAdmin user)
revokeAdminPrivilegeFrom config name newUserName
listDatabaseUsers config name >>= \users ->
case find ((newUserName ==) . userName) users of
Nothing -> assertFailure $ "No such user: " <> T.unpack newUserName
Just user -> assertBool
("User is still privileged: " <> T.unpack newUserName)
(not $ userIsAdmin user)
deleteDatabaseUser config name newUserName
-------------------------------------------------
-- Regressions
newtype WholeFloat = WholeFloat
{ wholeFloatValue :: Double
} deriving (Eq, Show)
-- #14: InfluxDB may return Int instead of Float when
-- the WholeFloat value happens to be a whole number.
case_regression_whole_Float_number :: Assertion
case_regression_whole_Float_number = runTest $ \config ->
withTestDatabase config $ \database -> do
series <- newName
post config database $
writeSeries series $ WholeFloat 42.0
ss <- query config database $ "select value from " <> series
case ss of
[sd] -> fromSeriesData sd @?= Right [WholeFloat 42]
_ -> assertFailure $ "Expect one series, but got: " ++ show ss
case_regression_really_big_Float_number :: Assertion
case_regression_really_big_Float_number = runTest $ \config ->
withTestDatabase config $ \database -> do
series <- newName
post config database $
writeSeries series $ WholeFloat 42e100
ss <- query config database $ "select value from " <> series
case ss of
[sd] -> fromSeriesData sd @?= Right [WholeFloat 42e100]
_ -> assertFailure $ "Expect one series, but got: " ++ show ss
-------------------------------------------------
data Val = Val Int deriving (Eq, Show)
instance ToSeriesData Val where
toSeriesColumns _ = V.fromList ["value"]
toSeriesPoints (Val n) = V.fromList [toValue n]
instance FromSeriesData Val where
parseSeriesData = withValues $ \values -> Val <$> values .: "value"
-------------------------------------------------
dropDatabaseIfExists :: Config -> Text -> IO ()
dropDatabaseIfExists config name =
dropDatabase config name
`catchAll` \_ -> return ()
deleteClusterAdminIfExists :: Config -> Text -> IO ()
deleteClusterAdminIfExists config name =
deleteClusterAdmin config (Admin name)
`catchAll` \_ -> return ()
-------------------------------------------------
runTest :: (Config -> IO a) -> IO a
runTest f = do
pool <- newServerPool localServer []
HC.withManager settings (f . Config rootCreds pool)
where
settings = HC.defaultManagerSettings
newName :: IO Text
newName = do
uniq <- newUnique
return $ T.pack $ "test_" ++ show (hashUnique uniq)
withTestDatabase :: Config -> (Text -> IO a) -> IO a
withTestDatabase config = bracket acquire release
where
acquire = do
name <- newName
dropDatabaseIfExists config name
createDatabase config name
return name
release = dropDatabase config
catchAll :: IO a -> (SomeException -> IO a) -> IO a
catchAll = E.catch
assertStatusCodeException :: Show a => IO a -> IO ()
assertStatusCodeException io = do
r <- try io
case r of
Left e -> case fromException e of
Just HC.StatusCodeException {} -> return ()
_ ->
assertFailure $ "Expect a StatusCodeException, but got " ++ show e
Right ss -> assertFailure $ "Expect an exception, but got " ++ show ss
-------------------------------------------------
main :: IO ()
main = $defaultMainGenerator
-------------------------------------------------
-- Instance deriving
deriveSeriesData defaultOptions
{ fieldLabelModifier = stripPrefixLower "wholeFloat" }
''WholeFloat