content: {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  
  module Icepeak.Server.SubscriptionTreeSpec (spec) where
  
  import Data.List (sort)
  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 Icepeak.Server.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 = sort (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)
                       ]
  