packages feed

genvalidity-mergeful 0.0.0.0 → 0.1.0.0

raw patch · 10 files changed

+752/−700 lines, 10 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.GenValidity.Mergeful: instance (Data.GenValidity.GenUnchecked i, GHC.Classes.Ord i, Data.GenValidity.GenUnchecked a) => Data.GenValidity.GenUnchecked (Data.Mergeful.ClientStore i a)
- Data.GenValidity.Mergeful: instance (Data.GenValidity.GenUnchecked i, GHC.Classes.Ord i, Data.GenValidity.GenUnchecked a) => Data.GenValidity.GenUnchecked (Data.Mergeful.ServerStore i a)
- Data.GenValidity.Mergeful: instance (Data.GenValidity.GenUnchecked i, GHC.Classes.Ord i, Data.GenValidity.GenUnchecked a) => Data.GenValidity.GenUnchecked (Data.Mergeful.SyncRequest i a)
- Data.GenValidity.Mergeful: instance (Data.GenValidity.GenUnchecked i, GHC.Classes.Ord i, Data.GenValidity.GenUnchecked a) => Data.GenValidity.GenUnchecked (Data.Mergeful.SyncResponse i a)
- Data.GenValidity.Mergeful: instance (Data.GenValidity.GenValid i, GHC.Classes.Ord i, Data.GenValidity.GenValid a) => Data.GenValidity.GenValid (Data.Mergeful.ClientStore i a)
- Data.GenValidity.Mergeful: instance (Data.GenValidity.GenValid i, GHC.Classes.Ord i, Data.GenValidity.GenValid a) => Data.GenValidity.GenValid (Data.Mergeful.ServerStore i a)
- Data.GenValidity.Mergeful: instance (Data.GenValidity.GenValid i, GHC.Classes.Ord i, Data.GenValidity.GenValid a) => Data.GenValidity.GenValid (Data.Mergeful.SyncRequest i a)
- Data.GenValidity.Mergeful: instance (Data.GenValidity.GenValid i, GHC.Classes.Ord i, Data.GenValidity.GenValid a) => Data.GenValidity.GenValid (Data.Mergeful.SyncResponse i a)
- Data.GenValidity.Mergeful: instance Data.GenValidity.GenUnchecked Data.Mergeful.ClientId
- Data.GenValidity.Mergeful: instance Data.GenValidity.GenValid Data.Mergeful.ClientId
- Data.GenValidity.Mergeful: mapWithIds :: (Ord i, GenValid a) => Set i -> Gen (Map i a)
- Data.GenValidity.Mergeful: splitSet :: Ord i => Set i -> Gen (Set i, Set i)
+ Data.GenValidity.Mergeful.Collection: instance (Data.GenValidity.GenUnchecked i, GHC.Classes.Ord i, Data.GenValidity.GenUnchecked a) => Data.GenValidity.GenUnchecked (Data.Mergeful.Collection.ClientStore i a)
+ Data.GenValidity.Mergeful.Collection: instance (Data.GenValidity.GenUnchecked i, GHC.Classes.Ord i, Data.GenValidity.GenUnchecked a) => Data.GenValidity.GenUnchecked (Data.Mergeful.Collection.ServerStore i a)
+ Data.GenValidity.Mergeful.Collection: instance (Data.GenValidity.GenUnchecked i, GHC.Classes.Ord i, Data.GenValidity.GenUnchecked a) => Data.GenValidity.GenUnchecked (Data.Mergeful.Collection.SyncRequest i a)
+ Data.GenValidity.Mergeful.Collection: instance (Data.GenValidity.GenUnchecked i, GHC.Classes.Ord i, Data.GenValidity.GenUnchecked a) => Data.GenValidity.GenUnchecked (Data.Mergeful.Collection.SyncResponse i a)
+ Data.GenValidity.Mergeful.Collection: instance (Data.GenValidity.GenValid i, GHC.Show.Show i, GHC.Classes.Ord i, Data.GenValidity.GenValid a) => Data.GenValidity.GenValid (Data.Mergeful.Collection.ClientStore i a)
+ Data.GenValidity.Mergeful.Collection: instance (Data.GenValidity.GenValid i, GHC.Show.Show i, GHC.Classes.Ord i, Data.GenValidity.GenValid a) => Data.GenValidity.GenValid (Data.Mergeful.Collection.ServerStore i a)
+ Data.GenValidity.Mergeful.Collection: instance (Data.GenValidity.GenValid i, GHC.Show.Show i, GHC.Classes.Ord i, Data.GenValidity.GenValid a) => Data.GenValidity.GenValid (Data.Mergeful.Collection.SyncRequest i a)
+ Data.GenValidity.Mergeful.Collection: instance (Data.GenValidity.GenValid i, GHC.Show.Show i, GHC.Classes.Ord i, Data.GenValidity.GenValid a) => Data.GenValidity.GenValid (Data.Mergeful.Collection.SyncResponse i a)
+ Data.GenValidity.Mergeful.Collection: instance Data.GenValidity.GenUnchecked Data.Mergeful.Collection.ClientId
+ Data.GenValidity.Mergeful.Collection: instance Data.GenValidity.GenValid Data.Mergeful.Collection.ClientId
+ Data.GenValidity.Mergeful.Collection: mapWithIds :: (Ord i, GenValid a) => Set i -> Gen (Map i a)
+ Data.GenValidity.Mergeful.Collection: splitSet :: Ord i => Set i -> Gen (Set i, Set i)

Files

+ CHANGELOG.md view
@@ -0,0 +1,9 @@+# Changelog++## [Unreleased]++## [0.1.0.0] - 2019-09-23++### Changed++* Compatibility with validity-containers >=0.5
− ChangeLog.md
@@ -1,3 +0,0 @@-# Changelog for mergeful--## Unreleased changes
genvalidity-mergeful.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.1.+-- This file has been generated from package.yaml by hpack version 0.31.2. -- -- see: https://github.com/sol/hpack ----- hash: dec5147002ec3be045920638188c5c8157bfce8d1d6778dd8186b06b56fa110d+-- hash: 86196b99586836b920b517c4273efac7cc705762e7ff8c445e911cfdafe35620  name:           genvalidity-mergeful-version:        0.0.0.0+version:        0.1.0.0 description:    Please see the README on GitHub at <https://github.com/NorfairKing/mergeful#readme> homepage:       https://github.com/NorfairKing/mergeful#readme bug-reports:    https://github.com/NorfairKing/mergeful/issues@@ -19,7 +19,7 @@ build-type:     Simple extra-source-files:     README.md-    ChangeLog.md+    CHANGELOG.md  source-repository head   type: git@@ -28,6 +28,7 @@ library   exposed-modules:       Data.GenValidity.Mergeful+      Data.GenValidity.Mergeful.Collection       Data.GenValidity.Mergeful.Item       Data.GenValidity.Mergeful.Timed       Data.GenValidity.Mergeful.Value@@ -49,10 +50,10 @@   type: exitcode-stdio-1.0   main-is: Spec.hs   other-modules:+      Data.Mergeful.CollectionSpec       Data.Mergeful.ItemSpec       Data.Mergeful.TimedSpec       Data.Mergeful.ValueSpec-      Data.MergefulSpec       Paths_genvalidity_mergeful   hs-source-dirs:       test
src/Data/GenValidity/Mergeful.hs view
@@ -1,102 +1,5 @@-{-# LANGUAGE RecordWildCards #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}- module Data.GenValidity.Mergeful where -import Data.Map (Map)-import qualified Data.Map as M-import Data.Set (Set)-import qualified Data.Set as S--import Data.Traversable--import Data.GenValidity-import Data.GenValidity.Containers ()-import Data.GenValidity.Time ()--import Test.QuickCheck-+import Data.GenValidity.Mergeful.Collection () import Data.GenValidity.Mergeful.Item ()--import Data.Mergeful--instance GenUnchecked ClientId--instance GenValid ClientId--instance (GenUnchecked i, Ord i, GenUnchecked a) => GenUnchecked (ClientStore i a)--instance (GenValid i, Ord i, GenValid a) => GenValid (ClientStore i a) where-  genValid =-    (`suchThat` isValid) $ do-      identifiers <- scale (* 3) genValid-      (s1, s2) <- splitSet identifiers-      (s3, s4) <- splitSet s1-      clientStoreAddedItems <- genValid-      clientStoreSyncedItems <- mapWithIds s2-      clientStoreSyncedButChangedItems <- mapWithIds s3-      clientStoreDeletedItems <- mapWithIds s4-      pure ClientStore {..}-  shrinkValid = shrinkValidStructurally--instance (GenUnchecked i, Ord i, GenUnchecked a) => GenUnchecked (ServerStore i a)--instance (GenValid i, Ord i, GenValid a) => GenValid (ServerStore i a) where-  genValid = genValidStructurallyWithoutExtraChecking-  shrinkValid = shrinkValidStructurallyWithoutExtraFiltering--instance (GenUnchecked i, Ord i, GenUnchecked a) => GenUnchecked (SyncRequest i a)--instance (GenValid i, Ord i, GenValid a) => GenValid (SyncRequest i a) where-  genValid =-    (`suchThat` isValid) $ do-      identifiers <- scale (* 3) genValid-      (s1, s2) <- splitSet identifiers-      (s3, s4) <- splitSet s1-      syncRequestNewItems <- genValid-      syncRequestKnownItems <- mapWithIds s2-      syncRequestKnownButChangedItems <- mapWithIds s3-      syncRequestDeletedItems <- mapWithIds s4-      pure SyncRequest {..}-  shrinkValid = shrinkValidStructurally--instance (GenUnchecked i, Ord i, GenUnchecked a) => GenUnchecked (SyncResponse i a)--instance (GenValid i, Ord i, GenValid a) => GenValid (SyncResponse i a) where-  genValid =-    (`suchThat` isValid) $ do-      identifiers <- scale (* 10) genValid-      (s01, s02) <- splitSet identifiers-      (s03, s04) <- splitSet s01-      (s05, s06) <- splitSet s02-      (s07, s08) <- splitSet s03-      (s09, s10) <- splitSet s04-      (s11, s12) <- splitSet s05-      (s13, s14) <- splitSet s06-      (s15, s16) <- splitSet s07-      syncResponseClientAdded <--        do m <- (genValid :: Gen (Map ClientId ()))-           if S.null s08-             then pure M.empty-             else forM m $ \() -> (,) <$> elements (S.toList s08) <*> genValid-      syncResponseClientChanged <- mapWithIds s09-      let syncResponseClientDeleted = s10-      syncResponseServerAdded <- mapWithIds s11-      syncResponseServerChanged <- mapWithIds s12-      let syncResponseServerDeleted = s13-      syncResponseConflicts <- mapWithIds s14-      syncResponseConflictsClientDeleted <- mapWithIds s15-      let syncResponseConflictsServerDeleted = s16-      pure SyncResponse {..}-  shrinkValid = shrinkValidStructurally--splitSet :: Ord i => Set i -> Gen (Set i, Set i)-splitSet s =-  if S.null s-    then pure (S.empty, S.empty)-    else do-      a <- elements $ S.toList s-      pure $ S.split a s--mapWithIds :: (Ord i, GenValid a) => Set i -> Gen (Map i a)-mapWithIds s = fmap M.fromList $ for (S.toList s) $ \i -> (,) i <$> genValid+import Data.GenValidity.Mergeful.Value ()
+ src/Data/GenValidity/Mergeful/Collection.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE RecordWildCards #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Data.GenValidity.Mergeful.Collection where++import Data.Map (Map)+import qualified Data.Map as M+import Data.Set (Set)+import qualified Data.Set as S++import Data.Traversable++import Data.GenValidity+import Data.GenValidity.Containers ()+import Data.GenValidity.Time ()++import Test.QuickCheck++import Data.GenValidity.Mergeful.Item ()++import Data.Mergeful++instance GenUnchecked ClientId++instance GenValid ClientId++instance (GenUnchecked i, Ord i, GenUnchecked a) => GenUnchecked (ClientStore i a)++instance (GenValid i, Show i, Ord i, GenValid a) => GenValid (ClientStore i a) where+  genValid =+    (`suchThat` isValid) $ do+      identifiers <- scale (* 3) genValid+      (s1, s2) <- splitSet identifiers+      (s3, s4) <- splitSet s1+      clientStoreAddedItems <- genValid+      clientStoreSyncedItems <- mapWithIds s2+      clientStoreSyncedButChangedItems <- mapWithIds s3+      clientStoreDeletedItems <- mapWithIds s4+      pure ClientStore {..}+  shrinkValid = shrinkValidStructurally++instance (GenUnchecked i, Ord i, GenUnchecked a) => GenUnchecked (ServerStore i a)++instance (GenValid i, Show i, Ord i, GenValid a) => GenValid (ServerStore i a) where+  genValid = genValidStructurallyWithoutExtraChecking+  shrinkValid = shrinkValidStructurallyWithoutExtraFiltering++instance (GenUnchecked i, Ord i, GenUnchecked a) => GenUnchecked (SyncRequest i a)++instance (GenValid i, Show i, Ord i, GenValid a) => GenValid (SyncRequest i a) where+  genValid =+    (`suchThat` isValid) $ do+      identifiers <- scale (* 3) genValid+      (s1, s2) <- splitSet identifiers+      (s3, s4) <- splitSet s1+      syncRequestNewItems <- genValid+      syncRequestKnownItems <- mapWithIds s2+      syncRequestKnownButChangedItems <- mapWithIds s3+      syncRequestDeletedItems <- mapWithIds s4+      pure SyncRequest {..}+  shrinkValid = shrinkValidStructurally++instance (GenUnchecked i, Ord i, GenUnchecked a) => GenUnchecked (SyncResponse i a)++instance (GenValid i, Show i, Ord i, GenValid a) => GenValid (SyncResponse i a) where+  genValid =+    (`suchThat` isValid) $ do+      identifiers <- scale (* 10) genValid+      (s01, s02) <- splitSet identifiers+      (s03, s04) <- splitSet s01+      (s05, s06) <- splitSet s02+      (s07, s08) <- splitSet s03+      (s09, s10) <- splitSet s04+      (s11, s12) <- splitSet s05+      (s13, s14) <- splitSet s06+      (s15, s16) <- splitSet s07+      syncResponseClientAdded <-+        do m <- genValid :: Gen (Map ClientId ())+           if S.null s08+             then pure M.empty+             else forM m $ \() -> (,) <$> elements (S.toList s08) <*> genValid+      syncResponseClientChanged <- mapWithIds s09+      let syncResponseClientDeleted = s10+      syncResponseServerAdded <- mapWithIds s11+      syncResponseServerChanged <- mapWithIds s12+      let syncResponseServerDeleted = s13+      syncResponseConflicts <- mapWithIds s14+      syncResponseConflictsClientDeleted <- mapWithIds s15+      let syncResponseConflictsServerDeleted = s16+      pure SyncResponse {..}+  shrinkValid = shrinkValidStructurally++splitSet :: Ord i => Set i -> Gen (Set i, Set i)+splitSet s =+  if S.null s+    then pure (S.empty, S.empty)+    else do+      a <- elements $ S.toList s+      pure $ S.split a s++mapWithIds :: (Ord i, GenValid a) => Set i -> Gen (Map i a)+mapWithIds s = fmap M.fromList $ for (S.toList s) $ \i -> (,) i <$> genValid
src/Data/GenValidity/Mergeful/Value.hs view
@@ -9,7 +9,9 @@ import Data.GenValidity.Mergeful.Timed ()  instance GenUnchecked ChangedFlag+ instance GenValid ChangedFlag+ instance GenUnchecked a => GenUnchecked (ClientValue a)  instance GenValid a => GenValid (ClientValue a) where
+ test/Data/Mergeful/CollectionSpec.hs view
@@ -0,0 +1,603 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}++module Data.Mergeful.CollectionSpec+  ( spec+  ) where++import Data.Functor.Identity+import Data.List+import qualified Data.Map as M+import qualified Data.Set as S+import Data.UUID+import GHC.Generics (Generic)+import System.Random+import Text.Show.Pretty++import Control.Monad.State++import Test.Hspec+import Test.QuickCheck+import Test.Validity+import Test.Validity.Aeson++import Data.GenValidity.UUID ()++import Data.Mergeful+import Data.Mergeful.Timed++import Data.GenValidity.Mergeful ()+import Data.GenValidity.Mergeful.Item ()++{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}++spec :: Spec+spec = do+  genValidSpec @ClientId+  jsonSpecOnValid @ClientId+  genValidSpec @(ClientStore Int Int)+  jsonSpecOnValid @(ClientStore Int Int)+  genValidSpec @(ServerStore Int Int)+  jsonSpecOnValid @(ServerStore Int Int)+  genValidSpec @(SyncRequest Int Int)+  jsonSpecOnValid @(SyncRequest Int Int)+  genValidSpec @(SyncResponse Int Int)+  jsonSpecOnValid @(SyncResponse Int Int)+  describe "initialClientStore" $ it "is valid" $ shouldBeValid $ initialClientStore @Int @Int+  describe "addItemToClientStore" $ do+    it "produces valid stores" $ producesValidsOnValids2 (addItemToClientStore @Int @Int)+    it "makes the client store one bigger" $+      forAllValid $ \cs ->+        forAllValid $ \a ->+          clientStoreSize @Int @Int (addItemToClientStore a cs) `shouldBe` (clientStoreSize cs + 1)+    it "ensures that the added item is in fact in the result" $+      forAllValid $ \cs ->+        forAllValid $ \a ->+          let cs' = addItemToClientStore @Int @Int a cs+           in a `elem` clientStoreAddedItems cs'+  describe "markItemDeletedInClientStore" $ do+    it "produces valid stores" $ producesValidsOnValids2 (markItemDeletedInClientStore @Int @Int)+    it "makes the client store one smaller" $+      let p cs = S.size (clientStoreUndeletedSyncIdSet cs) > 0+       in forAllShrink (genValid `suchThat` p) (filter p . shrinkValid) $ \cs ->+            forAll (elements $ S.toList $ clientStoreUndeletedSyncIdSet cs) $ \i ->+              (clientStoreSize @Int @Int (markItemDeletedInClientStore i cs) + 1) `shouldBe`+              clientStoreSize cs+    it "ensures that the added item is in fact not in the result" $+      let p cs = S.size (clientStoreUndeletedSyncIdSet cs) > 0+       in forAllShrink (genValid `suchThat` p) (filter p . shrinkValid) $ \cs ->+            forAll (elements $ S.toList $ clientStoreUndeletedSyncIdSet cs) $ \i ->+              let cs' = markItemDeletedInClientStore @Int @Int i cs+               in M.lookup (Right i) (clientStoreItems cs') `shouldBe` Nothing+  describe "changeItemInClientStore" $ do+    it "produces valid stores" $ producesValidsOnValids3 (changeItemInClientStore @Int @Int)+    it "doesn't change the size of the client store" $+      let p cs = S.size (clientStoreUndeletedSyncIdSet cs) > 0+       in forAllShrink (genValid `suchThat` p) (filter p . shrinkValid) $ \cs ->+            forAll (elements $ S.toList $ clientStoreUndeletedSyncIdSet cs) $ \i ->+              forAllValid $ \a ->+                clientStoreSize @Int @Int (changeItemInClientStore i a cs) `shouldBe`+                clientStoreSize cs+    it "ensures that the changed item is in fact in the result" $+      let p cs = S.size (clientStoreUndeletedSyncIdSet cs) > 0+       in forAllShrink (genValid `suchThat` p) (filter p . shrinkValid) $ \cs ->+            forAll (elements $ S.toList $ clientStoreUndeletedSyncIdSet cs) $ \i ->+              forAllValid $ \a ->+                let cs' = changeItemInClientStore @Int @Int i a cs+                 in M.lookup (Right i) (clientStoreItems cs') `shouldBe` Just a+  describe "deleteItemFromClientStore" $ do+    it "produces valid stores" $ producesValidsOnValids2 (deleteItemFromClientStore @Int @Int)+    it "makes the client store one smaller" $+      let p cs = S.size (clientStoreClientIdSet cs) > 0+       in forAllShrink (genValid `suchThat` p) (filter p . shrinkValid) $ \cs ->+            forAll (elements $ S.toList $ clientStoreClientIdSet cs) $ \cid ->+              (clientStoreSize @Int @Int (deleteItemFromClientStore cid cs) + 1) `shouldBe`+              clientStoreSize cs+  describe "initialServerStore" $ it "is valid" $ shouldBeValid $ initialServerStore @Int @Int+  describe "initialSyncRequest" $ it "is valid" $ shouldBeValid $ initialSyncRequest @Int @Int+  describe "emptySyncResponse" $ it "is valid" $ shouldBeValid $ emptySyncResponse @Int @Int+  describe "makeSyncRequest" $+    it "produces valid requests" $ producesValidsOnValids (makeSyncRequest @Int @Int)+  describe "mergeAddedItems" $+    it "produces valid results" $ producesValidsOnValids2 (mergeAddedItems @Int @Int)+  describe "mergeSyncedButChangedItems" $+    it "produces valid results" $ producesValidsOnValids2 (mergeSyncedButChangedItems @Int @Int)+  describe "mergeAddedItems" $+    it "produces valid results" $ producesValidsOnValids2 (mergeAddedItems @Int @Int)+  describe "mergeSyncedButChangedItems" $+    it "produces valid results" $ producesValidsOnValids2 (mergeSyncedButChangedItems @Int @Int)+  describe "mergeDeletedItems" $+    it "produces valid results" $ producesValidsOnValids2 (mergeDeletedItems @Int @Int)+  describe "mergeSyncResponseIgnoreProblems" $+    it "produces valid requests" $+    forAllValid $ \store ->+      forAllValid $ \response ->+        let res = mergeSyncResponseIgnoreProblems @Int @Int store response+         in case prettyValidate res of+              Right _ -> pure ()+              Left err ->+                expectationFailure $+                unlines+                  [ "Store:"+                  , ppShow store+                  , "Response:"+                  , ppShow response+                  , "Invalid result:"+                  , ppShow res+                  , "error:"+                  , err+                  ]+  describe "processServerSync" $+    it "produces valid tuples of a response and a store" $+    producesValidsOnValids2+      (\store request -> evalD $ processServerSync genD (store :: ServerStore UUID Int) request)+  describe "Syncing with mergeSyncResponseIgnoreProblems" $ do+    mergeFunctionSpec @Int mergeSyncResponseIgnoreProblems+    it "does not lose data after a conflict occurs" $+      forAllValid $ \uuid ->+        forAllValid $ \time1 ->+          forAllValid $ \i1 ->+            forAllValid $ \i2 ->+              forAllValid $ \i3 ->+                evalDM $ do+                  let sstore1 =+                        ServerStore+                          {serverStoreItems = M.singleton (uuid :: UUID) (Timed (i1 :: Int) time1)}+                      -- The server has an item+                      -- The first client has synced it, and modified it.+                  let cAstore1 =+                        initialClientStore+                          {clientStoreSyncedButChangedItems = M.singleton uuid (Timed i2 time1)}+                      -- The second client has synced it too, and modified it too.+                  let cBstore1 =+                        initialClientStore+                          {clientStoreSyncedButChangedItems = M.singleton uuid (Timed i3 time1)}+                      -- Client A makes sync request 1+                  let req1 = makeSyncRequest cAstore1+                      -- The server processes sync request 1+                  (resp1, sstore2) <- processServerSync genD sstore1 req1+                  let time2 = incrementServerTime time1+                      -- The server updates the item accordingly+                  lift $ do+                    resp1 `shouldBe`+                      (emptySyncResponse {syncResponseClientChanged = M.singleton uuid time2})+                    sstore2 `shouldBe`+                      (ServerStore {serverStoreItems = M.singleton uuid (Timed i2 time2)})+                      -- Client A merges the response+                  let cAstore2 = mergeSyncResponseIgnoreProblems cAstore1 resp1+                  lift $+                    cAstore2 `shouldBe`+                    (initialClientStore {clientStoreSyncedItems = M.singleton uuid (Timed i2 time2)})+                      -- Client B makes sync request 2+                  let req2 = makeSyncRequest cBstore1+                      -- The server processes sync request 2+                  (resp2, sstore3) <- processServerSync genD sstore2 req2+                      -- The server reports a conflict and does not change its store+                  lift $ do+                    resp2 `shouldBe`+                      (emptySyncResponse {syncResponseConflicts = M.singleton uuid (Timed i2 time2)})+                    sstore3 `shouldBe` sstore2+                      -- Client B merges the response+                  let cBstore2 = mergeSyncResponseIgnoreProblems cBstore1 resp2+                      -- Client does not update, but keeps its conflict+                  lift $+                    cBstore2 `shouldBe`+                    (initialClientStore+                       {clientStoreSyncedButChangedItems = M.singleton uuid (Timed i3 time1)})+                      -- Client A and Client B now *do not* have the same store+  describe "Syncing with mergeSyncResponseFromServer" $ do+    mergeFunctionSpec @Int mergeSyncResponseFromServer+    describe "mergeSyncResponseFromServer" $+      it "only differs from mergeSyncResponseIgnoreProblems on conflicts" $+      forAllValid $ \cstore ->+        forAllValid $ \sresp@SyncResponse {..} -> do+          let cstoreA = mergeSyncResponseFromServer (cstore :: ClientStore UUID Int) sresp+              cstoreB = mergeSyncResponseIgnoreProblems cstore sresp+          if cstoreA == cstoreB+            then pure ()+            else unless+                   (or+                      [ not (M.null syncResponseConflicts)+                      , not (M.null syncResponseConflictsClientDeleted)+                      , not (S.null syncResponseConflictsServerDeleted)+                      ]) $+                 expectationFailure $+                 unlines+                   [ "There was a difference between mergeSyncResponseFromServer and mergeSyncResponseIgnoreProblems that was somehow unrelated to the conflicts:"+                   , "syncResponseConflicts:"+                   , ppShow syncResponseConflicts+                   , "syncResponseConflictsClientDeleted:"+                   , ppShow syncResponseConflictsClientDeleted+                   , "syncResponseConflictsServerDeleted:"+                   , ppShow syncResponseConflictsServerDeleted+                   , "client store after mergeSyncResponseFromServer:"+                   , ppShow cstoreA+                   , "client store after mergeSyncResponseIgnoreProblems:"+                   , ppShow cstoreB+                   ]+    it "does not diverge after a conflict occurs" $+      forAllValid $ \uuid ->+        forAllValid $ \time1 ->+          forAllValid $ \i1 ->+            forAllValid $ \i2 ->+              forAllValid $ \i3 ->+                evalDM $ do+                  let sstore1 =+                        ServerStore+                          {serverStoreItems = M.singleton (uuid :: UUID) (Timed (i1 :: Int) time1)}+                      -- The server has an item+                      -- The first client has synced it, and modified it.+                  let cAstore1 =+                        initialClientStore+                          {clientStoreSyncedButChangedItems = M.singleton uuid (Timed i2 time1)}+                      -- The second client has synced it too, and modified it too.+                  let cBstore1 =+                        initialClientStore+                          {clientStoreSyncedButChangedItems = M.singleton uuid (Timed i3 time1)}+                      -- Client A makes sync request 1+                  let req1 = makeSyncRequest cAstore1+                      -- The server processes sync request 1+                  (resp1, sstore2) <- processServerSync genD sstore1 req1+                  let time2 = incrementServerTime time1+                      -- The server updates the item accordingly+                  lift $ do+                    resp1 `shouldBe`+                      (emptySyncResponse {syncResponseClientChanged = M.singleton uuid time2})+                    sstore2 `shouldBe`+                      (ServerStore {serverStoreItems = M.singleton uuid (Timed i2 time2)})+                      -- Client A merges the response+                  let cAstore2 = mergeSyncResponseFromServer cAstore1 resp1+                  lift $+                    cAstore2 `shouldBe`+                    (initialClientStore {clientStoreSyncedItems = M.singleton uuid (Timed i2 time2)})+                      -- Client B makes sync request 2+                  let req2 = makeSyncRequest cBstore1+                      -- The server processes sync request 2+                  (resp2, sstore3) <- processServerSync genD sstore2 req2+                      -- The server reports a conflict and does not change its store+                  lift $ do+                    resp2 `shouldBe`+                      (emptySyncResponse {syncResponseConflicts = M.singleton uuid (Timed i2 time2)})+                    sstore3 `shouldBe` sstore2+                      -- Client B merges the response+                  let cBstore2 = mergeSyncResponseFromServer cBstore1 resp2+                      -- Client does not update, but keeps its conflict+                  lift $ do+                    cBstore2 `shouldBe`+                      (initialClientStore+                         {clientStoreSyncedItems = M.singleton uuid (Timed i2 time2)})+                        -- Client A and Client B now have the same store+                    cBstore2 `shouldBe` cAstore2+  describe "Syncing with mergeSyncResponseUsingStrategy with a GCounter" $+    mergeFunctionSpec @Int $+    mergeSyncResponseUsingStrategy+      ItemMergeStrategy+        { itemMergeStrategyMergeChangeConflict =+            \clientItem (Timed serverItem t) -> Timed (max clientItem serverItem) t+        , itemMergeStrategyMergeClientDeletedConflict = \serverItem -> Just serverItem+        , itemMergeStrategyMergeServerDeletedConflict = \_ -> Nothing+        }++mergeFunctionSpec ::+     forall a. (Show a, Ord a, GenValid a)+  => (forall i. Ord i =>+                  ClientStore i a -> SyncResponse i a -> ClientStore i a)+  -> Spec+mergeFunctionSpec mergeFunc = do+  describe "Single client" $+    describe "Multi-item" $ do+      it "succesfully downloads everything from the server for an empty client" $+        forAllValid $ \sstore1 ->+          evalDM $ do+            let cstore1 = initialClientStore+            let req = makeSyncRequest cstore1+            (resp, sstore2) <- processServerSync genD sstore1 req+            let cstore2 = mergeFunc cstore1 resp+            lift $ do+              sstore2 `shouldBe` sstore1+              clientStoreSyncedItems cstore2 `shouldBe` serverStoreItems sstore2+      it "succesfully uploads everything to the server for an empty server" $+        forAllValid $ \items ->+          evalDM $ do+            let cstore1 = initialClientStore {clientStoreAddedItems = items}+            let sstore1 = initialServerStore+            let req = makeSyncRequest cstore1+            (resp, sstore2) <- processServerSync genD sstore1 req+            let cstore2 = mergeFunc cstore1 resp+            lift $ do+              sort (M.elems (M.map timedValue (clientStoreSyncedItems cstore2))) `shouldBe`+                sort (M.elems items)+              clientStoreSyncedItems cstore2 `shouldBe` serverStoreItems sstore2+      it "is idempotent with one client" $+        forAllValid $ \cstore1 ->+          forAllValid $ \sstore1 ->+            evalDM $ do+              let req1 = makeSyncRequest cstore1+              (resp1, sstore2) <- processServerSync genD sstore1 req1+              let cstore2 = mergeFunc cstore1 resp1+                  req2 = makeSyncRequest cstore2+              (resp2, sstore3) <- processServerSync genD sstore2 req2+              let cstore3 = mergeFunc cstore2 resp2+              lift $ do+                cstore2 `shouldBe` cstore3+                sstore2 `shouldBe` sstore3+  describe "Multiple clients" $ do+    describe "Single-item" $ do+      it "successfully syncs an addition accross to a second client" $+        forAllValid $ \i ->+          evalDM $ do+            let cAstore1 = initialClientStore {clientStoreAddedItems = M.singleton (ClientId 0) i}+              -- Client B is empty+            let cBstore1 = initialClientStore+              -- The server is empty+            let sstore1 = initialServerStore+              -- Client A makes sync request 1+            let req1 = makeSyncRequest cAstore1+              -- The server processes sync request 1+            (resp1, sstore2) <- processServerSync genD sstore1 req1+            let addedItems = syncResponseClientAdded resp1+            case M.toList addedItems of+              [(ClientId 0, (uuid, st))] -> do+                let time = initialServerTime+                lift $ st `shouldBe` time+                let items = M.singleton uuid (Timed i st)+                lift $ sstore2 `shouldBe` (ServerStore {serverStoreItems = items})+                  -- Client A merges the response+                let cAstore2 = mergeFunc cAstore1 resp1+                lift $ cAstore2 `shouldBe` (initialClientStore {clientStoreSyncedItems = items})+                  -- Client B makes sync request 2+                let req2 = makeSyncRequest cBstore1+                  -- The server processes sync request 2+                (resp2, sstore3) <- processServerSync genD sstore2 req2+                lift $ do+                  resp2 `shouldBe` (emptySyncResponse {syncResponseServerAdded = items})+                  sstore3 `shouldBe` sstore2+                  -- Client B merges the response+                let cBstore2 = mergeFunc cBstore1 resp2+                lift $ cBstore2 `shouldBe` (initialClientStore {clientStoreSyncedItems = items})+                  -- Client A and Client B now have the same store+                lift $ cAstore2 `shouldBe` cBstore2+              _ -> lift $ expectationFailure "Should have found exactly one added item."+      it "successfully syncs a modification accross to a second client" $+        forAllValid $ \uuid ->+          forAllValid $ \i ->+            forAllValid $ \j ->+              forAllValid $ \time1 ->+                evalDM $ do+                  let cAstore1 =+                        initialClientStore+                          {clientStoreSyncedItems = M.singleton uuid (Timed i time1)}+                    -- Client B had synced that same item, but has since modified it+                  let cBstore1 =+                        initialClientStore+                          {clientStoreSyncedButChangedItems = M.singleton uuid (Timed j time1)}+                    -- The server is has the item that both clients had before+                  let sstore1 = ServerStore {serverStoreItems = M.singleton uuid (Timed i time1)}+                    -- Client B makes sync request 1+                  let req1 = makeSyncRequest cBstore1+                    -- The server processes sync request 1+                  (resp1, sstore2) <- processServerSync genD sstore1 req1+                  let time2 = incrementServerTime time1+                  lift $ do+                    resp1 `shouldBe`+                      emptySyncResponse {syncResponseClientChanged = M.singleton uuid time2}+                    sstore2 `shouldBe`+                      ServerStore {serverStoreItems = M.singleton uuid (Timed j time2)}+                    -- Client B merges the response+                  let cBstore2 = mergeFunc cBstore1 resp1+                  lift $+                    cBstore2 `shouldBe`+                    initialClientStore {clientStoreSyncedItems = M.singleton uuid (Timed j time2)}+                    -- Client A makes sync request 2+                  let req2 = makeSyncRequest cAstore1+                    -- The server processes sync request 2+                  (resp2, sstore3) <- processServerSync genD sstore2 req2+                  lift $ do+                    resp2 `shouldBe`+                      emptySyncResponse+                        {syncResponseServerChanged = M.singleton uuid (Timed j time2)}+                    sstore3 `shouldBe` sstore2+                    -- Client A merges the response+                  let cAstore2 = mergeFunc cAstore1 resp2+                  lift $+                    cAstore2 `shouldBe`+                    initialClientStore {clientStoreSyncedItems = M.singleton uuid (Timed j time2)}+                    -- Client A and Client B now have the same store+                  lift $ cAstore2 `shouldBe` cBstore2+      it "succesfully syncs a deletion across to a second client" $+        forAllValid $ \uuid ->+          forAllValid $ \time1 ->+            forAllValid $ \i ->+              evalDM $ do+                let cAstore1 =+                      initialClientStore {clientStoreSyncedItems = M.singleton uuid (Timed i time1)}+                  -- Client A has a synced item.+                  -- Client B had synced that same item, but has since deleted it.+                let cBstore1 = initialClientStore {clientStoreDeletedItems = M.singleton uuid time1}+                  -- The server still has the undeleted item+                let sstore1 = ServerStore {serverStoreItems = M.singleton uuid (Timed i time1)}+                  -- Client B makes sync request 1+                let req1 = makeSyncRequest cBstore1+                  -- The server processes sync request 1+                (resp1, sstore2) <- processServerSync genD sstore1 req1+                lift $ do+                  resp1 `shouldBe` emptySyncResponse {syncResponseClientDeleted = S.singleton uuid}+                  sstore2 `shouldBe` initialServerStore+                  -- Client B merges the response+                let cBstore2 = mergeFunc cBstore1 resp1+                lift $ cBstore2 `shouldBe` initialClientStore+                  -- Client A makes sync request 2+                let req2 = makeSyncRequest cAstore1+                  -- The server processes sync request 2+                (resp2, sstore3) <- processServerSync genD sstore2 req2+                lift $ do+                  resp2 `shouldBe` emptySyncResponse {syncResponseServerDeleted = S.singleton uuid}+                  sstore3 `shouldBe` sstore2+                  -- Client A merges the response+                let cAstore2 = mergeFunc cAstore1 resp2+                lift $ cAstore2 `shouldBe` initialClientStore+                  -- Client A and Client B now have the same store+                lift $ cAstore2 `shouldBe` cBstore2+      it "does not run into a conflict if two clients both try to sync a deletion" $+        forAllValid $ \uuid ->+          forAllValid $ \time1 ->+            forAllValid $ \i ->+              evalDM $ do+                let cAstore1 = initialClientStore {clientStoreDeletedItems = M.singleton uuid time1}+                  -- Both client a and client b delete an item.+                let cBstore1 = initialClientStore {clientStoreDeletedItems = M.singleton uuid time1}+                  -- The server still has the undeleted item+                let sstore1 = ServerStore {serverStoreItems = M.singleton uuid (Timed i time1)}+                  -- Client A makes sync request 1+                let req1 = makeSyncRequest cAstore1+                  -- The server processes sync request 1+                (resp1, sstore2) <- processServerSync genD sstore1 req1+                lift $ do+                  resp1 `shouldBe`+                    (emptySyncResponse {syncResponseClientDeleted = S.singleton uuid})+                  sstore2 `shouldBe` (ServerStore {serverStoreItems = M.empty})+                  -- Client A merges the response+                let cAstore2 = mergeFunc cAstore1 resp1+                lift $ cAstore2 `shouldBe` initialClientStore+                  -- Client B makes sync request 2+                let req2 = makeSyncRequest cBstore1+                  -- The server processes sync request 2+                (resp2, sstore3) <- processServerSync genD sstore2 req2+                lift $ do+                  resp2 `shouldBe`+                    (emptySyncResponse {syncResponseClientDeleted = S.singleton uuid})+                  sstore3 `shouldBe` sstore2+                  -- Client B merges the response+                let cBstore2 = mergeFunc cBstore1 resp2+                lift $ do+                  cBstore2 `shouldBe` initialClientStore+                    -- Client A and Client B now have the same store+                  cAstore2 `shouldBe` cBstore2+    describe "Multiple items" $ do+      it "successfully syncs additions accross to a second client" $+        forAllValid $ \is ->+          evalDM $ do+            let cAstore1 = initialClientStore {clientStoreAddedItems = is}+              -- Client B is empty+            let cBstore1 = initialClientStore+              -- The server is empty+            let sstore1 = initialServerStore+              -- Client A makes sync request 1+            let req1 = makeSyncRequest cAstore1+              -- The server processes sync request 1+            (resp1, sstore2) <- processServerSync genD sstore1 req1+            let (rest, items) = mergeAddedItems is (syncResponseClientAdded resp1)+            lift $ do+              rest `shouldBe` M.empty+              sstore2 `shouldBe` (ServerStore {serverStoreItems = items})+              -- Client A merges the response+            let cAstore2 = mergeFunc cAstore1 resp1+            lift $ cAstore2 `shouldBe` (initialClientStore {clientStoreSyncedItems = items})+              -- Client B makes sync request 2+            let req2 = makeSyncRequest cBstore1+              -- The server processes sync request 2+            (resp2, sstore3) <- processServerSync genD sstore2 req2+            lift $ do+              resp2 `shouldBe` (emptySyncResponse {syncResponseServerAdded = items})+              sstore3 `shouldBe` sstore2+              -- Client B merges the response+            let cBstore2 = mergeFunc cBstore1 resp2+            lift $ cBstore2 `shouldBe` (initialClientStore {clientStoreSyncedItems = items})+              -- Client A and Client B now have the same store+            lift $ cAstore2 `shouldBe` cBstore2+      it "succesfully syncs deletions across to a second client" $+        forAllValid $ \items ->+          forAllValid $ \time1 ->+            evalDM $ do+              let syncedItems = M.map (\i -> Timed i time1) items+                  itemTimes = M.map (const time1) items+                  itemIds = M.keysSet items+              let cAstore1 = initialClientStore {clientStoreSyncedItems = syncedItems}+                -- Client A has synced items+                -- Client B had synced the same items, but has since deleted them.+              let cBstore1 = initialClientStore {clientStoreDeletedItems = itemTimes}+                -- The server still has the undeleted item+              let sstore1 = ServerStore {serverStoreItems = syncedItems}+                -- Client B makes sync request 1+              let req1 = makeSyncRequest cBstore1+                -- The server processes sync request 1+              (resp1, sstore2) <- processServerSync genD sstore1 req1+              lift $ do+                resp1 `shouldBe` emptySyncResponse {syncResponseClientDeleted = itemIds}+                sstore2 `shouldBe` initialServerStore+                -- Client B merges the response+              let cBstore2 = mergeFunc cBstore1 resp1+              lift $ cBstore2 `shouldBe` initialClientStore+                -- Client A makes sync request 2+              let req2 = makeSyncRequest cAstore1+                -- The server processes sync request 2+              (resp2, sstore3) <- processServerSync genD sstore2 req2+              lift $ do+                resp2 `shouldBe` emptySyncResponse {syncResponseServerDeleted = itemIds}+                sstore3 `shouldBe` sstore2+                -- Client A merges the response+              let cAstore2 = mergeFunc cAstore1 resp2+              lift $ cAstore2 `shouldBe` initialClientStore+                -- Client A and Client B now have the same store+              lift $ cAstore2 `shouldBe` cBstore2+      it "does not run into a conflict if two clients both try to sync a deletion" $+        forAllValid $ \items ->+          forAllValid $ \time1 ->+            evalDM $ do+              let cAstore1 =+                    initialClientStore {clientStoreDeletedItems = M.map (const time1) items}+                -- Both client a and client b delete their items.+              let cBstore1 =+                    initialClientStore {clientStoreDeletedItems = M.map (const time1) items}+                -- The server still has the undeleted items+              let sstore1 = ServerStore {serverStoreItems = M.map (\i -> Timed i time1) items}+                -- Client A makes sync request 1+              let req1 = makeSyncRequest cAstore1+                -- The server processes sync request 1+              (resp1, sstore2) <- processServerSync genD sstore1 req1+              lift $ do+                resp1 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = M.keysSet items})+                sstore2 `shouldBe` (ServerStore {serverStoreItems = M.empty}) -- TODO will probably need some sort of tombstoning.+                -- Client A merges the response+              let cAstore2 = mergeFunc cAstore1 resp1+              lift $ cAstore2 `shouldBe` initialClientStore+                -- Client B makes sync request 2+              let req2 = makeSyncRequest cBstore1+                -- The server processes sync request 2+              (resp2, sstore3) <- processServerSync genD sstore2 req2+              lift $ do+                resp2 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = M.keysSet items})+                sstore3 `shouldBe` sstore2+                -- Client B merges the response+              let cBstore2 = mergeFunc cBstore1 resp2+              lift $ do+                cBstore2 `shouldBe` initialClientStore+                  -- Client A and Client B now have the same store+                cAstore2 `shouldBe` cBstore2++newtype D m a =+  D+    { unD :: StateT StdGen m a+    }+  deriving (Generic, Functor, Applicative, Monad, MonadState StdGen, MonadTrans, MonadIO)++evalD :: D Identity a -> a+evalD = runIdentity . evalDM++-- runD :: D Identity a -> StdGen -> (a, StdGen)+-- runD = runState . unD+evalDM :: Functor m => D m a -> m a+evalDM d = fst <$> runDM d (mkStdGen 42)++runDM :: D m a -> StdGen -> m (a, StdGen)+runDM = runStateT . unD++genD :: Monad m => D m UUID+genD = do+  r <- get+  let (u, r') = random r+  put r'+  pure u
test/Data/Mergeful/ItemSpec.hs view
@@ -82,7 +82,7 @@             store2 `shouldBe` ServerEmpty             resp `shouldBe` ItemSyncResponseClientDeleted     describe "Server changes" $ do-      it "tells the client that there is a new item at the server side" $ do+      it "tells the client that there is a new item at the server side" $         forAllValid $ \i ->           forAllValid $ \st -> do             let store1 = ServerFull (Timed i st)@@ -90,7 +90,7 @@             let (resp, store2) = processServerItemSync @Int store1 req             store2 `shouldBe` store1             resp `shouldBe` ItemSyncResponseServerAdded (Timed i st)-      it "tells the client that there is a modified item at the server side" $ do+      it "tells the client that there is a modified item at the server side" $         forAllValid $ \i ->           forAllSubsequent $ \(st, st') -> do             let store1 = ServerFull (Timed i st')@@ -98,7 +98,7 @@             let (resp, store2) = processServerItemSync @Int store1 req             store2 `shouldBe` store1             resp `shouldBe` ItemSyncResponseServerChanged (Timed i st')-      it "tells the client that there is a deleted item at the server side" $ do+      it "tells the client that there is a deleted item at the server side" $         forAllValid $ \st -> do           let store1 = ServerEmpty               req = ItemSyncRequestKnown st
test/Data/Mergeful/ValueSpec.hs view
@@ -48,36 +48,36 @@           let (resp, store2) = processServerValueSync @Int store1 req           store2 `shouldBe` store1           resp `shouldBe` ValueSyncResponseInSync-    describe "Client changes" $ do+    describe "Client changes" $       it "changes the item that the client tells the server to change" $-        forAllValid $ \i ->-          forAllValid $ \j ->-            forAllValid $ \st -> do-              let store1 = ServerValue (Timed i st)-                  req = ValueSyncRequestKnownButChanged (Timed j st)-              let (resp, store2) = processServerValueSync @Int store1 req-              let time = incrementServerTime st-              store2 `shouldBe` ServerValue (Timed j time)-              resp `shouldBe` ValueSyncResponseClientChanged time-    describe "Server changes" $ do-      it "tells the client that there is a modified item at the server side" $ do-        forAllValid $ \i ->+      forAllValid $ \i ->+        forAllValid $ \j ->+          forAllValid $ \st -> do+            let store1 = ServerValue (Timed i st)+                req = ValueSyncRequestKnownButChanged (Timed j st)+            let (resp, store2) = processServerValueSync @Int store1 req+            let time = incrementServerTime st+            store2 `shouldBe` ServerValue (Timed j time)+            resp `shouldBe` ValueSyncResponseClientChanged time+    describe "Server changes" $+      it "tells the client that there is a modified item at the server side" $+      forAllValid $ \i ->+        forAllSubsequent $ \(st, st') -> do+          let store1 = ServerValue (Timed i st')+              req = ValueSyncRequestKnown st+          let (resp, store2) = processServerValueSync @Int store1 req+          store2 `shouldBe` store1+          resp `shouldBe` ValueSyncResponseServerChanged (Timed i st')+    describe "Conflicts" $+      it "notices a conflict if the client and server are trying to sync different items" $+      forAllValid $ \i ->+        forAllValid $ \j ->           forAllSubsequent $ \(st, st') -> do             let store1 = ServerValue (Timed i st')-                req = ValueSyncRequestKnown st+                req = ValueSyncRequestKnownButChanged (Timed j st)             let (resp, store2) = processServerValueSync @Int store1 req             store2 `shouldBe` store1-            resp `shouldBe` ValueSyncResponseServerChanged (Timed i st')-    describe "Conflicts" $ do-      it "notices a conflict if the client and server are trying to sync different items" $-        forAllValid $ \i ->-          forAllValid $ \j ->-            forAllSubsequent $ \(st, st') -> do-              let store1 = ServerValue (Timed i st')-                  req = ValueSyncRequestKnownButChanged (Timed j st)-              let (resp, store2) = processServerValueSync @Int store1 req-              store2 `shouldBe` store1-              resp `shouldBe` ValueSyncResponseConflict (Timed i st')+            resp `shouldBe` ValueSyncResponseConflict (Timed i st')   describe "syncing" $ do     it "succesfully syncs a modification across to a second client" $       forAllValid $ \time1 ->
− test/Data/MergefulSpec.hs
@@ -1,565 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeApplications #-}--module Data.MergefulSpec-  ( spec-  ) where--import Data.Functor.Identity-import Data.List-import Data.Map (Map)-import qualified Data.Map as M-import qualified Data.Set as S-import Data.UUID-import GHC.Generics (Generic)-import System.Random-import Text.Show.Pretty--import Control.Monad.State--import Test.Hspec-import Test.Validity-import Test.Validity.Aeson--import Data.GenValidity.UUID ()--import Data.Mergeful-import Data.Mergeful.Timed--import Data.GenValidity.Mergeful ()-import Data.GenValidity.Mergeful.Item ()--{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}--spec :: Spec-spec = do-  genValidSpec @ClientId-  jsonSpecOnValid @ClientId-  genValidSpec @(ClientStore Int Int)-  jsonSpecOnValid @(ClientStore Int Int)-  genValidSpec @(ServerStore Int Int)-  jsonSpecOnValid @(ServerStore Int Int)-  genValidSpec @(SyncRequest Int Int)-  jsonSpecOnValid @(SyncRequest Int Int)-  genValidSpec @(SyncResponse Int Int)-  jsonSpecOnValid @(SyncResponse Int Int)-  describe "initialClientStore" $ it "is valid" $ shouldBeValid $ initialClientStore @Int @Int-  describe "addItemToClientStore" $-    it "produces valid stores" $ producesValidsOnValids2 (addItemToClientStore @Int @Int)-  describe "markItemDeletedInClientStore" $-    it "produces valid stores" $ producesValidsOnValids2 (markItemDeletedInClientStore @Int @Int)-  describe "changeItemInClientStore" $-    it "produces valid stores" $ producesValidsOnValids3 (changeItemInClientStore @Int @Int)-  describe "deleteItemFromClientStore" $-    it "produces valid stores" $ producesValidsOnValids2 (deleteItemFromClientStore @Int @Int)-  describe "initialServerStore" $ it "is valid" $ shouldBeValid $ initialServerStore @Int @Int-  describe "initialSyncRequest" $ it "is valid" $ shouldBeValid $ initialSyncRequest @Int @Int-  describe "emptySyncResponse" $ it "is valid" $ shouldBeValid $ emptySyncResponse @Int @Int-  describe "makeSyncRequest" $-    it "produces valid requests" $ producesValidsOnValids (makeSyncRequest @Int @Int)-  describe "mergeAddedItems" $-    it "produces valid results" $ producesValidsOnValids2 (mergeAddedItems @Int @Int)-  describe "mergeSyncedButChangedItems" $-    it "produces valid results" $ producesValidsOnValids2 (mergeSyncedButChangedItems @Int @Int)-  describe "mergeAddedItems" $-    it "produces valid results" $ producesValidsOnValids2 (mergeAddedItems @Int @Int)-  describe "mergeSyncedButChangedItems" $-    it "produces valid results" $ producesValidsOnValids2 (mergeSyncedButChangedItems @Int @Int)-  describe "mergeDeletedItems" $-    it "produces valid results" $ producesValidsOnValids2 (mergeDeletedItems @Int @Int)-  describe "mergeSyncResponseIgnoreProblems" $-    it "produces valid requests" $-    forAllValid $ \store ->-      forAllValid $ \response ->-        let res = mergeSyncResponseIgnoreProblems @Int @Int store response-         in case prettyValidate res of-              Right _ -> pure ()-              Left err ->-                expectationFailure $-                unlines-                  [ "Store:"-                  , ppShow store-                  , "Response:"-                  , ppShow response-                  , "Invalid result:"-                  , ppShow res-                  , "error:"-                  , err-                  ]-  describe "mergeSyncResponseFromServer" $ do-    it "only differs from mergeSyncResponseIgnoreProblems on conflicts" $-      forAllValid $ \cstore ->-        forAllValid $ \sresp@SyncResponse {..} -> do-          let cstoreA = mergeSyncResponseFromServer (cstore :: ClientStore (UUID) Int) sresp-              cstoreB = mergeSyncResponseIgnoreProblems cstore sresp-          if cstoreA == cstoreB-            then pure ()-            else unless-                   (or-                      [ not (M.null syncResponseConflicts)-                      , not (M.null syncResponseConflictsClientDeleted)-                      , not (S.null syncResponseConflictsServerDeleted)-                      ]) $-                 expectationFailure $-                 unlines-                   [ "There was a difference between mergeSyncResponseFromServer and mergeSyncResponseIgnoreProblems that was somehow unrelated to the conflicts:"-                   , "syncResponseConflicts:"-                   , ppShow syncResponseConflicts-                   , "syncResponseConflictsClientDeleted:"-                   , ppShow syncResponseConflictsClientDeleted-                   , "syncResponseConflictsServerDeleted:"-                   , ppShow syncResponseConflictsServerDeleted-                   , "client store after mergeSyncResponseFromServer:"-                   , ppShow cstoreA-                   , "client store after mergeSyncResponseIgnoreProblems:"-                   , ppShow cstoreB-                   ]-  describe "processServerSync with mergeSyncResponseIgnoreProblems" $ do-    it "produces valid tuples of a response and a store" $-      producesValidsOnValids2-        (\store request -> evalD $ processServerSync genD (store :: ServerStore (UUID) Int) request)-    describe "Single client" $ do-      describe "Multi-item" $ do-        it "succesfully downloads everything from the server for an empty client" $-          forAllValid $ \sstore1 ->-            evalDM $ do-              let cstore1 = initialClientStore :: ClientStore (UUID) Int-              let req = makeSyncRequest cstore1-              (resp, sstore2) <- processServerSync genD sstore1 req-              let cstore2 = mergeSyncResponseIgnoreProblems cstore1 resp-              lift $ do-                sstore2 `shouldBe` sstore1-                clientStoreSyncedItems cstore2 `shouldBe` serverStoreItems sstore2-        it "succesfully uploads everything to the server for an empty server" $-          forAllValid $ \items ->-            evalDM $ do-              let cstore1 = initialClientStore {clientStoreAddedItems = items}-              let sstore1 = initialServerStore :: ServerStore (UUID) Int-              let req = makeSyncRequest cstore1-              (resp, sstore2) <- processServerSync genD sstore1 req-              let cstore2 = mergeSyncResponseIgnoreProblems cstore1 resp-              lift $ do-                sort (M.elems (M.map timedValue (clientStoreSyncedItems cstore2))) `shouldBe`-                  sort (M.elems items)-                clientStoreSyncedItems cstore2 `shouldBe` serverStoreItems sstore2-        it "is idempotent with one client" $-          forAllValid $ \cstore1 ->-            forAllValid $ \sstore1 ->-              evalDM $ do-                let req1 = makeSyncRequest (cstore1 :: ClientStore (UUID) Int)-                (resp1, sstore2) <- processServerSync genD sstore1 req1-                let cstore2 = mergeSyncResponseIgnoreProblems cstore1 resp1-                    req2 = makeSyncRequest cstore2-                (resp2, sstore3) <- processServerSync genD sstore2 req2-                let cstore3 = mergeSyncResponseIgnoreProblems cstore2 resp2-                lift $ do-                  cstore2 `shouldBe` cstore3-                  sstore2 `shouldBe` sstore3-    describe "Multiple clients" $ do-      describe "Single-item" $ do-        it "successfully syncs an addition accross to a second client" $-          forAllValid $ \i ->-            evalDM $ do-              let cAstore1 = initialClientStore {clientStoreAddedItems = M.singleton (ClientId 0) i}-              -- Client B is empty-              let cBstore1 = initialClientStore :: ClientStore (UUID) Int-              -- The server is empty-              let sstore1 = initialServerStore-              -- Client A makes sync request 1-              let req1 = makeSyncRequest cAstore1-              -- The server processes sync request 1-              (resp1, sstore2) <- processServerSync genD sstore1 req1-              let addedItems = syncResponseClientAdded resp1-              case M.toList addedItems of-                [(ClientId 0, (uuid, st))] -> do-                  let time = initialServerTime-                  lift $ st `shouldBe` time-                  let items = M.singleton uuid (Timed i st)-                  lift $ sstore2 `shouldBe` (ServerStore {serverStoreItems = items})-                  -- Client A merges the response-                  let cAstore2 = mergeSyncResponseIgnoreProblems cAstore1 resp1-                  lift $ cAstore2 `shouldBe` (initialClientStore {clientStoreSyncedItems = items})-                  -- Client B makes sync request 2-                  let req2 = makeSyncRequest cBstore1-                  -- The server processes sync request 2-                  (resp2, sstore3) <- processServerSync genD sstore2 req2-                  lift $ do-                    resp2 `shouldBe` (emptySyncResponse {syncResponseServerAdded = items})-                    sstore3 `shouldBe` sstore2-                  -- Client B merges the response-                  let cBstore2 = mergeSyncResponseIgnoreProblems cBstore1 resp2-                  lift $ cBstore2 `shouldBe` (initialClientStore {clientStoreSyncedItems = items})-                  -- Client A and Client B now have the same store-                  lift $ cAstore2 `shouldBe` cBstore2-                _ -> lift $ expectationFailure "Should have found exactly one added item."-        it "successfully syncs a modification accross to a second client" $-          forAllValid $ \uuid ->-            forAllValid $ \i ->-              forAllValid $ \j ->-                forAllValid $ \time1 ->-                  evalDM $ do-                    let cAstore1 =-                          initialClientStore-                            { clientStoreSyncedItems =-                                M.singleton (uuid :: UUID) (Timed (i :: Int) time1)-                            }-                    -- Client B had synced that same item, but has since modified it-                    let cBstore1 =-                          initialClientStore-                            {clientStoreSyncedButChangedItems = M.singleton uuid (Timed j time1)}-                    -- The server is has the item that both clients had before-                    let sstore1 = ServerStore {serverStoreItems = M.singleton uuid (Timed i time1)}-                    -- Client B makes sync request 1-                    let req1 = makeSyncRequest cBstore1-                    -- The server processes sync request 1-                    (resp1, sstore2) <- processServerSync genD sstore1 req1-                    let time2 = incrementServerTime time1-                    lift $ do-                      resp1 `shouldBe`-                        emptySyncResponse {syncResponseClientChanged = M.singleton uuid time2}-                      sstore2 `shouldBe`-                        ServerStore {serverStoreItems = M.singleton uuid (Timed j time2)}-                    -- Client B merges the response-                    let cBstore2 = mergeSyncResponseIgnoreProblems cBstore1 resp1-                    lift $-                      cBstore2 `shouldBe`-                      initialClientStore {clientStoreSyncedItems = M.singleton uuid (Timed j time2)}-                    -- Client A makes sync request 2-                    let req2 = makeSyncRequest cAstore1-                    -- The server processes sync request 2-                    (resp2, sstore3) <- processServerSync genD sstore2 req2-                    lift $ do-                      resp2 `shouldBe`-                        emptySyncResponse-                          {syncResponseServerChanged = M.singleton uuid (Timed j time2)}-                      sstore3 `shouldBe` sstore2-                    -- Client A merges the response-                    let cAstore2 = mergeSyncResponseIgnoreProblems cAstore1 resp2-                    lift $-                      cAstore2 `shouldBe`-                      initialClientStore {clientStoreSyncedItems = M.singleton uuid (Timed j time2)}-                    -- Client A and Client B now have the same store-                    lift $ cAstore2 `shouldBe` cBstore2-        it "succesfully syncs a deletion across to a second client" $-          forAllValid $ \uuid ->-            forAllValid $ \time1 ->-              forAllValid $ \i ->-                evalDM $ do-                  let cAstore1 =-                        initialClientStore-                          { clientStoreSyncedItems =-                              M.singleton (uuid :: UUID) (Timed (i :: Int) time1)-                          }-                  -- Client A has a synced item.-                  -- Client B had synced that same item, but has since deleted it.-                  let cBstore1 =-                        initialClientStore {clientStoreDeletedItems = M.singleton uuid time1}-                  -- The server still has the undeleted item-                  let sstore1 = ServerStore {serverStoreItems = M.singleton uuid (Timed i time1)}-                  -- Client B makes sync request 1-                  let req1 = makeSyncRequest cBstore1-                  -- The server processes sync request 1-                  (resp1, sstore2) <- processServerSync genD sstore1 req1-                  lift $ do-                    resp1 `shouldBe`-                      emptySyncResponse {syncResponseClientDeleted = S.singleton uuid}-                    sstore2 `shouldBe` initialServerStore-                  -- Client B merges the response-                  let cBstore2 = mergeSyncResponseIgnoreProblems cBstore1 resp1-                  lift $ cBstore2 `shouldBe` initialClientStore-                  -- Client A makes sync request 2-                  let req2 = makeSyncRequest cAstore1-                  -- The server processes sync request 2-                  (resp2, sstore3) <- processServerSync genD sstore2 req2-                  lift $ do-                    resp2 `shouldBe`-                      emptySyncResponse {syncResponseServerDeleted = S.singleton uuid}-                    sstore3 `shouldBe` sstore2-                  -- Client A merges the response-                  let cAstore2 = mergeSyncResponseIgnoreProblems cAstore1 resp2-                  lift $ cAstore2 `shouldBe` initialClientStore-                  -- Client A and Client B now have the same store-                  lift $ cAstore2 `shouldBe` cBstore2-        it "does not run into a conflict if two clients both try to sync a deletion" $-          forAllValid $ \uuid ->-            forAllValid $ \time1 ->-              forAllValid $ \i ->-                evalDM $ do-                  let cAstore1 =-                        initialClientStore-                          {clientStoreDeletedItems = M.singleton (uuid :: UUID) time1}-                  -- Both client a and client b delete an item.-                  let cBstore1 =-                        initialClientStore {clientStoreDeletedItems = M.singleton uuid time1}-                  -- The server still has the undeleted item-                  let sstore1 =-                        ServerStore {serverStoreItems = M.singleton uuid (Timed (i :: Int) time1)}-                  -- Client A makes sync request 1-                  let req1 = makeSyncRequest cAstore1-                  -- The server processes sync request 1-                  (resp1, sstore2) <- processServerSync genD sstore1 req1-                  lift $ do-                    resp1 `shouldBe`-                      (emptySyncResponse {syncResponseClientDeleted = S.singleton uuid})-                    sstore2 `shouldBe` (ServerStore {serverStoreItems = M.empty})-                  -- Client A merges the response-                  let cAstore2 = mergeSyncResponseIgnoreProblems cAstore1 resp1-                  lift $ cAstore2 `shouldBe` initialClientStore-                  -- Client B makes sync request 2-                  let req2 = makeSyncRequest cBstore1-                  -- The server processes sync request 2-                  (resp2, sstore3) <- processServerSync genD sstore2 req2-                  lift $ do-                    resp2 `shouldBe`-                      (emptySyncResponse {syncResponseClientDeleted = S.singleton uuid})-                    sstore3 `shouldBe` sstore2-                  -- Client B merges the response-                  let cBstore2 = mergeSyncResponseIgnoreProblems cBstore1 resp2-                  lift $ do-                    cBstore2 `shouldBe` initialClientStore-                    -- Client A and Client B now have the same store-                    cAstore2 `shouldBe` cBstore2-        it "does not lose data after a conflict occurs" $-          forAllValid $ \uuid ->-            forAllValid $ \time1 ->-              forAllValid $ \i1 ->-                forAllValid $ \i2 ->-                  forAllValid $ \i3 ->-                    evalDM $ do-                      let sstore1 =-                            ServerStore-                              { serverStoreItems =-                                  M.singleton (uuid :: UUID) (Timed (i1 :: Int) time1)-                              }-                      -- The server has an item-                      -- The first client has synced it, and modified it.-                      let cAstore1 =-                            initialClientStore-                              {clientStoreSyncedButChangedItems = M.singleton uuid (Timed i2 time1)}-                      -- The second client has synced it too, and modified it too.-                      let cBstore1 =-                            initialClientStore-                              {clientStoreSyncedButChangedItems = M.singleton uuid (Timed i3 time1)}-                      -- Client A makes sync request 1-                      let req1 = makeSyncRequest cAstore1-                      -- The server processes sync request 1-                      (resp1, sstore2) <- processServerSync genD sstore1 req1-                      let time2 = incrementServerTime time1-                      -- The server updates the item accordingly-                      lift $ do-                        resp1 `shouldBe`-                          (emptySyncResponse {syncResponseClientChanged = M.singleton uuid time2})-                        sstore2 `shouldBe`-                          (ServerStore {serverStoreItems = M.singleton uuid (Timed i2 time2)})-                      -- Client A merges the response-                      let cAstore2 = mergeSyncResponseIgnoreProblems cAstore1 resp1-                      lift $-                        cAstore2 `shouldBe`-                        (initialClientStore-                           {clientStoreSyncedItems = M.singleton uuid (Timed i2 time2)})-                      -- Client B makes sync request 2-                      let req2 = makeSyncRequest cBstore1-                      -- The server processes sync request 2-                      (resp2, sstore3) <- processServerSync genD sstore2 req2-                      -- The server reports a conflict and does not change its store-                      lift $ do-                        resp2 `shouldBe`-                          (emptySyncResponse-                             {syncResponseConflicts = M.singleton uuid (Timed i2 time2)})-                        sstore3 `shouldBe` sstore2-                      -- Client B merges the response-                      let cBstore2 = mergeSyncResponseIgnoreProblems cBstore1 resp2-                      -- Client does not update, but keeps its conflict-                      lift $ do-                        cBstore2 `shouldBe`-                          (initialClientStore-                             {clientStoreSyncedButChangedItems = M.singleton uuid (Timed i3 time1)})-                      -- Client A and Client B now *do not* have the same store-      describe "Multiple items" $ do-        it "successfully syncs additions accross to a second client" $-          forAllValid $ \is ->-            evalDM $ do-              let cAstore1 = initialClientStore {clientStoreAddedItems = is}-              -- Client B is empty-              let cBstore1 = initialClientStore :: ClientStore (UUID) Int-              -- The server is empty-              let sstore1 = initialServerStore-              -- Client A makes sync request 1-              let req1 = makeSyncRequest cAstore1-              -- The server processes sync request 1-              (resp1, sstore2) <- processServerSync genD sstore1 req1-              let (rest, items) = mergeAddedItems is (syncResponseClientAdded resp1)-              lift $ do-                rest `shouldBe` M.empty-                sstore2 `shouldBe` (ServerStore {serverStoreItems = items})-              -- Client A merges the response-              let cAstore2 = mergeSyncResponseIgnoreProblems cAstore1 resp1-              lift $ cAstore2 `shouldBe` (initialClientStore {clientStoreSyncedItems = items})-              -- Client B makes sync request 2-              let req2 = makeSyncRequest cBstore1-              -- The server processes sync request 2-              (resp2, sstore3) <- processServerSync genD sstore2 req2-              lift $ do-                resp2 `shouldBe` (emptySyncResponse {syncResponseServerAdded = items})-                sstore3 `shouldBe` sstore2-              -- Client B merges the response-              let cBstore2 = mergeSyncResponseIgnoreProblems cBstore1 resp2-              lift $ cBstore2 `shouldBe` (initialClientStore {clientStoreSyncedItems = items})-              -- Client A and Client B now have the same store-              lift $ cAstore2 `shouldBe` cBstore2-        it "succesfully syncs deletions across to a second client" $-          forAllValid $ \items ->-            forAllValid $ \time1 ->-              evalDM $ do-                let syncedItems = M.map (\i -> Timed i time1) (items :: Map (UUID) Int)-                    itemTimes = M.map (const time1) items-                    itemIds = M.keysSet items-                let cAstore1 = initialClientStore {clientStoreSyncedItems = syncedItems}-                -- Client A has synced items-                -- Client B had synced the same items, but has since deleted them.-                let cBstore1 = initialClientStore {clientStoreDeletedItems = itemTimes}-                -- The server still has the undeleted item-                let sstore1 = ServerStore {serverStoreItems = syncedItems}-                -- Client B makes sync request 1-                let req1 = makeSyncRequest cBstore1-                -- The server processes sync request 1-                (resp1, sstore2) <- processServerSync genD sstore1 req1-                lift $ do-                  resp1 `shouldBe` emptySyncResponse {syncResponseClientDeleted = itemIds}-                  sstore2 `shouldBe` initialServerStore-                -- Client B merges the response-                let cBstore2 = mergeSyncResponseIgnoreProblems cBstore1 resp1-                lift $ cBstore2 `shouldBe` initialClientStore-                -- Client A makes sync request 2-                let req2 = makeSyncRequest cAstore1-                -- The server processes sync request 2-                (resp2, sstore3) <- processServerSync genD sstore2 req2-                lift $ do-                  resp2 `shouldBe` emptySyncResponse {syncResponseServerDeleted = itemIds}-                  sstore3 `shouldBe` sstore2-                -- Client A merges the response-                let cAstore2 = mergeSyncResponseIgnoreProblems cAstore1 resp2-                lift $ cAstore2 `shouldBe` initialClientStore-                -- Client A and Client B now have the same store-                lift $ cAstore2 `shouldBe` cBstore2-        it "does not run into a conflict if two clients both try to sync a deletion" $-          forAllValid $ \items ->-            forAllValid $ \time1 ->-              evalDM $ do-                let cAstore1 =-                      initialClientStore-                        {clientStoreDeletedItems = M.map (const time1) (items :: Map (UUID) Int)}-                -- Both client a and client b delete their items.-                let cBstore1 =-                      initialClientStore {clientStoreDeletedItems = M.map (const time1) items}-                -- The server still has the undeleted items-                let sstore1 = ServerStore {serverStoreItems = M.map (\i -> Timed i time1) items}-                -- Client A makes sync request 1-                let req1 = makeSyncRequest cAstore1-                -- The server processes sync request 1-                (resp1, sstore2) <- processServerSync genD sstore1 req1-                lift $ do-                  resp1 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = M.keysSet items})-                  sstore2 `shouldBe` (ServerStore {serverStoreItems = M.empty}) -- TODO will probably need some sort of tombstoning.-                -- Client A merges the response-                let cAstore2 = mergeSyncResponseIgnoreProblems cAstore1 resp1-                lift $ cAstore2 `shouldBe` initialClientStore-                -- Client B makes sync request 2-                let req2 = makeSyncRequest cBstore1-                -- The server processes sync request 2-                (resp2, sstore3) <- processServerSync genD sstore2 req2-                lift $ do-                  resp2 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = M.keysSet items})-                  sstore3 `shouldBe` sstore2-                -- Client B merges the response-                let cBstore2 = mergeSyncResponseIgnoreProblems cBstore1 resp2-                lift $ do-                  cBstore2 `shouldBe` initialClientStore-                  -- Client A and Client B now have the same store-                  cAstore2 `shouldBe` cBstore2-  describe "processServerSync with mergeSyncResponseFromServer" $ do-    describe "Multiple clients" $ do-      describe "Single-item" $ do-        it "does not diverge after a conflict occurs" $-          forAllValid $ \uuid ->-            forAllValid $ \time1 ->-              forAllValid $ \i1 ->-                forAllValid $ \i2 ->-                  forAllValid $ \i3 ->-                    evalDM $ do-                      let sstore1 =-                            ServerStore-                              { serverStoreItems =-                                  M.singleton (uuid :: UUID) (Timed (i1 :: Int) time1)-                              }-                      -- The server has an item-                      -- The first client has synced it, and modified it.-                      let cAstore1 =-                            initialClientStore-                              {clientStoreSyncedButChangedItems = M.singleton uuid (Timed i2 time1)}-                      -- The second client has synced it too, and modified it too.-                      let cBstore1 =-                            initialClientStore-                              {clientStoreSyncedButChangedItems = M.singleton uuid (Timed i3 time1)}-                      -- Client A makes sync request 1-                      let req1 = makeSyncRequest cAstore1-                      -- The server processes sync request 1-                      (resp1, sstore2) <- processServerSync genD sstore1 req1-                      let time2 = incrementServerTime time1-                      -- The server updates the item accordingly-                      lift $ do-                        resp1 `shouldBe`-                          (emptySyncResponse {syncResponseClientChanged = M.singleton uuid time2})-                        sstore2 `shouldBe`-                          (ServerStore {serverStoreItems = M.singleton uuid (Timed i2 time2)})-                      -- Client A merges the response-                      let cAstore2 = mergeSyncResponseFromServer cAstore1 resp1-                      lift $-                        cAstore2 `shouldBe`-                        (initialClientStore-                           {clientStoreSyncedItems = M.singleton uuid (Timed i2 time2)})-                      -- Client B makes sync request 2-                      let req2 = makeSyncRequest cBstore1-                      -- The server processes sync request 2-                      (resp2, sstore3) <- processServerSync genD sstore2 req2-                      -- The server reports a conflict and does not change its store-                      lift $ do-                        resp2 `shouldBe`-                          (emptySyncResponse-                             {syncResponseConflicts = M.singleton uuid (Timed i2 time2)})-                        sstore3 `shouldBe` sstore2-                      -- Client B merges the response-                      let cBstore2 = mergeSyncResponseFromServer cBstore1 resp2-                      -- Client does not update, but keeps its conflict-                      lift $ do-                        cBstore2 `shouldBe`-                          (initialClientStore-                             {clientStoreSyncedItems = M.singleton uuid (Timed i2 time2)})-                        -- Client A and Client B now have the same store-                        cBstore2 `shouldBe` cAstore2--newtype D m a =-  D-    { unD :: StateT StdGen m a-    }-  deriving (Generic, Functor, Applicative, Monad, MonadState StdGen, MonadTrans, MonadIO)--evalD :: D Identity a -> a-evalD = runIdentity . evalDM---- runD :: D Identity a -> StdGen -> (a, StdGen)--- runD = runState . unD-evalDM :: Functor m => D m a -> m a-evalDM d = fst <$> runDM d (mkStdGen 42)--runDM :: D m a -> StdGen -> m (a, StdGen)-runDM = runStateT . unD--genD :: Monad m => D m UUID-genD = do-  r <- get-  let (u, r') = random r-  put r'-  pure u