mergeful-persistent-0.1.0.0: test/Data/Mergeful/Persistent/SingleClientSpec.hs
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Data.Mergeful.Persistent.SingleClientSpec
( spec,
)
where
import Control.Monad.Reader
import Data.List
import qualified Data.Map as M
import Data.Mergeful
import Database.Persist.Sql
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.Validity
import TestUtils
{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}
spec :: Spec
spec = modifyMaxShrinks (const 0) $
oneClientSpec $ do
describe "sanity" $ do
describe "setupClient & clientGetStore" $ do
it "roundtrips" $ \te -> forAllValid $ \cstore -> runTest te $ do
setupClient cstore
cstore' <- clientGetStore
liftIO $ cstore' `shouldBe` cstore
describe "setupServer & serverGetStore" $ do
it "roundtrips" $ \te -> forAllValid $ \sstore -> runTest te $ do
setupServer sstore
sstore' <- serverGetStore
liftIO $ sstore' `shouldBe` sstore
describe "mergeFromServerStrategy" $ do
let strat = mergeFromServerStrategy
mergeFunctionSpec strat
emptyResponseSpec strat
describe "mergeFromClientStrategy" $ do
let strat = mergeFromClientStrategy
mergeFunctionSpec strat
xdescribe "does not hold" $ emptyResponseSpec strat
describe "mergeUsingCRDTStrategy" $ do
let strat = mergeUsingCRDTStrategy max
mergeFunctionSpec strat
emptyResponseSpec strat
mergeFunctionSpec :: ItemMergeStrategy Thing -> SpecWith TestEnv
mergeFunctionSpec strat = do
let mergeFunc = clientMergeSyncResponse strat
describe "Single Client" $ do
describe "Single item" $ do
it "succesfully downloads an item from the server for an empty client" $ \te ->
forAllValid $ \uuid ->
forAllValid $ \tst -> runTest te $ do
let sstore1 = ServerStore $ M.singleton uuid tst
setupClient initialClientStore
setupServer sstore1
req <- clientMakeSyncRequest
resp <- serverProcessSync req
sstore2 <- serverGetStore
mergeFunc resp
cstore2 <- clientGetStore
lift $ do
sstore2 `shouldBe` sstore1
clientStoreSyncedItems cstore2 `shouldBe` serverStoreItems sstore2
it "succesfully uploads an item to the server for an empty server" $ \te ->
forAllValid $ \cid ->
forAllValid $ \t ->
runTest te $ do
let cstore1 = initialClientStore {clientStoreAddedItems = M.singleton cid t}
let sstore1 = initialServerStore
setupClient cstore1
setupServer sstore1
req <- clientMakeSyncRequest
resp <- serverProcessSync req
sstore2 <- serverGetStore
mergeFunc resp
cstore2 <- clientGetStore
lift $ do
sort (M.elems (M.map timedValue (clientStoreSyncedItems cstore2)))
`shouldBe` sort [t]
clientStoreSyncedItems cstore2 `shouldBe` serverStoreItems sstore2
describe "Multiple items" $ do
it "succesfully downloads an item from the server for an empty client" $ \te ->
forAllValid $ \sstore1 -> runTest te $ do
setupClient initialClientStore
setupServer sstore1
req <- clientMakeSyncRequest
resp <- serverProcessSync req
sstore2 <- serverGetStore
mergeFunc resp
cstore2 <- clientGetStore
lift $ do
sstore2 `shouldBe` sstore1
clientStoreSyncedItems cstore2 `shouldBe` serverStoreItems sstore2
it "succesfully uploads everything to the server for an empty server" $ \te ->
forAllValid $ \items ->
runTest te $ do
let cstore1 = initialClientStore {clientStoreAddedItems = items}
let sstore1 = initialServerStore
setupClient cstore1
setupServer sstore1
req <- clientMakeSyncRequest
resp <- serverProcessSync req
sstore2 <- serverGetStore
mergeFunc resp
cstore2 <- clientGetStore
lift $ do
sort (M.elems (M.map timedValue (clientStoreSyncedItems cstore2)))
`shouldBe` sort (M.elems items)
clientStoreSyncedItems cstore2 `shouldBe` serverStoreItems sstore2
emptyResponseSpec :: ItemMergeStrategy Thing -> SpecWith TestEnv
emptyResponseSpec strat = do
let mergeFunc = clientMergeSyncResponse strat
it "is returns an empty response on the second sync with no modifications" $ \te -> forAllValid $ \cstore1 -> forAllValid $ \sstore1 ->
runTest te $ do
setupClient cstore1
setupServer sstore1
req1 <- clientMakeSyncRequest
resp1 <- serverProcessSync req1
mergeFunc resp1
req2 <- clientMakeSyncRequest
resp2 <- serverProcessSync req2
lift $ resp2 `shouldBe` emptySyncResponse
type T a = ReaderT TestEnv IO a
runTest :: TestEnv -> T a -> IO a
runTest = flip runReaderT
runClientDB :: SqlPersistT IO a -> T a
runClientDB func = do
pool <- asks testEnvClientPool
liftIO $ runSqlPool func pool
runServerDB :: SqlPersistT IO a -> T a
runServerDB func = do
pool <- asks testEnvServerPool
liftIO $ runSqlPool func pool
type CS = ClientStore ClientThingId ServerThingId Thing
type SReq = SyncRequest ClientThingId ServerThingId Thing
type SS = ServerStore ServerThingId Thing
type SResp = SyncResponse ClientThingId ServerThingId Thing
setupClient :: CS -> T ()
setupClient = runClientDB . setupClientThingQuery
setupServer :: SS -> T ()
setupServer = runServerDB . setupServerThingQuery
clientGetStore :: T CS
clientGetStore = runClientDB clientGetStoreThingQuery
clientMakeSyncRequest :: T SReq
clientMakeSyncRequest = runClientDB clientMakeSyncRequestThingQuery
serverGetStore :: T SS
serverGetStore = runServerDB serverGetStoreThingQuery
serverProcessSync :: SReq -> T SResp
serverProcessSync = runServerDB . serverProcessSyncThingQuery
clientMergeSyncResponse :: ItemMergeStrategy Thing -> SResp -> T ()
clientMergeSyncResponse strat = runClientDB . clientMergeSyncResponseThingQuery strat
data TestEnv = TestEnv
{ testEnvClientPool :: ConnectionPool,
testEnvServerPool :: ConnectionPool
}
oneClientSpec :: SpecWith TestEnv -> Spec
oneClientSpec = around withTestEnv
withTestEnv :: (TestEnv -> IO a) -> IO a
withTestEnv func =
withServerPool $ \serverPool -> withClientPool $ \clientPool -> do
let tenv = TestEnv {testEnvClientPool = clientPool, testEnvServerPool = serverPool}
liftIO $ func tenv