{-# LANGUAGE OverloadedStrings #-} module ConnectionSpec (spec) where import AFRP (Request(..)) import Data.Aeson (object, (.=)) import qualified Data.HashMap.Strict as HM import Data.Maybe (fromJust) import Data.Text (Text) import Data.Time (UTCTime (..), utc) import Data.UUID (fromString) import HomeAssistant.Controller (Service (..), Target(..)) import HomeAssistant.Runtime.Bus import HomeAssistant.Runtime.Flags (loadFlags, setEnabled) import HomeAssistant.Runtime.Metrics (registerAppMetrics) import HomeAssistant.Runtime.RateLimit (registerRateLimitMetrics, slidingWindowLimiter) import HomeAssistant.Runtime.Connection (dispatchService, encodeService, isTriggerEvent) import Katip (Namespace (Namespace), runKatipContextT) import Katip.Monadic (runNoLoggingT) import Support (withTestLogEnv) import System.IO.Temp (withTempDirectory) import qualified System.Metrics as Metrics import System.Timeout (timeout) import Test.Hspec spec :: Spec spec = do describe "encodeService" $ do it "encodes a call_service message" $ encodeService 7 (Service "light" "turn_on" Nothing [EntityId "light.bedroom_masse"]) `shouldBe` object [ "id" .= (7 :: Int) , "type" .= ("call_service" :: Text) , "domain" .= ("light" :: Text) , "service" .= ("turn_on" :: Text) , "target" .= object ["entity_id" .= ("light.bedroom_masse" :: Text)] ] it "includes service_data when present" $ encodeService 8 (Service "light" "turn_on" (Just (object ["brightness" .= (200 :: Int)])) [EntityId "light.bedroom_masse"]) `shouldBe` object [ "id" .= (8 :: Int) , "type" .= ("call_service" :: Text) , "domain" .= ("light" :: Text) , "service" .= ("turn_on" :: Text) , "target" .= object ["entity_id" .= ("light.bedroom_masse" :: Text)] , "service_data" .= object ["brightness" .= (200 :: Int)] ] describe "isTriggerEvent" $ do it "is true for event frames" $ isTriggerEvent (object ["type" .= ("event" :: Text)]) `shouldBe` True it "is false for result frames" $ isTriggerEvent (object ["type" .= ("result" :: Text)]) `shouldBe` False it "is false when there is no type" $ isTriggerEvent (object ["id" .= (1 :: Int)]) `shouldBe` False describe "dispatchService" $ do it "skips disabled handlers without consuming a call id" $ do store <- Metrics.newStore m <- registerAppMetrics store rlMetrics <- registerRateLimitMetrics store limiter <- slidingWindowLimiter rlMetrics 10 20 withTempDirectory "/tmp" "connection-spec" $ \dir -> do flags <- runNoLoggingT (loadFlags dir ["test"]) setEnabled flags "test" False withTestLogEnv $ \le -> withBus le m flags $ \bus -> do timeout 1000000 (do runKatipContextT (busLogEnv bus) () (Namespace ["connection"]) $ dispatchService limiter bus (req 1) (lightOn [EntityId "light.test"]) generateCallId (busGen bus)) `shouldReturn` Just 1 sample <- Metrics.sampleAll store HM.lookup "hass.service.out" sample `shouldBe` Just (Metrics.Counter 0) req :: Int -> Request req n = Request (UTCTime (toEnum 0) (fromIntegral (0 :: Int))) utc (fromJust (fromString uuid)) "test" where pad i = replicate (12 - length (show i)) '0' <> show i uuid = "00000000-0000-0000-0000-" <> pad n lightOn :: [Target] -> Service lightOn targets = Service "light" "turn_on" Nothing targets lightOff :: [Target] -> Service lightOff targets = Service "light" "turn_off" Nothing targets