2015-10-04 23:55:12 +02:00
|
|
|
{-# LANGUAGE CPP #-}
|
2015-01-21 08:27:25 +01:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
|
|
{-# LANGUAGE QuasiQuotes #-}
|
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
{-# LANGUAGE TypeOperators #-}
|
2015-10-02 14:38:19 +02:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2014-12-24 13:55:25 +01:00
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
2015-07-22 12:55:44 +02:00
|
|
|
module Servant.JSSpec where
|
2014-12-24 13:55:25 +01:00
|
|
|
|
2015-08-17 23:56:29 +02:00
|
|
|
import Data.Either (isRight)
|
2015-10-04 23:55:12 +02:00
|
|
|
#if !MIN_VERSION_base(4,8,0)
|
|
|
|
import Data.Monoid ((<>),mconcat)
|
|
|
|
#else
|
2015-10-04 23:40:22 +02:00
|
|
|
import Data.Monoid ((<>))
|
2015-10-04 23:55:12 +02:00
|
|
|
#endif
|
2015-08-17 23:56:29 +02:00
|
|
|
import Data.Proxy
|
2015-10-02 14:38:19 +02:00
|
|
|
import Data.Text (Text)
|
|
|
|
import qualified Data.Text as T
|
|
|
|
import Language.ECMAScript3.Parser (program, parse)
|
|
|
|
import Test.Hspec hiding (shouldContain, shouldNotContain)
|
2015-08-17 23:56:29 +02:00
|
|
|
|
2016-01-16 19:17:46 +01:00
|
|
|
import Servant.API.Internal.Test.ComprehensiveAPI
|
2015-08-17 23:56:29 +02:00
|
|
|
import Servant.JS
|
2015-09-21 12:31:00 +02:00
|
|
|
import Servant.JS.Internal
|
2015-08-17 23:56:29 +02:00
|
|
|
import qualified Servant.JS.Angular as NG
|
|
|
|
import qualified Servant.JS.Axios as AX
|
|
|
|
import qualified Servant.JS.JQuery as JQ
|
|
|
|
import qualified Servant.JS.Vanilla as JS
|
|
|
|
import Servant.JSSpec.CustomHeaders
|
2014-12-24 13:55:25 +01:00
|
|
|
|
2016-01-16 19:17:46 +01:00
|
|
|
-- * comprehensive api
|
|
|
|
|
2016-01-18 19:55:14 +01:00
|
|
|
-- This declaration simply checks that all instances are in place.
|
|
|
|
_ = jsForAPI comprehensiveAPI vanillaJS :: Text
|
2016-01-16 19:17:46 +01:00
|
|
|
|
|
|
|
-- * specs
|
|
|
|
|
2015-10-02 14:38:19 +02:00
|
|
|
type TestAPI = "simple" :> ReqBody '[JSON,FormUrlEncoded] Text :> Post '[JSON] Bool
|
2015-03-03 22:59:36 +01:00
|
|
|
:<|> "has.extension" :> Get '[FormUrlEncoded,JSON] Bool
|
2014-12-24 13:55:25 +01:00
|
|
|
|
2015-02-19 01:36:31 +01:00
|
|
|
type TopLevelRawAPI = "something" :> Get '[JSON] Int
|
2015-07-30 18:44:13 +02:00
|
|
|
:<|> Raw IO ()
|
2015-01-02 10:46:21 +01:00
|
|
|
|
2015-10-02 14:38:19 +02:00
|
|
|
type HeaderHandlingAPI = "something" :> Header "Foo" Text
|
2015-02-19 01:36:31 +01:00
|
|
|
:> Get '[JSON] Int
|
2015-01-21 08:27:25 +01:00
|
|
|
|
2015-10-02 14:38:19 +02:00
|
|
|
type CustomAuthAPI = "something" :> Authorization "Basic" Text
|
2015-02-19 01:36:31 +01:00
|
|
|
:> Get '[JSON] Int
|
2015-01-21 08:47:23 +01:00
|
|
|
|
2015-10-02 14:38:19 +02:00
|
|
|
type CustomHeaderAPI = "something" :> MyLovelyHorse Text
|
2015-02-19 01:36:31 +01:00
|
|
|
:> Get '[JSON] Int
|
2015-01-21 09:32:06 +01:00
|
|
|
|
2015-10-02 14:38:19 +02:00
|
|
|
type CustomHeaderAPI2 = "something" :> WhatsForDinner Text
|
2015-02-19 01:36:31 +01:00
|
|
|
:> Get '[JSON] Int
|
2015-01-22 01:41:06 +01:00
|
|
|
|
2015-01-21 08:27:25 +01:00
|
|
|
headerHandlingProxy :: Proxy HeaderHandlingAPI
|
|
|
|
headerHandlingProxy = Proxy
|
|
|
|
|
2015-01-21 08:47:23 +01:00
|
|
|
customAuthProxy :: Proxy CustomAuthAPI
|
|
|
|
customAuthProxy = Proxy
|
|
|
|
|
2015-01-21 09:32:06 +01:00
|
|
|
customHeaderProxy :: Proxy CustomHeaderAPI
|
|
|
|
customHeaderProxy = Proxy
|
|
|
|
|
2015-01-22 01:41:06 +01:00
|
|
|
customHeaderProxy2 :: Proxy CustomHeaderAPI2
|
|
|
|
customHeaderProxy2 = Proxy
|
|
|
|
|
2015-07-17 23:36:38 +02:00
|
|
|
data TestNames = Vanilla
|
|
|
|
| VanillaCustom
|
|
|
|
| JQuery
|
|
|
|
| JQueryCustom
|
|
|
|
| Angular
|
|
|
|
| AngularCustom
|
2015-07-27 16:49:52 +02:00
|
|
|
| Axios
|
|
|
|
| AxiosCustom
|
2015-07-17 23:36:38 +02:00
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
customOptions :: CommonGeneratorOptions
|
2015-07-28 15:06:00 +02:00
|
|
|
customOptions = defCommonGeneratorOptions
|
|
|
|
{ successCallback = "okCallback"
|
|
|
|
, errorCallback = "errorCallback"
|
|
|
|
}
|
2015-08-17 23:50:42 +02:00
|
|
|
|
2014-12-24 13:55:25 +01:00
|
|
|
spec :: Spec
|
2015-07-17 23:36:38 +02:00
|
|
|
spec = describe "Servant.JQuery" $ do
|
2015-07-23 13:47:44 +02:00
|
|
|
generateJSSpec Vanilla JS.generateVanillaJS
|
|
|
|
generateJSSpec VanillaCustom (JS.generateVanillaJSWith customOptions)
|
|
|
|
generateJSSpec JQuery JQ.generateJQueryJS
|
|
|
|
generateJSSpec JQueryCustom (JQ.generateJQueryJSWith customOptions)
|
|
|
|
generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions)
|
|
|
|
generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions)
|
2015-07-28 15:06:00 +02:00
|
|
|
generateJSSpec Axios (AX.generateAxiosJS AX.defAxiosOptions)
|
2015-09-21 12:31:00 +02:00
|
|
|
generateJSSpec AxiosCustom (AX.generateAxiosJSWith (AX.defAxiosOptions { AX.withCredentials = True }) customOptions)
|
2015-08-17 23:50:42 +02:00
|
|
|
|
2015-07-23 13:47:44 +02:00
|
|
|
angularSpec Angular
|
2015-07-28 15:44:55 +02:00
|
|
|
axiosSpec
|
2015-07-28 15:06:00 +02:00
|
|
|
--angularSpec AngularCustom
|
2015-07-17 23:36:38 +02:00
|
|
|
|
2015-10-04 23:40:22 +02:00
|
|
|
shouldContain :: Text -> Text -> Expectation
|
2015-10-02 14:38:19 +02:00
|
|
|
a `shouldContain` b = shouldSatisfy a (T.isInfixOf b)
|
2015-10-04 23:40:22 +02:00
|
|
|
|
|
|
|
shouldNotContain :: Text -> Text -> Expectation
|
2015-10-02 14:38:19 +02:00
|
|
|
a `shouldNotContain` b = shouldNotSatisfy a (T.isInfixOf b)
|
|
|
|
|
2015-08-17 23:50:42 +02:00
|
|
|
axiosSpec :: Spec
|
2015-07-28 15:44:55 +02:00
|
|
|
axiosSpec = describe specLabel $ do
|
2015-12-02 16:56:56 +01:00
|
|
|
let reqList = listFromAPI (Proxy :: Proxy NoTypes) (Proxy :: Proxy TestAPI)
|
2015-07-28 15:44:55 +02:00
|
|
|
it "should add withCredentials when needed" $ do
|
2015-12-02 14:22:01 +01:00
|
|
|
let jsText = genJS withCredOpts $ reqList
|
2015-07-28 15:44:55 +02:00
|
|
|
output jsText
|
2015-10-02 14:38:19 +02:00
|
|
|
jsText `shouldContain` "withCredentials: true"
|
2015-07-28 15:44:55 +02:00
|
|
|
it "should add xsrfCookieName when needed" $ do
|
2015-12-02 14:22:01 +01:00
|
|
|
let jsText = genJS cookieOpts $ reqList
|
2015-07-28 15:44:55 +02:00
|
|
|
output jsText
|
|
|
|
jsText `shouldContain` ("xsrfCookieName: 'MyXSRFcookie'")
|
|
|
|
it "should add withCredentials when needed" $ do
|
2015-12-02 14:22:01 +01:00
|
|
|
let jsText = genJS headerOpts $ reqList
|
2015-07-28 15:44:55 +02:00
|
|
|
output jsText
|
|
|
|
jsText `shouldContain` ("xsrfHeaderName: 'MyXSRFheader'")
|
|
|
|
where
|
|
|
|
specLabel = "Axios"
|
|
|
|
output _ = return ()
|
|
|
|
withCredOpts = AX.defAxiosOptions { AX.withCredentials = True }
|
|
|
|
cookieOpts = AX.defAxiosOptions { AX.xsrfCookieName = Just "MyXSRFcookie" }
|
|
|
|
headerOpts = AX.defAxiosOptions { AX.xsrfHeaderName = Just "MyXSRFheader" }
|
2015-10-02 14:38:19 +02:00
|
|
|
genJS :: AxiosOptions -> [AjaxReq] -> Text
|
|
|
|
genJS opts req = mconcat . map (AX.generateAxiosJS opts) $ req
|
2015-07-28 15:44:55 +02:00
|
|
|
|
2015-08-17 23:50:42 +02:00
|
|
|
angularSpec :: TestNames -> Spec
|
2015-07-17 23:36:38 +02:00
|
|
|
angularSpec test = describe specLabel $ do
|
2015-12-02 16:56:56 +01:00
|
|
|
let reqList = listFromAPI (Proxy :: Proxy NoTypes) (Proxy :: Proxy TestAPI)
|
2015-07-17 23:36:38 +02:00
|
|
|
it "should implement a service globally" $ do
|
2015-12-02 14:22:01 +01:00
|
|
|
let jsText = genJS reqList
|
2015-07-17 23:36:38 +02:00
|
|
|
output jsText
|
2015-10-02 14:38:19 +02:00
|
|
|
jsText `shouldContain` (".service('" <> testName <> "'")
|
2015-08-17 23:50:42 +02:00
|
|
|
|
2015-07-17 23:36:38 +02:00
|
|
|
it "should depend on $http service globally" $ do
|
2015-12-02 14:22:01 +01:00
|
|
|
let jsText = genJS reqList
|
2015-07-17 23:36:38 +02:00
|
|
|
output jsText
|
2015-10-02 14:38:19 +02:00
|
|
|
jsText `shouldContain` ("('" <> testName <> "', function($http) {")
|
2015-08-17 23:50:42 +02:00
|
|
|
|
2015-07-17 23:36:38 +02:00
|
|
|
it "should not depend on $http service in handlers" $ do
|
2015-12-02 14:22:01 +01:00
|
|
|
let jsText = genJS reqList
|
2015-07-17 23:36:38 +02:00
|
|
|
output jsText
|
|
|
|
jsText `shouldNotContain` "getsomething($http, "
|
|
|
|
where
|
2015-10-02 14:38:19 +02:00
|
|
|
specLabel = "AngularJS(" <> (show test) <> ")"
|
2015-07-17 23:36:38 +02:00
|
|
|
output _ = return ()
|
|
|
|
testName = "MyService"
|
|
|
|
ngOpts = NG.defAngularOptions { NG.serviceName = testName }
|
2015-07-22 19:23:31 +02:00
|
|
|
genJS req = NG.angularService ngOpts req
|
2015-08-17 23:50:42 +02:00
|
|
|
|
2015-10-02 14:38:19 +02:00
|
|
|
generateJSSpec :: TestNames -> (AjaxReq -> Text) -> Spec
|
2015-07-17 23:36:38 +02:00
|
|
|
generateJSSpec n gen = describe specLabel $ do
|
2015-10-04 23:40:22 +02:00
|
|
|
let parseFromText = parse program ""
|
2015-01-02 10:46:21 +01:00
|
|
|
it "should generate valid javascript" $ do
|
2015-10-02 14:38:19 +02:00
|
|
|
let s = jsForAPI (Proxy :: Proxy TestAPI) (mconcat . map gen)
|
|
|
|
parseFromText s `shouldSatisfy` isRight
|
2015-01-02 10:46:21 +01:00
|
|
|
|
|
|
|
it "should use non-empty function names" $ do
|
2015-07-17 23:36:38 +02:00
|
|
|
let (_ :<|> topLevel) = javascript (Proxy :: Proxy TopLevelRawAPI)
|
|
|
|
output $ genJS (topLevel "GET")
|
2015-10-02 14:38:19 +02:00
|
|
|
parseFromText (genJS $ topLevel "GET") `shouldSatisfy` isRight
|
2014-12-24 13:55:25 +01:00
|
|
|
|
2015-01-21 08:27:25 +01:00
|
|
|
it "should handle simple HTTP headers" $ do
|
2015-07-17 23:36:38 +02:00
|
|
|
let jsText = genJS $ javascript headerHandlingProxy
|
|
|
|
output jsText
|
2015-10-02 14:38:19 +02:00
|
|
|
parseFromText jsText `shouldSatisfy` isRight
|
2015-01-21 08:27:25 +01:00
|
|
|
jsText `shouldContain` "headerFoo"
|
2015-07-17 23:36:38 +02:00
|
|
|
jsText `shouldContain` (header n "Foo" $ "headerFoo")
|
2015-01-21 08:27:25 +01:00
|
|
|
|
2015-01-21 08:47:23 +01:00
|
|
|
it "should handle complex HTTP headers" $ do
|
2015-07-17 23:36:38 +02:00
|
|
|
let jsText = genJS $ javascript customAuthProxy
|
|
|
|
output jsText
|
2015-10-02 14:38:19 +02:00
|
|
|
parseFromText jsText `shouldSatisfy` isRight
|
2015-01-21 08:47:23 +01:00
|
|
|
jsText `shouldContain` "headerAuthorization"
|
2015-07-17 23:36:38 +02:00
|
|
|
jsText `shouldContain` (header n "Authorization" $ "\"Basic \" + headerAuthorization")
|
2015-01-21 08:47:23 +01:00
|
|
|
|
2015-01-21 09:32:06 +01:00
|
|
|
it "should handle complex, custom HTTP headers" $ do
|
2015-07-17 23:36:38 +02:00
|
|
|
let jsText = genJS $ javascript customHeaderProxy
|
|
|
|
output jsText
|
2015-10-02 14:38:19 +02:00
|
|
|
parseFromText jsText `shouldSatisfy` isRight
|
2015-01-21 09:32:06 +01:00
|
|
|
jsText `shouldContain` "headerXMyLovelyHorse"
|
2015-07-17 23:36:38 +02:00
|
|
|
jsText `shouldContain` (header n "X-MyLovelyHorse" $ "\"I am good friends with \" + headerXMyLovelyHorse")
|
2015-01-22 01:41:06 +01:00
|
|
|
|
|
|
|
it "should handle complex, custom HTTP headers (template replacement)" $ do
|
2015-07-17 23:36:38 +02:00
|
|
|
let jsText = genJS $ javascript customHeaderProxy2
|
|
|
|
output jsText
|
2015-10-02 14:38:19 +02:00
|
|
|
parseFromText jsText `shouldSatisfy` isRight
|
2015-01-22 01:41:06 +01:00
|
|
|
jsText `shouldContain` "headerXWhatsForDinner"
|
2015-07-17 23:36:38 +02:00
|
|
|
jsText `shouldContain` (header n "X-WhatsForDinner" $ "\"I would like \" + headerXWhatsForDinner + \" with a cherry on top.\"")
|
2015-04-19 11:56:29 +02:00
|
|
|
|
|
|
|
it "can generate the whole javascript code string at once with jsForAPI" $ do
|
2015-10-02 14:45:50 +02:00
|
|
|
let jsStr = jsForAPI (Proxy :: Proxy TestAPI) (mconcat . map gen)
|
2015-10-02 14:38:19 +02:00
|
|
|
parseFromText jsStr `shouldSatisfy` isRight
|
2015-07-17 23:36:38 +02:00
|
|
|
where
|
2015-10-02 14:38:19 +02:00
|
|
|
specLabel = "generateJS(" <> (show n) <> ")"
|
2015-07-28 01:27:20 +02:00
|
|
|
output _ = return ()
|
2015-07-22 19:23:31 +02:00
|
|
|
genJS req = gen req
|
2015-10-02 14:38:19 +02:00
|
|
|
header :: TestNames -> Text -> Text -> Text
|
2015-07-17 23:36:38 +02:00
|
|
|
header v headerName headerValue
|
2015-10-02 14:38:19 +02:00
|
|
|
| v `elem` [Vanilla, VanillaCustom] = "xhr.setRequestHeader(\"" <> headerName <> "\", " <> headerValue <> ");\n"
|
|
|
|
| otherwise = "headers: { \"" <> headerName <> "\": " <> headerValue <> " }\n"
|