packages feed

toxcore-0.2.11: test/Network/Tox/DHT/DhtStateSpec.hs

{-# LANGUAGE Trustworthy #-}
module Network.Tox.DHT.DhtStateSpec where

import           Test.Hspec
import           Test.QuickCheck

import           Control.Monad                 (unless, when)
import           Data.List                     (nub, sort)
import           Data.Proxy                    (Proxy (..))
import qualified Data.Set                      as Set

import qualified Network.Tox.Crypto.KeyPair    as KeyPair
import           Network.Tox.DHT.DhtState      (DhtState)
import qualified Network.Tox.DHT.DhtState      as DhtState
import qualified Network.Tox.DHT.Distance      as Distance
import qualified Network.Tox.DHT.NodeList      as NodeList
import           Network.Tox.EncodingSpec
import qualified Network.Tox.NodeInfo.NodeInfo as NodeInfo


spec :: Spec
spec = do
  readShowSpec (Proxy :: Proxy DhtState)

  it "the state can never contain itself" $
    property $ \keyPair time nodeInfo ->
      let
        dhtState = DhtState.empty time keyPair
        afterAdd = DhtState.addNode time
          nodeInfo { NodeInfo.publicKey = KeyPair.publicKey keyPair }
          dhtState
      in
      afterAdd `shouldBe` dhtState

  describe "adding a node that was not yet contained" $ do
    it "should result in a different state" $
      property $ \keyPair time nodeInfo ->
        let
          dhtState = DhtState.empty time keyPair
          afterAdd = DhtState.addNode time nodeInfo dhtState
        in
        unless (DhtState.containsNode (NodeInfo.publicKey nodeInfo) dhtState) $
          afterAdd `shouldNotBe` dhtState

    it "and removing it yields the same state" $
      property $ \keyPair time nodeInfo ->
        let
          dhtState    = DhtState.empty time keyPair
          afterAdd    = DhtState.addNode time nodeInfo dhtState
          afterRemove = DhtState.removeNode (NodeInfo.publicKey nodeInfo) afterAdd
        in
        unless (DhtState.containsNode (NodeInfo.publicKey nodeInfo) dhtState) $
          afterRemove `shouldBe` dhtState

  describe "adding a node" $
    it "and adding it again does not change the state twice" $
      property $ \keyPair time nodeInfo ->
        let
          dhtState  = DhtState.empty time keyPair
          afterAdd1 = DhtState.addNode time nodeInfo dhtState
          afterAdd2 = DhtState.addNode time nodeInfo afterAdd1
        in
        afterAdd1 `shouldBe` afterAdd2

  describe "adding a search node" $ do
    it "should result in a different state" $
      property $ \keyPair time publicKey ->
        let
          dhtState = DhtState.empty time keyPair
          afterAdd = DhtState.addSearchKey time publicKey dhtState
        in
        afterAdd `shouldNotBe` dhtState

    it "and removing it yields the same state" $
      property $ \keyPair time publicKey ->
        let
          dhtState    = DhtState.empty time keyPair
          afterAdd    = DhtState.addSearchKey time publicKey dhtState
          afterRemove = DhtState.removeSearchKey publicKey afterAdd
        in
        afterRemove `shouldBe` dhtState

    it "and adding it again does not change the state twice" $
      property $ \keyPair time publicKey ->
        let
          dhtState  = DhtState.empty time keyPair
          afterAdd1 = DhtState.addSearchKey time publicKey dhtState
          afterAdd2 = DhtState.addSearchKey time publicKey afterAdd1
        in
        afterAdd1 `shouldBe` afterAdd2

    it "and adding a node info for it will not add it to the search entry's Client List" $
      property $ \keyPair time nodeInfo ->
        let
          dhtState = DhtState.empty time keyPair
          afterAddSearchKey = DhtState.addSearchKey time
            (NodeInfo.publicKey nodeInfo)
            dhtState
        in
        DhtState.size (DhtState.addNode time nodeInfo afterAddSearchKey)
        `shouldBe`
        DhtState.size (DhtState.addNode time nodeInfo dhtState)

  describe "takeClosestNodesTo" $ do
    it "returns the requested number of nodes if there are enough nodes in the state" $
      property $ \dhtState n publicKey -> n >= 0 ==>
        let
          taken = DhtState.takeClosestNodesTo n publicKey dhtState
          nodes = NodeList.foldNodes (flip Set.insert) Set.empty dhtState
        in
        when (Set.size nodes >= n) $ length taken `shouldBe` n

    it "returns distinct nodes sorted by distance from the given key" $
      property $ \dhtState n publicKey -> n >= 0 ==>
        let
          taken = DhtState.takeClosestNodesTo n publicKey dhtState
          dists = map (Distance.xorDistance publicKey . NodeInfo.publicKey) taken
        in
        dists `shouldBe` (nub . sort) dists