packages feed

ble-0.4.0.0: test/BluetoothSpec.hs

{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
#ifndef Bluez
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
#else
#ifndef DBus
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
#endif
#endif
module BluetoothSpec (spec) where

import Bluetooth
import Bluetooth.Internal.Types (Error)
import Control.Monad
import Control.Monad.IO.Class
import Data.Either              (isRight)
import Data.Maybe               (isJust)
import DBus
import Test.Hspec

import qualified Data.ByteString as BS

import Mock


spec :: Spec
spec = do
#ifdef Bluez
  registerApplicationSpec
  unregisterApplicationSpec
  advertiseSpec
  unadvertiseSpec
#endif
#ifdef DBusMock
  getServiceSpec
  getAllServicesSpec
  getAllDevicesSpec
#endif
  return ()

registerApplicationSpec :: Spec
registerApplicationSpec = describe "registerApplication" $ before connect $ do

  it "registers the service with bluez" $ \conn -> do
    v <- runBluetoothM (registerApplication testApp) conn
    v `shouldSatisfy` isRight
    -- We verify that the application is in fact registered by checking that
    -- attempting to register it again throws an AlreadyExists error.
    Left err <- runBluetoothM (registerApplication testApp) conn
    _ <- runBluetoothM (unregisterApplication $ fromRight v) conn
    show err `shouldContain` "Already Exists"

unregisterApplicationSpec :: Spec
unregisterApplicationSpec = describe "unregisterApplication" $ before connect $ do

  it "unregisters the service" $ \conn -> do
    Right p <- runBluetoothM (registerApplication testApp) conn
    v1 <- runBluetoothM (unregisterApplication p) conn
    v1 `shouldSatisfy` isRight
    v2 <- runBluetoothM (registerApplication testApp) conn
    void $ runBluetoothM (unregisterApplication $ fromRight v2) conn
    v2 `shouldSatisfy` isRight

advertiseSpec :: Spec
advertiseSpec = describe "advertise" $ before connect $ do

  let checkAdvert ad conn = do
        v <- runBluetoothM (advertise ad) conn
        v `shouldSatisfy` isRight
        -- We verify that the advertisement was registered by checking that
        -- attempting to register it again throws an AlreadyExists error.
        Left err <- runBluetoothM (advertise ad) conn
        _ <- runBluetoothM (unadvertise ad) conn
        show err `shouldContain` "Already Exists"

  it "advertises a set of services" $ \conn -> checkAdvert testAdv conn

  {-it "works with service data" $ \conn -> do-}
    {-let adv = testAdv-}
         {-& value . serviceData . at "351930f8"-}
         {-?~ "hi"-}
    {-checkAdvert adv conn-}

  it "works with manufacturer data" $ \conn -> do
    let adv = testAdv & value . manufacturerData . at 1 ?~ "hi"
    checkAdvert adv conn

unadvertiseSpec :: Spec
unadvertiseSpec = describe "unadvertise" $ before connect $ do

  it "unregisters an advertisement" $ \conn -> do
    v1 <- runBluetoothM (advertise testAdv) conn
    v1 `shouldSatisfy` isRight
    Left err <- runBluetoothM (advertise testAdv) conn
    show err `shouldContain` "Already Exists"
    v2 <- runBluetoothM (unadvertise testAdv) conn
    v2 `shouldSatisfy` isRight
    v3 <- runBluetoothM (advertise testAdv) conn
    v3 `shouldSatisfy` isRight

getServiceSpec :: Spec
getServiceSpec = describe "getService" $ before connect $ do

  it "retrieves services by UUID" $ \conn -> withAService $ do
    Right ms <- runBluetoothM (getService mockServiceUUID) conn
    ms `shouldSatisfy` isJust

  it "returns Nothing if the application does not exist" $ \conn -> withAService $ do
    let unknownUUID = "da92ce4a-2a0f-4c5d-ac68-9d3b01886976"
    h <- runBluetoothM (getService unknownUUID) conn
    h `shouldBe` Right Nothing

  context "the returned service" $ do

    it "has the service's characteristics" $ \conn -> withAService $ do
      Right (Just serv) <- runBluetoothM (getService mockServiceUUID) conn
      let [char] = serv ^. characteristics
      char ^. uuid `shouldBe` mockCharUUID

    it "has characteristics that can be read" $ \conn -> withAService $ do
      Right (Just serv) <- runBluetoothM (getService mockServiceUUID) conn
      let [char] = serv ^. characteristics
      let Just handler = char ^. readValue
      res <- runBluetoothM handler conn
      res `shouldBe` (Right $ BS.singleton 3)

    it "has characteristics that can be written" $ \conn -> withAService $ do
      Right (Just serv) <- runBluetoothM (getService mockServiceUUID) conn
      let [char] = serv ^. characteristics
      let Just whandler = char ^. writeValue
          Just rhandler = char ^. readValue
      writeResult <- runBluetoothM (whandler $ BS.singleton 10)  conn
      writeResult `shouldBe` Right True
      readResult <- runBluetoothM rhandler conn
      readResult `shouldBe` (Right $ BS.singleton 10)

getAllServicesSpec :: Spec
getAllServicesSpec = describe "getAllServices" $ before connect $ do

  it "retrieves services" $ \conn -> withAService $ do
    Right [service] <- runBluetoothM getAllServices conn
    service ^. uuid `shouldBe` mockServiceUUID

getAllDevicesSpec :: Spec
getAllDevicesSpec = describe "getAllDevices" $ before connect $ do

  it "retrieves devices" $ \conn -> withAService $ do
    Right devices <- runBluetoothM getAllDevices conn
    devices `shouldContain` ["11:22:33:44:55:66"]

  {-context "the returned device" $-}
    {-pendingWith "dbusmock doesn't currently allow changing properties"-}

    {-it "allows connecting" $ \conn -> withAService $ do-}
      {-resp1 <- runBluetoothM (isConnected "11:22:33:44:55:66") conn-}
      {-resp1 `shouldBe` Right False-}
      {-Right () <- runBluetoothM (connectTo "11:22:33:44:55:66") conn-}
      {-resp2 <- runBluetoothM (isConnected "11:22:33:44:55:66") conn-}
      {-resp2 `shouldBe` Right True-}

    {-it "allows pairing" $ \conn -> withAService $ do-}
      {-resp1 <- runBluetoothM (isPaired "11:22:33:44:55:66") conn-}
      {-resp1 `shouldBe` Right False-}
      {-Right () <- runBluetoothM (pairWith "11:22:33:44:55:66") conn-}
      {-resp2 <- runBluetoothM (isPaired "11:22:33:44:55:66") conn-}
      {-resp2 `shouldBe` Right True-}

    {-it "allows trusting" $ \conn -> withAService $ do-}
      {-resp1 <- runBluetoothM (isTrusted "11:22:33:44:55:66") conn-}
      {-resp1 `shouldBe` Right False-}
      {-Right () <- runBluetoothM (trust "11:22:33:44:55:66") conn-}
      {-resp2 <- runBluetoothM (isTrusted "11:22:33:44:55:66") conn-}
      {-resp2 `shouldBe` Right True-}
      {-Right () <- runBluetoothM (distrust "11:22:33:44:55:66") conn-}
      {-resp3 <- runBluetoothM (isTrusted "11:22:33:44:55:66") conn-}
      {-resp3 `shouldBe` Right True-}

-- * Test service

testApp :: Application
testApp
  = "/com/turingjump/test"
      & services .~ [testService]

testService :: Service 'Local
testService
  = "351930f8-7d31-43c1-92f5-fd2f0eac272f"
      & characteristics .~ [testCharacteristic]

testCharacteristic :: CharacteristicBS 'Local
testCharacteristic
  = "cdcb58aa-7e4c-4d22-b0bf-a90cd67ba60b"
      & readValue ?~ encodeRead go
      & properties .~ [CPRead, CPNotify]
  where
    go :: Handler BS.ByteString
    go = do
      liftIO $ putStrLn "Reading characteristic!"
      return "response"

testAdv :: WithObjectPath Advertisement
testAdv
  = advertisementFor testApp

-- * Utils

fromRight :: Either a b -> b
fromRight (Right x) = x
fromRight _ = error "Expected Right, got Left"

-- * Orphans

instance Eq MethodError where
  a == b = show a == show b

instance Eq Error where
  a == b = show a == show b

instance Show (Service m) where
  show a = show $ a ^. uuid

instance Eq (Service m) where
  a == b = a ^. uuid == b ^. uuid