packages feed

couchdb-enumerator-0.3.5: test/Database/CouchDB/Enumerator/Test/Util.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Database.CouchDB.Enumerator.Test.Util (
      CouchT
    , testCouch
    , testCouchCase
    , testCouchProperty
    , isSubmapOf
    , assertStr
    , assertObjMember
    , checkError
    , assertRecvError
    , checkRevision
    , checkLoad
    , clearObject
    , ArbitraryObject(..)
)where

import Control.Applicative
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Exception.Lifted as E
import Control.Monad
import Control.Monad.Trans.Reader
import qualified Data.Aeson as A
import qualified Data.HashMap.Lazy as M
import Data.Maybe (fromJust)
import Database.CouchDB.Enumerator
import qualified Data.Text as T
import qualified Data.Vector as V

import Test.Framework (Test)
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Test.Framework.Providers.HUnit (testCase)
import Test.QuickCheck
import Test.QuickCheck.Monadic
import Test.HUnit hiding (Test, path)

type CouchT m a = ReaderT CouchConnection m a

testCouch :: CouchT IO a -> IO ()
testCouch c = withCouchConnection "localhost" 5984 "testcouchenum" (runReaderT c) >> return ()

testCouchCase :: String -> CouchT IO a -> Test
testCouchCase s c = testCase s $ testCouch c

testCouchProperty :: (Show a, Arbitrary a) => String -> (Int,Int) -> ([a] -> CouchT IO b) -> Test
testCouchProperty s i f = testProperty s $ monadicIO $ do
    len <- pick $ choose i
    lst <- pick $ vector len
    run $ testCouch $ f lst

-- | Assert that the value is a string, and check that it matches the given string
assertStr :: T.Text -> A.Value -> Assertion
assertStr t (A.String t') = unless (t == t') $ assertFailure $ "strings are not equal. expecting " 
                                                   ++ T.unpack t ++ "  received  " ++ T.unpack t'
assertStr _ _ = assertFailure "expecting a JSON string"

member :: T.Text -> A.Object -> Bool
member k o = M.lookup k o /= Nothing

isSubmapOf :: A.Object -> A.Object -> Bool
isSubmapOf x y = 0 == M.size (M.difference x y)

-- | Assert that the given key exists, and the value matches the given assertion
assertObjMember :: T.Text -> (A.Value -> Assertion) -> A.Object -> Assertion
assertObjMember t f x = do
    assertBool (T.unpack t ++ " is missing") $ member t x
    f $ fromJust $ M.lookup t x

-- | Check an action for a couch error
checkError :: MonadBaseControl IO m => Maybe Int -> m () -> m ()
checkError code m = E.catch m handler
  where handler e@(CouchError c _) = unless (c == code) $ E.throwIO e

-- | Expect a couch error with the given code
assertRecvError :: (MonadIO m, MonadBaseControl IO m) => Maybe Int -> m a -> m ()
assertRecvError code v = checkError code $ v >> liftIO (assertFailure "was expecting a couch error")

-- | Check that an object in the database matches the given value.
checkLoad :: String -> A.Object -> CouchT IO ()
checkLoad n obj = do
    obj' <- couchGet n []
    lift $ assertBool "returned object does not match" $ isSubmapOf obj obj'

checkRevision :: String -> Revision -> CouchT IO ()
checkRevision n r = do
    r' <- couchRev n
    lift $ assertBool "returned revision does not match" $ r == r'

-- | Delete the given object, useful for the start of a test
clearObject :: String -> CouchT IO ()
clearObject n = checkError (Just 404) go
  where go = do rev <- couchRev n
                couchDelete n rev

newtype ArbitraryObject = ArbitraryObject { unArbObject :: A.Object }
    deriving (Show,Eq,A.FromJSON,A.ToJSON)

instance Arbitrary T.Text where
    arbitrary = liftM T.pack $ listOf $ elements $ ['a'..'z'] ++ ['A'..'Z'] ++ " 1234567890!@#$%^&*()+|"
    shrink "" = []
    shrink x  = [T.tail x]

arbBaseValue :: Gen A.Value
arbBaseValue = oneof [ A.String <$> arbitrary
                     , A.toJSON <$> (arbitrary :: Gen Integer)
                     , A.Bool <$> arbitrary
                     , return A.Null
                     ]

arbObject :: Bool -> Gen A.Object
arbObject onlyBase = do nkeys <- choose (3,15)
                        keys <- vectorOf nkeys arbitrary
                        vals <- vectorOf nkeys $ if onlyBase
                                                    then arbBaseValue
                                                    else frequency [ (8, arbBaseValue)
                                                                   , (1, A.Object <$> arbObject False)
                                                                   , (1, A.Array <$> arbArrayOfObj)
                                                                   ]

                        return $ M.fromList $ zip keys vals 

arbArrayOfObj :: Gen A.Array
arbArrayOfObj = do len <- choose (1,20)
                   vals <- vectorOf len (A.Object <$> arbObject False)
                   return $ V.fromList vals

instance Arbitrary ArbitraryObject where
    arbitrary = ArbitraryObject <$> arbObject True