From 9c4d4ef52d630a1c3347e008872d017da967e2cf Mon Sep 17 00:00:00 2001 From: Erik Aker Date: Wed, 15 Feb 2017 21:37:55 -0800 Subject: [PATCH] Add a few tests. Fixes #5 #1 --- servant-py.cabal | 5 ++- src/Servant/PY/Internal.hs | 16 +++++----- src/Servant/PY/Requests.hs | 2 +- test/Servant/PY/InternalSpec.hs | 56 ++++++++++++++++++++++++++------- 4 files changed, 58 insertions(+), 21 deletions(-) diff --git a/servant-py.cabal b/servant-py.cabal index dfcecca..7b48f68 100644 --- a/servant-py.cabal +++ b/servant-py.cabal @@ -38,7 +38,7 @@ library ghc-options: -Wall executable servant-py-exe - ghc-options: -Wall + ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N hs-source-dirs: examples if flag(example) @@ -72,12 +72,15 @@ test-suite servant-py-test Servant.PY.InternalSpec build-depends: base , servant-py + , aeson , base-compat + , bytestring , hspec , hspec-expectations , lens , QuickCheck , servant + , servant-foreign , text default-language: Haskell2010 diff --git a/src/Servant/PY/Internal.hs b/src/Servant/PY/Internal.hs index cde50fe..18058e3 100644 --- a/src/Servant/PY/Internal.hs +++ b/src/Servant/PY/Internal.hs @@ -175,18 +175,18 @@ filterBmpChars = Set.filter (< '\65536') -- This function creates a dict where the keys are string representations of variable -- names. This is due to the way arguments are passed into the function, and these -- arguments named params. In other words, [("key", "key")] becomes: {"key": key} -toPyDict :: [Text] -> Text -toPyDict dict +toPyDict :: Text -> [Text] -> Text +toPyDict offset dict | null dict = "{}" - | otherwise = "{" <> insides <> "}" - where insides = mconcat $ combiner <$> dict + | otherwise = "{" <> T.intercalate (",\n" <> offset) insides <> "}" + where insides = combiner <$> dict combiner a = "\"" <> a <> "\": " <> a -- Query params are passed into the function that makes the request, so we make -- a python dict out of them. -toPyParams :: [QueryArg f] -> Text -toPyParams [] = "" -toPyParams qargs = toPyDict paramList +toPyParams :: Text -> [QueryArg f] -> Text +toPyParams _ [] = "" +toPyParams offset qargs = toPyDict offset paramList where paramList = fmap (\qarg -> qarg ^. queryArgName.argName._PathSegment) qargs toPyHeader :: HeaderArg f -> Text @@ -255,7 +255,7 @@ withFormattedCaptures offset segments = formattedCaptures (capturesToFormatArgs <> ")" formatBuilder :: Text -> Text -formatBuilder val = val <> "=parse.quote("<> val <> ")" +formatBuilder val = val <> "=parse.quote(str("<> val <> "))" segmentToStr :: Segment f -> Text segmentToStr (Segment (Static s)) = s ^. _PathSegment diff --git a/src/Servant/PY/Requests.hs b/src/Servant/PY/Requests.hs index f24d984..30302eb 100644 --- a/src/Servant/PY/Requests.hs +++ b/src/Servant/PY/Requests.hs @@ -68,7 +68,7 @@ generatePyRequestWith opts req = "\n" <> remaining = remainingReqCall $ PyRequestArgs (not . null $ hs) (not . null $ queryparams) hasBody paramDef | null queryparams = "" - | otherwise = indent' <> "params = " <> toPyParams queryparams <> "\n" + | otherwise = indent' <> "params = " <> toPyParams (indent' <> indent') queryparams <> "\n" headerDef | null hs = "" | otherwise = indent' <> "headers = " <> buildHeaderDict hs <> "\n" diff --git a/test/Servant/PY/InternalSpec.hs b/test/Servant/PY/InternalSpec.hs index b33b699..649f887 100644 --- a/test/Servant/PY/InternalSpec.hs +++ b/test/Servant/PY/InternalSpec.hs @@ -1,7 +1,7 @@ {-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} @@ -9,13 +9,13 @@ module Servant.PY.InternalSpec where -import Data.Either (isRight) +import Data.Aeson +import qualified Data.ByteString.Char8 as B import Data.Monoid () -import Data.Monoid.Compat ((<>)) import Data.Proxy import Data.Text (Text) import qualified Data.Text as T -import GHC.TypeLits +import GHC.Generics import Prelude () import Prelude.Compat import Test.Hspec hiding @@ -27,10 +27,37 @@ import Test.QuickCheck (Arbitrary (..), import Servant.API.ContentTypes import Servant.API.Internal.Test.ComprehensiveAPI +import Servant.Foreign import Servant.PY.Internal +data SomeJson = SomeJson + { uvalue :: !T.Text + , pvalue :: !T.Text + , otherMissing :: Maybe T.Text + } deriving (Eq, Show, Generic) +instance ToJSON SomeJson + +-- * Our API type +type TestApi = "counter-req-header" :> Post '[JSON] SomeJson + :<|> "counter-queryparam" + :> QueryParam "sortby" T.Text + :> Header "Some-Header" T.Text :> Get '[JSON] SomeJson + :<|> "login-queryflag" :> QueryFlag "published" :> Get '[JSON] SomeJson + :<|> "login-params-authors-with-reqBody" + :> QueryParams "authors" T.Text + :> ReqBody '[JSON] SomeJson :> Post '[JSON] SomeJson + :<|> "login-with-path-var-and-header" + :> Capture "id" Int + :> Capture "Name" T.Text + :> Capture "hungrig" Bool + :> ReqBody '[JSON] SomeJson + :> Post '[JSON] (Headers '[Header "test-head" B.ByteString] SomeJson) + +testApi :: Proxy TestApi +testApi = Proxy + customOptions :: CommonGeneratorOptions customOptions = defCommonGeneratorOptions { urlPrefix = "urlForRequesting:9000" @@ -60,10 +87,17 @@ internalSpec = describe "Internal" $ do it "should only indent using whitespace" $ property $ \n -> indenter n indent == mconcat (replicate n (T.pack " ")) - -- it "should generate only valid python identifiers for any ASCII route" $ do - -- let parseIdentifier = fmap T.pack "" - -- property $ \x -> let valid = toValidFunctionName $ getASCII x in - -- Right valid == parseIdentifier valid - -- - -- it "should generate a valid python identifier when supplied with hyphens, unicode whitespace, non-bmp unicode" $ do - -- toValidFunctionName "a_--a\66352b\6158c\65075" `shouldBe` "a_abc\65075" + it "should generate a valid python identifier when supplied with hyphens, unicode whitespace, non-bmp unicode" $ + toValidFunctionName "a_--a\66352b\6158c\65075" `shouldBe` "a_abc\65075" + + it "should produce PyDicts where the key is a quoted version of the variable name" $ do + let dict = toPyDict " " ["forty", "one", "people"] + dict `shouldBe` "{\"forty\": forty,\n \"one\": one,\n \"people\": people}" + + let reqList = listFromAPI (Proxy :: Proxy NoTypes) (Proxy :: Proxy NoContent) (Proxy :: Proxy TestApi) + it "should correctly find captures" $ do + let captured = captures . last $ reqList + captured `shouldBe` ["id", "Name", "hungrig"] + it "should not incorrectly find captures" $ do + let captured = captures . head $ reqList + captured `shouldBe` []