packages feed

genvalidity-mergeless-0.3.0.0: test/Data/Mergeless/ItemSpec.hs

{-# LANGUAGE TypeApplications #-}

module Data.Mergeless.ItemSpec
  ( spec,
  )
where

import Data.GenValidity.Mergeless.Item ()
import Data.GenValidity.UUID ()
import Data.Mergeless.Item
import Test.Hspec
import Test.Validity
import Test.Validity.Aeson

{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}

spec :: Spec
spec = do
  eqSpec @(ClientItem Int)
  genValidSpec @(ClientItem Int)
  jsonSpec @(ClientItem Int)
  eqSpec @(ItemSyncRequest Int)
  genValidSpec @(ItemSyncRequest Int)
  jsonSpec @(ItemSyncRequest Int)
  eqSpec @(ItemSyncResponse Int)
  genValidSpec @(ItemSyncResponse Int)
  jsonSpec @(ItemSyncResponse Int)
  eqSpec @(ServerItem Int)
  genValidSpec @(ServerItem Int)
  jsonSpec @(ServerItem Int)
  describe "makeItemSyncrequest" $
    it "produces valid requests" $
      producesValid (makeItemSyncRequest @Int)
  describe "mergeItemSyncResponse" $
    it "produces valid client items" $
      producesValid2 (mergeItemSyncResponse @Int)
  describe "processItemSync" $
    it "produces valid tuples" $
      producesValid2 (processServerItemSync @Int)
  describe "processItemSync" $ do
    describe "Interaction" $ do
      it "makes no changes if the sync request reflects the state of the empty server" $ do
        let store1 = ServerItemEmpty
            req = ItemSyncRequestPoll
        let (resp, store2) = processServerItemSync @Int store1 req
        store2 `shouldBe` store1
        resp `shouldBe` ItemSyncResponseInSyncEmpty
      it "makes no changes if the sync request reflects the state of the full server" $
        forAllValid $
          \s -> do
            let store1 = ServerItemFull s
                req = ItemSyncRequestKnown
            let (resp, store2) = processServerItemSync @Int store1 req
            store2 `shouldBe` store1
            resp `shouldBe` ItemSyncResponseInSyncFull
    describe "Client changes" $ do
      it "adds the item that the client tells the server to add" $
        forAllValid $
          \a -> do
            let store1 = ServerItemEmpty
                req = ItemSyncRequestNew a
            let (resp, store2) = processServerItemSync @Int store1 req
            store2 `shouldBe` ServerItemFull a
            resp `shouldBe` ItemSyncResponseClientAdded
      it "deletes the item that the client tells the server to delete" $
        forAllValid $
          \s -> do
            let store1 = ServerItemFull s
                req = ItemSyncRequestDeleted
            let (resp, store2) = processServerItemSync @Int store1 req
            store2 `shouldBe` ServerItemEmpty
            resp `shouldBe` ItemSyncResponseClientDeleted
    describe "Server changes" $ do
      it "tells the client that there is a new item at the server side" $
        forAllValid $
          \s -> do
            let store1 = ServerItemFull s
                req = ItemSyncRequestPoll
            let (resp, store2) = processServerItemSync @Int store1 req
            store2 `shouldBe` store1
            resp `shouldBe` ItemSyncResponseServerAdded s
      it "tells the client that there is a deleted item at the server side" $ do
        let store1 = ServerItemEmpty
            req = ItemSyncRequestKnown
        let (resp, store2) = processServerItemSync @Int store1 req
        store2 `shouldBe` store1
        resp `shouldBe` ItemSyncResponseServerDeleted
    describe "High level properties" $ do
      it "it always possible to add an item from scratch" $
        forAllValid $
          \a -> do
            let cstore1 = ClientAdded (a :: Int)
            let sstore1 = ServerItemEmpty
            let req1 = makeItemSyncRequest cstore1
                (resp1, sstore2) = processServerItemSync sstore1 req1
                cstore2 = mergeItemSyncResponse cstore1 resp1
            resp1 `shouldBe` ItemSyncResponseClientAdded
            sstore2 `shouldBe` ServerItemFull a
            cstore2 `shouldBe` ClientSynced a
      it "is idempotent with one client" $
        forAllValid $
          \si1 ->
            forAllValid $ \ci1 -> do
              let req1 = makeItemSyncRequest @Int ci1
                  (resp1, si2) = processServerItemSync si1 req1
                  ci2 = mergeItemSyncResponse ci1 resp1
                  req2 = makeItemSyncRequest ci2
                  (resp2, si3) = processServerItemSync si2 req2
                  ci3 = mergeItemSyncResponse ci2 resp2
              ci3 `shouldBe` ci2
              si3 `shouldBe` si2