Skip to content
Snippets Groups Projects
SpecRedemption.hs 4.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    
    -- | Test suite related to voucher redemption.
    
    module SpecRedemption where
    
    
    import Data.ByteString
      ( ByteString
      )
    
    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
    
      ( ResponseMatcher(ResponseMatcher)
      , WaiExpectation
      , with
    
      , shouldRespondWith
      , liftIO
      )
    import Test.Hspec.Wai.QuickCheck
      ( property
      )
    
    import Test.QuickCheck
    
      )
    import Test.QuickCheck.Instances.Text ()
    
    import Util.Spec
      ( wrongMethodNotAllowed
      , nonJSONUnsupportedMediaType
      , wrongJSONInvalidRequest
      )
    
    import Util.WAI
      ( postJSON
      )
    
    import PaymentServer.Redemption
      ( RedemptionAPI
    
      , redemptionServer
      )
    import PaymentServer.Persistence
    
      ( RedeemError(NotPaid)
      , Voucher
    
      , Fingerprint
      , VoucherDatabase(payForVoucher, redeemVoucher)
      , MemoryVoucherDatabase
      , memory
      )
    
    redemptionAPI :: Proxy RedemptionAPI
    redemptionAPI = Proxy
    
    app :: VoucherDatabase d => d -> Application
    app = serve redemptionAPI . redemptionServer
    
    path = "/"
    
    
    propertyRedeem :: ByteString -> Voucher -> [BlindedToken] -> ResponseMatcher -> WaiExpectation
    propertyRedeem path voucher tokens matcher =
      postJSON path (encode $ Redeem voucher tokens) `shouldRespondWith` matcher
    
    -- | A VoucherDatabaseTestDouble has a VoucherDatabase instance which provides
    -- a number of different behaviors which are useful to be able to directly
    -- test against.
    data VoucherDatabaseTestDouble
      -- | A RefuseRedemption database always refuses redemption with a given error.
      = RefuseRedemption RedeemError
      -- | A PermitRedemption database always permits redemption.
      | PermitRedemption
      deriving (Show)
    
    instance VoucherDatabase VoucherDatabaseTestDouble where
      payForVoucher _ voucher = return ()
      redeemVoucher (RefuseRedemption err) _ _ = return $ Left err
      redeemVoucher PermitRedemption _ _ = return $ Right ()
    
    spec_redemption :: Spec
    spec_redemption = parallel $ do
      database <- runIO memory
      with (return . app $ database) $
        do
          describe (printf "error behavior of POST %s" (show path)) $
            do
              wrongMethodNotAllowed "GET" path
              nonJSONUnsupportedMediaType path
              wrongJSONInvalidRequest path "{}"
    
          -- I would rather write these two as property tests but I don't know
          -- how.
          describe "double redemption" $ do
            it "succeeds with the same tokens" $ do
              let voucher = "abc" :: Voucher
              let tokens = [ "def", "ghi" ] :: [BlindedToken]
              liftIO $ payForVoucher database voucher
              propertyRedeem path voucher tokens 200
              propertyRedeem path voucher tokens 200
    
            it "fails with different tokens" $ do
              let voucher = "jkl" :: Voucher
              let firstTokens = [ "mno", "pqr" ] :: [BlindedToken]
              let secondTokens = [ "stu", "vwx" ] :: [BlindedToken]
              liftIO $ payForVoucher database voucher
              propertyRedeem path voucher firstTokens 200
              propertyRedeem path voucher secondTokens 400
    
    
      describe "redemption" $ do
        with (return . app $ RefuseRedemption NotPaid) $
          it "receives 400 (Invalid Request) when the voucher is not paid" $ property $
          \(voucher :: Voucher) (tokens :: [BlindedToken]) ->
            propertyRedeem path voucher tokens 400
    
        with (return $ app PermitRedemption) $
          it "receive 200 (OK) when redemption succeeds" $ property $
          \(voucher :: Voucher) (tokens :: [BlindedToken]) ->
            propertyRedeem path voucher tokens 200
    
        -- it "receive 200 (OK) when the voucher is paid and previously redeemed with the same tokens" $
        --   property $ \(voucher :: Voucher) (tokens :: [BlindedToken]) ->
        --   do
        --     liftIO $ payForVoucher database voucher
        --     postJSON path (encode $ Redeem voucher tokens) `shouldRespondWith` 200
        --     postJSON path (encode $ Redeem voucher tokens) `shouldRespondWith` 200
    
        -- it "receive 400 (OK) when the voucher is paid and previously redeemed with different tokens" $
        --   property $ \(voucher :: Voucher) (firstTokens :: [BlindedToken]) (secondTokens :: [BlindedToken]) ->
        --   do
        --     liftIO $ payForVoucher database voucher
        --     postJSON path (encode $ Redeem voucher firstTokens) `shouldRespondWith` 200
        --     postJSON path (encode $ Redeem voucher secondTokens) `shouldRespondWith` 400