{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module SubscriptionTreeSpec (spec) where import Data.List (sortOn) import Test.Hspec (Spec, describe, it, shouldBe) import Test.Hspec.QuickCheck (prop) import Test.QuickCheck.Instances () import qualified Data.Aeson as AE import qualified Data.HashMap.Strict as HM import Subscription (SubscriptionTree (..), broadcast', empty, subscribe, unsubscribe) spec :: Spec spec = do describe "SubscriptionTree" $ do it "adds a client listening to the root when calling subscribe with []" $ do let path = [] conn_id = 1 :: Int conn = "dummy connection" :: String after = SubscriptionTree (HM.fromList [(conn_id, conn)]) HM.empty subscribe path conn_id conn empty `shouldBe` after it "adds a client listening to \"some\" when calling subscribe with [\"some\"]" $ do let path = ["some"] conn_id = 1 :: Int conn = "dummy connection" :: [Char] after = SubscriptionTree HM.empty (HM.fromList [("some", SubscriptionTree (HM.fromList [(conn_id,conn)]) HM.empty)]) subscribe path conn_id conn empty `shouldBe` after it "adds two clients: ones listening to the root and one to \"some\"" $ do let root = [] path = ["some"] conn_id = 1 :: Int conn_id2 = 2 :: Int conn = "dummy connection" :: [Char] conn2 = "dummy connection2" :: [Char] after = SubscriptionTree (HM.fromList [(conn_id, conn)]) (HM.fromList [("some", SubscriptionTree (HM.fromList [(conn_id2,conn2)]) HM.empty)]) subscribe root conn_id conn (subscribe path conn_id2 conn2 empty) `shouldBe` after prop "adding clients is commutative" $ \ lpath rpath (lid :: Int) -> let rid = lid + 1 -- The ids need to be distinct. lconn = "dummy connection left" :: [Char] rconn = "dummy connection right" :: [Char] ladd = subscribe lpath lid lconn radd = subscribe rpath rid rconn in (ladd . radd) empty `shouldBe` (radd . ladd) empty prop "adding and removing a client is identity" $ \ path (cid :: Int) -> let conn = "dummy connection" :: String subUnsub = unsubscribe path cid . subscribe path cid conn in subUnsub empty `shouldBe` empty it "removing a non-existing client does nothing" $ do let path = [] conn_id = 1 :: Int unsubscribe path conn_id (empty :: SubscriptionTree Int String)`shouldBe` empty do let conn1 , conn2 , conn3 , conn4 :: Int conn1 = 1; conn2 = 2; conn3 = 3; conn4 = 4 root = SubscriptionTree (HM.fromList [(conn1, conn1)]) (HM.fromList [ ("foo", root_foo) , ("baz", root_baz) ]) root_foo = SubscriptionTree (HM.fromList [(conn2, conn2)]) (HM.fromList [("bar", root_foo_bar)]) root_foo_bar = SubscriptionTree (HM.fromList [(conn3, conn3)]) HM.empty root_baz = SubscriptionTree (HM.fromList [(conn4, conn4)]) HM.empty value = AE.object ["foo" AE..= value_foo, "baz" AE..= value_baz] value_foo = AE.object ["bar" AE..= value_foo_bar] value_foo_bar = AE.Null value_baz = AE.object [] broadcast'' path = sortOn fst $ broadcast' path value root it "notifies everyone on root updates" $ do broadcast'' [] `shouldBe` [ (conn1, value) , (conn2, value_foo) , (conn3, value_foo_bar) , (conn4, value_baz) ] it "notifies parents and children about updates" $ do broadcast'' ["foo"] `shouldBe` [ (conn1, value) , (conn2, value_foo) , (conn3, value_foo_bar) ] it "notifies parents and children about updates" $ do broadcast'' ["foo", "bar"] `shouldBe` [ (conn1, value) , (conn2, value_foo) , (conn3, value_foo_bar) ]