From 7deeb175e04e07086419b74ec2e6c8c8396ca069 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Fri, 30 Aug 2019 15:34:02 -0400 Subject: [PATCH] [wip] try writing a test for redemption behavior --- PaymentServer.cabal | 2 + src/PaymentServer/Persistence.hs | 1 + src/PaymentServer/Redemption.hs | 71 ++++++++++++++++++++++++++++ src/PaymentServer/Server.hs | 4 +- test/SpecRedemption.hs | 79 ++++++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/PaymentServer/Redemption.hs create mode 100644 test/SpecRedemption.hs diff --git a/PaymentServer.cabal b/PaymentServer.cabal index 3e5ad7d..8f716de 100644 --- a/PaymentServer.cabal +++ b/PaymentServer.cabal @@ -17,6 +17,7 @@ library hs-source-dirs: src exposed-modules: PaymentServer.Processors.Stripe , PaymentServer.Persistence + , PaymentServer.Redemption , PaymentServer.Server , PaymentServer.Main build-depends: base >= 4.7 && < 5 @@ -46,6 +47,7 @@ test-suite PaymentServer-test main-is: Driver.hs other-modules: SpecStripe , SpecPersistence + , SpecRedemption , Util.WAI , Util.Gen , Util.JSON diff --git a/src/PaymentServer/Persistence.hs b/src/PaymentServer/Persistence.hs index eea14b4..d08f12a 100644 --- a/src/PaymentServer/Persistence.hs +++ b/src/PaymentServer/Persistence.hs @@ -3,6 +3,7 @@ module PaymentServer.Persistence , Fingerprint , RedeemError(NotPaid, AlreadyRedeemed) , VoucherDatabase(payForVoucher, redeemVoucher) + , MemoryVoucherDatabase , memory ) where diff --git a/src/PaymentServer/Redemption.hs b/src/PaymentServer/Redemption.hs new file mode 100644 index 0000000..7d465e4 --- /dev/null +++ b/src/PaymentServer/Redemption.hs @@ -0,0 +1,71 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE DeriveGeneric #-} + +-- | This module implements the exposed interface for redeeming a voucher for +-- signatures. +module PaymentServer.Redemption + ( RedemptionAPI + , redemptionServer + ) where + +import GHC.Generics + ( Generic + ) + +import Data.Text + ( Text + ) +import Data.Aeson + ( ToJSON(toJSON) + , FromJSON + , genericToEncoding + , defaultOptions + , encode + , object + , (.=) + ) +import Servant + ( Server + , ServerError(errBody, errHeaders) + , err400 + , throwError + ) +import Servant.API + ( JSON + , Post + , ReqBody + , (:>) + ) +import PaymentServer.Persistence + ( VoucherDatabase + , Voucher + ) + +data Result + = Failed + deriving (Show, Eq) + +type BlindedToken = Text + +data Redeem + = Redeem { redeemVoucher :: Voucher, redeemTokens :: [BlindedToken] } + deriving (Show, Eq, Generic) + +instance FromJSON Redeem + +instance ToJSON Result where + toJSON Failed = object [ "success" .= False ] + +type RedemptionAPI = ReqBody '[JSON] Redeem :> Post '[JSON] Result + +jsonErr400 = err400 + { errBody = encode Failed + , errHeaders = [ ("Content-Type", "application/json") ] + } + +redemptionServer :: VoucherDatabase d => d -> Server RedemptionAPI +redemptionServer _ = redeem + +redeem request = return Failed -- throwError jsonErr400 diff --git a/src/PaymentServer/Server.hs b/src/PaymentServer/Server.hs index f4ecaf3..bd51423 100644 --- a/src/PaymentServer/Server.hs +++ b/src/PaymentServer/Server.hs @@ -24,7 +24,9 @@ import PaymentServer.Persistence ) -- | This is the complete type of the server API. -type PaymentServerAPI = "v1" :> "stripe" :> StripeAPI +type PaymentServerAPI + = "v1" :> "stripe" :> StripeAPI + -- :<|> "v1" :> "redeem" :> RedeemAPI -- | Create a server which uses the given database. paymentServer :: VoucherDatabase d => d -> Server PaymentServerAPI diff --git a/test/SpecRedemption.hs b/test/SpecRedemption.hs new file mode 100644 index 0000000..241be04 --- /dev/null +++ b/test/SpecRedemption.hs @@ -0,0 +1,79 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- | Test suite related to voucher redemption. + +module SpecRedemption where + +import Text.Printf + ( printf + ) +import Control.Exception + ( bracket + ) +import Servant + ( Application + , Proxy(Proxy) + , serve + ) +import Test.Hspec + ( Spec + , parallel + , describe + , it + , before + , around + , shouldReturn + , shouldBe + ) +import Test.Hspec.Wai + ( with + , post + , shouldRespondWith + , liftIO + ) +import Test.Hspec.Wai.QuickCheck + ( property + ) +import Util.Spec + ( wrongMethodNotAllowed + , nonJSONUnsupportedMediaType + , wrongJSONInvalidRequest + ) +import PaymentServer.Redemption + ( RedemptionAPI + , redemptionServer + ) +import PaymentServer.Persistence + ( Voucher + , Fingerprint + , VoucherDatabase(payForVoucher, redeemVoucher) + , MemoryVoucherDatabase + , memory + ) + +redemptionAPI :: Proxy RedemptionAPI +redemptionAPI = Proxy + +app :: VoucherDatabase d => d -> Application +app = serve redemptionAPI . redemptionServer + +path = "/" + +spec_simple :: Spec +spec_simple = with (app <$> memory) $ parallel $ do + describe (printf "error behavior of POST %s" (show path)) $ do + wrongMethodNotAllowed "GET" path + nonJSONUnsupportedMediaType path + wrongJSONInvalidRequest path "{}" + +withConnection :: VoucherDatabase d => IO d -> ((d -> IO ()) -> IO ()) +withConnection getDB = bracket getDB (\db -> return ()) + +spec_db :: Spec +spec_db = do + around (withConnection memory) $ do + describe "redemptionServer" $ do + it "responds to redemption of an unpaid voucher with 400 (Invalid Request)" $ + \(db :: MemoryVoucherDatabase) -> do + payForVoucher db "abcdefg" -- GitLab