packages feed

cfenv-0.1.0.0: test/System/CloudFoundry/EnvironmentSpec.hs

{-# LANGUAGE QuasiQuotes #-}

module System.CloudFoundry.EnvironmentSpec where

import qualified Data.Map.Strict as Map
import System.Environment (setEnv, unsetEnv)

import Test.Hspec
import Text.RawString.QQ

import qualified System.CloudFoundry.Environment as CfEnv

spec :: Spec
spec = do
  describe "isRunningOnCf" $ do
    it "returns true if VCAP_APPLICATION has a value" $ do
      setEnv "VCAP_APPLICATION" "{}"
      CfEnv.isRunningOnCf `shouldReturn` True

    it "returns true if VCAP_APPLICATION is not set" $ do
      unsetEnv "VCAP_APPLICATION"
      CfEnv.isRunningOnCf `shouldReturn` False

    it "returns true if VCAP_APPLICATION is empty" $ do
      setEnv "VCAP_APPLICATION" ""
      CfEnv.isRunningOnCf `shouldReturn` False

    it "returns true if VCAP_APPLICATION is only whitespace" $ do
      setEnv "VCAP_APPLICATION" "    "
      CfEnv.isRunningOnCf `shouldReturn` False

  describe "current" $
    before setEnvVars $ do
      it "returns error if HOME is not set" $ do
        unsetEnv "HOME"
        CfEnv.current `shouldThrow` anyIOException

      it "returns error if MEMORY_LIMIT is not set" $ do
        unsetEnv "MEMORY_LIMIT"
        CfEnv.current `shouldThrow` anyIOException

      it "returns error if PWD is not set" $ do
        unsetEnv "PWD"
        CfEnv.current `shouldThrow` anyIOException

      it "returns error if PORT is not set" $ do
        unsetEnv "PORT"
        CfEnv.current `shouldThrow` anyIOException

      it "returns error if PORT is not valid number" $ do
        setEnv "PORT" "abc"
        CfEnv.current `shouldThrow` (== CfEnv.NotInteger "PORT" "abc")

      it "returns error if TMPDIR is not set" $ do
        unsetEnv "TMPDIR"
        CfEnv.current `shouldThrow` anyIOException

      it "returns error if USER is not set" $ do
        unsetEnv "USER"
        CfEnv.current `shouldThrow` anyIOException

      it "returns error if VCAP_APPLICATION is not set" $ do
        unsetEnv "VCAP_APPLICATION"
        CfEnv.current `shouldThrow` anyIOException

      it "returns error if VCAP_APPLICATION bad JSON" $ do
        setEnv "VCAP_APPLICATION" "not-json"
        CfEnv.current `shouldThrow` (== CfEnv.DecodeError "VCAP_APPLICATION" "Error in $: string")

      it "returns Application if VCAP_SERVICES is not set" $ do
        unsetEnv "VCAP_SERVICES"
        app <- CfEnv.current
        app `shouldSatisfy` isApplication

      it "returns error if VCAP_SERVICES bad JSON" $ do
        setEnv "VCAP_SERVICES" "not-json"
        CfEnv.current `shouldThrow` (== CfEnv.DecodeError "VCAP_SERVICES" "Error in $: string")

      it "returns Application when the environment is correct" $ do
        let servicesInJson =
              CfEnv.Services $
                  Map.singleton
                    "cleardb"
                    [ CfEnv.Service
                          { CfEnv.name = "service_name"
                          , CfEnv.label = "service_label"
                          , CfEnv.tags = ["tag_a"]
                          , CfEnv.plan = "service_plan"
                          , CfEnv.credentials =
                              Map.fromList
                                [ ("username", "service_username")
                                , ("password", "service_password")
                                ]
                          }
                      ]

        let expected = CfEnv.Application
                { CfEnv.appId = "abc_application_id"
                , CfEnv.applicationUris =
                    ["haskell-test.cfapps.io"]
                , CfEnv.cfApi = "https://api.sys.foundation"
                , CfEnv.home = "/home/userZ"
                , CfEnv.host = "app_host"
                , CfEnv.instanceId = "abc_instance_id"
                , CfEnv.index = 100
                , CfEnv.memoryLimit = "256M"
                , CfEnv.appName = "app_name"
                , CfEnv.pwd = "/pwd"
                , CfEnv.port = 9000
                , CfEnv.tmpDir = "/tmpdir"
                , CfEnv.services = servicesInJson
                , CfEnv.spaceId = "abc_space_id"
                , CfEnv.spaceName = "development"
                , CfEnv.user = "tom"
                , CfEnv.version = "xxx_version"
                , CfEnv.limits =
                    CfEnv.Limits
                      { CfEnv.disk = 1024
                      , CfEnv.fds = 16384
                      , CfEnv.mem = 2048
                      }
                }

        CfEnv.current `shouldReturn` expected

  describe "lookupCurrent" $ do
    it "returns Just Application if running on Cloud Foundry" $ do
      expected <- CfEnv.current
      CfEnv.lookupCurrent `shouldReturn` Just expected

    it "returns Nothing if not running on Cloud Foundry" $ do
      unsetEnv "VCAP_APPLICATION"
      CfEnv.lookupCurrent `shouldReturn` Nothing

  describe "credentialString" $ do
    let service = CfEnv.Service
          { CfEnv.name = "service_name"
          , CfEnv.label = "service_label"
          , CfEnv.tags = ["tag_a"]
          , CfEnv.plan = "service_plan"
          , CfEnv.credentials = Map.singleton "the-key" "the-value"
          }

    it "returns nothing if there is no matching string" $ do
      CfEnv.credentialString "unknown" service `shouldBe` Nothing

    it "returns the string if it is found" $ do
      CfEnv.credentialString "the-key" service `shouldBe` Just "the-value"

  context "for Service searching functions" $ do
    let serviceA =
          CfEnv.Service
            { CfEnv.name = "name-a"
            , CfEnv.label = "label-a"
            , CfEnv.tags = ["good_tag", "ignore_tag"]
            , CfEnv.plan = "plan-a"
            , CfEnv.credentials = Map.empty
            }
    let serviceB =
          CfEnv.Service
            { CfEnv.name = "name-b"
            , CfEnv.label = "label-b"
            , CfEnv.tags = ["ignore_tag"]
            , CfEnv.plan = "plan-b"
            , CfEnv.credentials = Map.empty
            }
    let serviceC =
          CfEnv.Service
            { CfEnv.name = "name-c"
            , CfEnv.label = "label-c"
            , CfEnv.tags = ["good_tag"]
            , CfEnv.plan = "plan-c"
            , CfEnv.credentials = Map.empty
            }
    let services = CfEnv.Services $ Map.fromList
                                      [ ("relational-db", [serviceA, serviceB])
                                      , ("message-queue" , [serviceC])
                                      ]

    describe "withTag" $ do
      it "returns an empty list if no matching services are found" $ do
        CfEnv.withTag "bad_tag" services `shouldBe` []

      it "returns the services with matching tags" $ do
        CfEnv.withTag "good_tag" services `shouldBe` [serviceC , serviceA]

    describe "withName" $ do
      it "returns nothing if there is no service with the given name" $ do
        CfEnv.withName "unknown" services `shouldBe` Nothing

      it "returns the service if it exists" $ do
        CfEnv.withName "name-b" services `shouldBe` Just serviceB

    describe "withLabel" $ do
      it "returns an empty list if there is no service with the given label" $ do
        CfEnv.withLabel "unknown" services `shouldBe` []

      it "returns the services with that labe" $ do
        CfEnv.withLabel "relational-db" services `shouldBe` [serviceA, serviceB]

setEnvVars :: IO ()
setEnvVars = do
  setEnv "HOME" "/home/userZ"
  setEnv "MEMORY_LIMIT" "256M"
  setEnv "PORT" "9000"
  setEnv "PWD" "/pwd"
  setEnv "TMPDIR" "/tmpdir"
  setEnv "USER" "tom"
  setEnv "VCAP_APPLICATION" vcapApplicationJson
  setEnv "VCAP_SERVICES" vcapServicesJson

vcapApplicationJson :: String
vcapApplicationJson =
  [r|{
    "instance_id": "abc_instance_id",
    "application_id": "abc_application_id",
    "application_uris": [
      "haskell-test.cfapps.io"
    ],
    "instance_index": 100,
    "name": "app_name",
    "port": 1080,
    "host": "app_host",
    "version": "xxx_version",
    "application_uris": ["appname.apps.foundation"],
    "space_id": "abc_space_id",
    "space_name": "development",
    "cf_api": "https://api.sys.foundation",
    "limits": {
      "disk": 1024,
      "fds": 16384,
      "mem": 2048
    }
  }|]

vcapServicesJson :: String
vcapServicesJson =
  [r|
    {
      "cleardb": [{
        "name": "service_name",
        "label": "service_label",
        "tags": ["tag_a"],
        "plan": "service_plan",
        "credentials": {
          "username": "service_username",
          "password": "service_password"
        }
      }]
    }
  |]

isApplication :: CfEnv.Application -> Bool
isApplication _ = True