persistent-pagination-0.1.1.0: test/Database/Esqueleto/PaginationSpec.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Database.Esqueleto.PaginationSpec where
import Conduit
import Control.Concurrent
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Reader
import qualified Data.List as List
import qualified Data.Map as Map
import Data.Maybe
import qualified Data.Set as Set
import Data.Time
import Database.Persist.Sqlite hiding ((==.))
import Database.Persist.TH
import Test.Hspec
import Test.QuickCheck
import Database.Esqueleto.Pagination
import Database.Esqueleto (val, (==.), (^.))
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistUpperCase|
User
name String
age Int
createdAt UTCTime
deriving Eq Ord Show
|]
spec :: Spec
spec = do
it "streamEntities descend" $ do
xs <- runDb $ do
runConduit
$ streamEntities
emptyQuery
UserCreatedAt
(PageSize 10)
Descend
(Range Nothing Nothing)
.| sinkList
let sortedKeys = List.sort (map entityKey xs)
map length (List.group sortedKeys)
`shouldBe`
map length (map (:[]) sortedKeys)
it "streamEntities ascend" $ do
xs <- runDb $ do
runConduit
$ streamEntities
emptyQuery
UserCreatedAt
(PageSize 10)
Ascend
(Range Nothing Nothing)
.| sinkList
length xs `shouldBe` entityCount
Set.toList (Set.fromList (map entityKey xs))
`shouldBe`
List.sort (map entityKey xs)
it "getPage" $ do
let pgSize = 10
Just page <- runDb $ do
getPage emptyQuery UserCreatedAt (PageSize pgSize) Ascend (Range Nothing Nothing :: DesiredRange UTCTime)
let records1 = pageRecords page
length records1
`shouldBe`
pgSize
pageRecordCount page
`shouldBe`
pgSize
let mmin = rangeMin (pageRange page)
mpage2 <- runDb $ do
nextPage page
void mpage2 `shouldSatisfy` isJust
let Just page2 = mpage2
records2 = pageRecords page2
length records2
`shouldBe`
pageRecordCount page2
(Set.fromList records1 `Set.intersection` Set.fromList records2)
`shouldBe` Set.empty
(Set.fromList (map entityKey records1) `Set.intersection` Set.fromList (map entityKey records2))
`shouldBe` Set.empty
it "works for all pages" $ do
pages <- runDb $ do
Just page <-
getPage emptyQuery UserCreatedAt (PageSize 10) Ascend (Range Nothing Nothing :: DesiredRange UTCTime)
whileJust page nextPage
let sortedKeys =
List.sort (concatMap (map entityKey . pageRecords) pages)
List.group sortedKeys `shouldBe` map pure sortedKeys
it "works for id" $ do
(records0, records1) <-
runDb $
(,) <$> do
runConduit
$ streamEntities
emptyQuery
UserId
(PageSize 10)
Ascend
(Range Nothing Nothing)
.| sinkList
<*> do
selectList [] []
length (Set.fromList records0)
`shouldBe`
entityCount
let mkMap = Map.fromList . map (\e -> (entityKey e, entityVal e))
r0map = mkMap records0
r1map = mkMap records1
Map.keys r0map
`shouldBe`
Map.keys r1map
void $ flip Map.traverseWithKey r0map $ \k a ->
Map.lookup k r1map
`shouldBe`
Just a
void $ flip Map.traverseWithKey r1map $ \k a ->
Map.lookup k r0map
`shouldBe`
Just a
it "works with a filter" $ do
let searchAge = 1234
usersWithSearchAge <-
traverse
(\k -> fmap k getCurrentTime)
[User "foo" searchAge, User "bar" searchAge, User "baz" searchAge]
irrelevantUsers <-
traverse
(\k -> fmap k getCurrentTime)
[User "foo" 0, User "bar" 0, User "baz" 0, User "quux" 0]
(returnedEntities, properUserIds) <-
runDb $ do
userIdsWithSearchAge <- insertMany usersWithSearchAge
_ <- insertMany irrelevantUsers
records <- runConduit
$ streamEntities
(\e -> e ^. UserAge ==. val searchAge)
UserCreatedAt
(PageSize 1)
Descend
(Range Nothing Nothing)
.| sinkList
pure (records, userIdsWithSearchAge)
length returnedEntities
`shouldBe`
length usersWithSearchAge
forM_ returnedEntities $ \entity ->
entity
`shouldSatisfy`
((searchAge ==) . userAge . entityVal)
Set.fromList (map entityVal returnedEntities)
`shouldBe`
Set.fromList usersWithSearchAge
Set.fromList (map entityKey returnedEntities)
`shouldBe`
Set.fromList properUserIds
whileJust :: Monad m => a -> (a -> m (Maybe a)) -> m [a]
whileJust a k = (a :) <$> do
ma <- k a
case ma of
Nothing -> pure []
Just a' -> whileJust a' k
runDb :: SqlPersistM a -> IO a
runDb action = do
runSqlite ":memory:" $ do
runMigration migrateAll
seedDatabase
action
entityCount :: Int
entityCount = 63
seedDatabase :: SqlPersistM ()
seedDatabase = do
let now = UTCTime (fromGregorian 1990 1 1) 0
forM_ [1..entityCount] $ \n -> do
str <- liftIO $ generate $ do
i <- choose (5, 20)
vectorOf i arbitrary
insert $ User str n ((50 * fromIntegral n) `addUTCTime` now)
typeChecksWithSqlReadT
:: MonadIO m
=> ConduitT Void (Entity User) (ReaderT SqlReadBackend m) ()
typeChecksWithSqlReadT =
streamEntities
emptyQuery
UserCreatedAt
(PageSize 10)
Descend
(Range Nothing Nothing)
typeChecksWithSqlWriteT
:: MonadIO m
=> ConduitT Void (Entity User) (ReaderT SqlWriteBackend m) ()
typeChecksWithSqlWriteT =
streamEntities
emptyQuery
UserCreatedAt
(PageSize 10)
Descend
(Range Nothing Nothing)