From de16b1a556e723da101bb56449c75ec7376d76d7 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Fri, 23 Aug 2019 15:13:40 -0400 Subject: [PATCH] Make gen of charges with vouchers more than vanishingly unlikely This does a better job covering good and bad cases in the implementation, though currently none of the values resulting are used so they're still not actually covered. --- test/SpecStripe.hs | 39 ++++++-------- test/Util/Gen.hs | 123 ++++++++++++++++++++++++++++----------------- 2 files changed, 90 insertions(+), 72 deletions(-) diff --git a/test/SpecStripe.hs b/test/SpecStripe.hs index 9c4f61f..d825bbe 100644 --- a/test/SpecStripe.hs +++ b/test/SpecStripe.hs @@ -35,23 +35,19 @@ import Test.QuickCheck ( Property , Gen , arbitrary - , suchThat - , suchThatMap , generate , forAll , (===) , (=/=) ) -import Test.QuickCheck.Instances.Tuple - ( (>*<) - ) import Util.WAI ( postJSON ) import Util.Gen - ( GoodChargeEvent(GoodChargeEvent) + ( ChargeEvents(GoodChargeEvent, BadChargeEvent) , chargeSucceededEvents - , hasVoucher + , metaDatasWithVoucher + , metaDatasWithoutVoucher ) import Util.JSON ( -- ToJSON instance for Event @@ -76,9 +72,6 @@ import PaymentServer.Persistence ( Voucher , memory ) -import Data.List.Index - ( insertAt - ) stripeAPI :: Proxy StripeAPI stripeAPI = Proxy @@ -102,26 +95,22 @@ spec_webhook = with app $ do -- convention of giving the same name to *similar* functions. describe "success behavior of POST /webhook" $ it "responds to a JSON Event body with 200 (OK)" $ - property $ - \(GoodChargeEvent event) -> - postJSON "/webhook" (encode event) `shouldRespondWith` 200 { matchBody = MatchBody bodyMatcher } + let + test e = + postJSON "/webhook" (encode e) `shouldRespondWith` 200 { matchBody = MatchBody bodyMatcher } + -- For now these are the same. Maybe they always will be? The HTTP + -- behavior is the same though the backend behavior may differ. + xtest_postWithEventBody (GoodChargeEvent e) = test e + xtest_postWithEventBody (BadChargeEvent e) = test e + in + property xtest_postWithEventBody + + bodyMatcher :: [Network.HTTP.Types.Header] -> Body -> Maybe String bodyMatcher _ "{}" = Nothing bodyMatcher _ body = Just $ show body -metaDatasWithoutVoucher = (arbitrary :: Gen MetaData) `suchThat` (not . hasVoucher) - --- Just filtering out random metadatas that don't have a voucher makes for an --- incredibly inefficient generator. So start without a voucher and then add --- one. -metaDatasWithVoucher = ((arbitrary :: Gen Voucher) >*< metaDatasWithoutVoucher) `suchThatMap` (Just. uncurry addVoucher) - -addVoucher :: Voucher -> MetaData -> MetaData -addVoucher voucher (MetaData []) = MetaData [("Voucher", voucher)] -addVoucher voucher (MetaData items) = - MetaData (insertAt (1234567 `mod` length items) ("Voucher", voucher) items) - prop_getVoucherFindsVoucher :: Property prop_getVoucherFindsVoucher = forAll metaDatasWithVoucher $ \x -> getVoucher x =/= Nothing diff --git a/test/Util/Gen.hs b/test/Util/Gen.hs index 863116c..e74de2b 100644 --- a/test/Util/Gen.hs +++ b/test/Util/Gen.hs @@ -2,24 +2,26 @@ module Util.Gen ( chargeSucceededEvents + , metaDatasWithoutVoucher + , metaDatasWithVoucher , posixTimes , hasVoucher - , GoodChargeEvent(GoodChargeEvent) + , ChargeEvents(GoodChargeEvent, BadChargeEvent) ) where import Data.Text ( Text ) - +import Data.List.Index + ( insertAt + ) import Data.Time.Clock ( UTCTime(UTCTime) ) - import Data.Time.Clock.POSIX ( POSIXTime , posixSecondsToUTCTime ) - import Web.Stripe.Types ( Charge(Charge) , StatementDescription(StatementDescription) @@ -33,60 +35,36 @@ import Web.Stripe.Types , Amount(Amount) , StripeList(StripeList, list, totalCount, hasMore) ) - import Web.Stripe.Event ( Event(Event, eventId, eventCreated, eventLiveMode, eventType, eventData, eventObject, eventPendingWebHooks, eventRequest) , EventType(ChargeSucceededEvent) , EventData(ChargeEvent) , EventId(EventId) ) - - import Test.QuickCheck ( Gen , Arbitrary , arbitrary , Positive(Positive) , oneof + , suchThat , suchThatMap ) - +import Test.QuickCheck.Instances.Tuple + ( (>*<) + ) import Test.QuickCheck.Instances.Time ( -- Get the `Gen UTCTime` instance ) import Test.QuickCheck.Instances.Text ( -- Get the `Gen Text` instance ) +import PaymentServer.Persistence + ( Voucher + ) instance Arbitrary Charge where - arbitrary = Charge - <$> arbitrary -- chargeId :: ChargeId - <*> return "charge" -- chargeObject :: Text - <*> posixTimes -- chargeCreated :: UTCTime - <*> arbitrary -- chargeLiveMode :: Bool - <*> arbitrary -- chargePaid :: Bool - <*> arbitrary -- chargeAmount :: Amount - <*> oneof - [ return UnknownCurrency - , return USD - ] -- chargeCurrency :: Currency - <*> return False -- chargeRefunded :: Bool - <*> return Nothing -- chargeCreditCard :: Maybe Card - <*> arbitrary -- chargeCaptured :: Bool - <*> return mempty -- chargeRefunds :: StripeList Refund - <*> return Nothing -- chargeBalanceTransaction :: Maybe (Expandable TransactionId) - <*> return Nothing -- chargeFailureMessage :: Maybe Text - <*> return Nothing -- chargeFailureCode :: Maybe Text - <*> return 0 -- chargeAmountRefunded :: Int - <*> arbitrary -- chargeCustomerId :: Maybe (Expandable CustomerId) - <*> return Nothing -- chargeInvoice :: Maybe (Expandable InvoiceId) - <*> arbitrary -- chargeDescription :: Maybe Description - <*> return Nothing -- chargeDispute :: Maybe Dispute - <*> arbitrary -- chargeMetaData :: MetaData - <*> arbitrary -- chargeStatementDescription :: Maybe StatementDescription - <*> arbitrary -- chargeReceiptEmail :: Maybe Text - <*> arbitrary -- chargeReceiptNumber :: Maybe Text - + arbitrary = charges True instance Semigroup (StripeList a) where -- Very weak implementation @@ -106,11 +84,20 @@ instance Arbitrary EventId where instance Arbitrary StatementDescription where arbitrary = StatementDescription <$> arbitrary +metaDatasWithoutVoucher = (arbitrary :: Gen MetaData) `suchThat` (not . hasVoucher) +-- Just filtering out random metadatas that don't have a voucher makes for an +-- incredibly inefficient generator. So start without a voucher and then add +-- one. +metaDatasWithVoucher = ((arbitrary :: Gen Voucher) >*< metaDatasWithoutVoucher) `suchThatMap` (Just. uncurry addVoucher) + +addVoucher :: Voucher -> MetaData -> MetaData +addVoucher voucher (MetaData []) = MetaData [("Voucher", voucher)] +addVoucher voucher (MetaData items) = + MetaData (insertAt (1234567 `mod` length items) ("Voucher", voucher) items) instance Arbitrary MetaData where arbitrary = MetaData <$> arbitrary - instance Arbitrary Description where arbitrary = Description <$> arbitrary @@ -130,26 +117,68 @@ instance Arbitrary CustomerId where instance Arbitrary a => Arbitrary (Expandable a) where arbitrary = Id <$> arbitrary -chargeSucceededEvents :: Gen Event -chargeSucceededEvents = +chargeSucceededEvents :: Bool -> Gen Event +chargeSucceededEvents withVoucher = Event <$> arbitrary -- eventId <*> posixTimes -- eventCreated <*> arbitrary -- eventLiveMode <*> return ChargeSucceededEvent -- eventType - <*> (ChargeEvent - <$> arbitrary -- the charge - ) -- eventData + <*> chargeEvents withVoucher -- eventData <*> return "event" -- eventObject <*> arbitrary -- eventPendingWebHooks <*> arbitrary -- eventRequest -data GoodChargeEvent = GoodChargeEvent Event deriving (Show, Eq) - -instance Arbitrary GoodChargeEvent where - arbitrary = chargeSucceededEvents `suchThatMap` (Just . GoodChargeEvent) - +chargeEvents :: Bool -> Gen EventData +chargeEvents withVoucher = + ChargeEvent <$> charges withVoucher + +charges :: Bool -> Gen Charge +charges withVoucher = + Charge + <$> arbitrary -- chargeId :: ChargeId + <*> return "charge" -- chargeObject :: Text + <*> posixTimes -- chargeCreated :: UTCTime + <*> arbitrary -- chargeLiveMode :: Bool + <*> arbitrary -- chargePaid :: Bool + <*> arbitrary -- chargeAmount :: Amount + <*> oneof + [ return UnknownCurrency + , return USD + ] -- chargeCurrency :: Currency + <*> return False -- chargeRefunded :: Bool + <*> return Nothing -- chargeCreditCard :: Maybe Card + <*> arbitrary -- chargeCaptured :: Bool + <*> return mempty -- chargeRefunds :: StripeList Refund + <*> return Nothing -- chargeBalanceTransaction :: Maybe (Expandable TransactionId) + <*> return Nothing -- chargeFailureMessage :: Maybe Text + <*> return Nothing -- chargeFailureCode :: Maybe Text + <*> return 0 -- chargeAmountRefunded :: Int + <*> arbitrary -- chargeCustomerId :: Maybe (Expandable CustomerId) + <*> return Nothing -- chargeInvoice :: Maybe (Expandable InvoiceId) + <*> arbitrary -- chargeDescription :: Maybe Description + <*> return Nothing -- chargeDispute :: Maybe Dispute + <*> ( + if withVoucher then + metaDatasWithVoucher + else + metaDatasWithVoucher + ) -- chargeMetaData :: MetaData + <*> arbitrary -- chargeStatementDescription :: Maybe StatementDescription + <*> arbitrary -- chargeReceiptEmail :: Maybe Text + <*> arbitrary -- chargeReceiptNumber :: Maybe Text + +data ChargeEvents + = GoodChargeEvent Event + | BadChargeEvent Event + deriving (Show, Eq) + +instance Arbitrary ChargeEvents where + arbitrary = oneof + [ chargeSucceededEvents True `suchThatMap` (Just . GoodChargeEvent) + , chargeSucceededEvents False `suchThatMap` (Just . BadChargeEvent) + ] posixTimes :: Gen UTCTime posixTimes = (arbitrary :: Gen Integer) `suchThatMap` (Just . posixSecondsToUTCTime . fromIntegral . abs) -- GitLab