curl-runnings 0.16.4 → 0.17.0
raw patch · 9 files changed
+122/−105 lines, 9 filesdep −timedep ~aesonPVP ok
version bump matches the API change (PVP)
Dependencies removed: time
Dependency ranges changed: aeson
API changes (from Hackage documentation)
+ Testing.CurlRunnings.Internal.Aeson: findWithDefault :: a -> KeyType -> MapType a -> a
+ Testing.CurlRunnings.Internal.Aeson: fromText :: Text -> Key
+ Testing.CurlRunnings.Internal.Aeson: toText :: Key -> Text
+ Testing.CurlRunnings.Internal.Aeson: type KeyType = Key
+ Testing.CurlRunnings.Internal.Aeson: type MapType v = KeyMap v
+ Testing.CurlRunnings.Internal.KeyValuePairs: instance GHC.Classes.Eq Testing.CurlRunnings.Internal.KeyValuePairs.KeyValuePair
+ Testing.CurlRunnings.Internal.KeyValuePairs: instance GHC.Classes.Eq Testing.CurlRunnings.Internal.KeyValuePairs.KeyValuePairs
- Testing.CurlRunnings.Internal.KeyValuePairs: KeyValuePair :: Text -> Text -> KeyValuePair
+ Testing.CurlRunnings.Internal.KeyValuePairs: KeyValuePair :: KeyType -> Text -> KeyValuePair
- Testing.CurlRunnings.Types: KeyValuePair :: Text -> Text -> KeyValuePair
+ Testing.CurlRunnings.Types: KeyValuePair :: KeyType -> Text -> KeyValuePair
Files
- curl-runnings.cabal +6/−8
- src/Testing/CurlRunnings.hs +36/−39
- src/Testing/CurlRunnings/Internal.hs +0/−6
- src/Testing/CurlRunnings/Internal/Aeson.hs +41/−0
- src/Testing/CurlRunnings/Internal/KeyValuePairs.hs +13/−5
- src/Testing/CurlRunnings/Internal/Parser.hs +3/−3
- src/Testing/CurlRunnings/Internal/Payload.hs +7/−7
- src/Testing/CurlRunnings/Types.hs +6/−20
- test/Spec.hs +10/−17
curl-runnings.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 3c27906a20aba4f4ab885acf4616178457bdbd82ce1dcd57942ff7e06a0bba25+-- hash: f7b42ddbfbcd70bee034e064181b72a998e326a895efdf2c524583c4f47131ca name: curl-runnings-version: 0.16.4+version: 0.17.0 synopsis: A framework for declaratively writing curl based API tests description: Please see the README on Github at <https://github.com/aviaviavi/curl-runnings#readme> category: Testing@@ -37,6 +37,7 @@ Testing.CurlRunnings Testing.CurlRunnings.Types Testing.CurlRunnings.Internal+ Testing.CurlRunnings.Internal.Aeson Testing.CurlRunnings.Internal.Parser Testing.CurlRunnings.Internal.Headers Testing.CurlRunnings.Internal.KeyValuePairs@@ -46,7 +47,7 @@ hs-source-dirs: src build-depends:- aeson >=1.2.4.0 && <2.0+ aeson >=1.2.4.0 , base >=4.0 && <5 , base64-bytestring >=1.0.0.2 , bytestring >=0.10.8.2@@ -57,8 +58,6 @@ , dhall-json >=1.0.9 , directory >=1.3.0.2 , hashable >=1.2.7.0- , hspec >=2.4.4- , hspec-expectations >=0.8.2 , http-client-tls >=0.3.5.3 , http-conduit >=2.3.6 , http-types >=0.12.3@@ -66,7 +65,6 @@ , pretty-simple >=2.0.2.1 , regex-posix >=0.95.2 , text >=1.2.2.2- , time >=1.8.0.2 , transformers >=0.5.2.0 , unordered-containers >=0.2.8.0 , vector >=0.12.0@@ -81,7 +79,7 @@ app ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends:- aeson >=1.2.4.0 && <2.0+ aeson >=1.2.4.0 , base >=4.7 , bytestring >=0.10.8.2 , cmdargs >=0.10.20@@ -102,7 +100,7 @@ test ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends:- aeson >=1.2.4.0 && <2.0+ aeson >=1.2.4.0 , base >=4.0 && <5 , bytestring >=0.10.8.2 , curl-runnings
src/Testing/CurlRunnings.hs view
@@ -15,7 +15,6 @@ import Control.Arrow import Control.Exception-import qualified Control.Exception import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Except@@ -26,19 +25,13 @@ import qualified Data.ByteString.Lazy as B import qualified Data.CaseInsensitive as CI import Data.Either-import qualified Data.HashMap.Strict as H-import Data.List+import Data.List (find) import Data.Maybe-import Data.Monoid+import Data.String (fromString) import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.IO as TIO-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.IO as TLIO-import Data.Time.Clock import qualified Data.Vector as V-import qualified Data.Vector as V-import qualified Data.Yaml as Y import qualified Data.Yaml.Include as YI import qualified Dhall import qualified Dhall.Import@@ -46,7 +39,6 @@ import qualified Dhall.Parser import qualified Dhall.TypeCheck import Network.Connection (TLSSettings (..))-import Network.HTTP.Client.TLS (mkManagerSettings) import Network.HTTP.Conduit import Network.HTTP.Simple hiding (Header) import qualified Network.HTTP.Simple as HTTP@@ -54,6 +46,7 @@ import System.Directory import System.Environment import Testing.CurlRunnings.Internal+import qualified Testing.CurlRunnings.Internal.Aeson as A import Testing.CurlRunnings.Internal.Parser import Testing.CurlRunnings.Types import Text.Printf@@ -110,7 +103,7 @@ appendQueryParameters :: [KeyValuePair] -> Request -> Request appendQueryParameters newParams r = setQueryString (existing ++ newQuery) r where existing = NT.parseQuery $ queryString r- newQuery = NT.simpleQueryToQuery $ fmap (\(KeyValuePair k v) -> (T.encodeUtf8 k, T.encodeUtf8 v)) newParams+ newQuery = NT.simpleQueryToQuery $ fmap (\(KeyValuePair k v) -> (T.encodeUtf8 . A.toText $ k, T.encodeUtf8 v)) newParams setPayload :: Maybe Payload -> Request -> Request -- TODO - for backwards compatability, empty requests will set an empty json@@ -121,7 +114,7 @@ setPayload Nothing = setRequestBodyJSON emptyObject setPayload (Just (JSON v)) = setRequestBodyJSON v setPayload (Just (URLEncoded (KeyValuePairs xs))) = setRequestBodyURLEncoded $ kvpairs xs where- kvpairs = fmap (\(KeyValuePair k v) -> (T.encodeUtf8 k, T.encodeUtf8 v))+ kvpairs = fmap (\(KeyValuePair k v) -> (T.encodeUtf8 . A.toText $ k, T.encodeUtf8 v)) -- | Run a single test case, and returns the result. IO is needed here since this method is responsible -- for actually curling the test case endpoint and parsing the result.@@ -249,15 +242,17 @@ (Just (BasicAuthentication u p)) -> let interpolated = sequence [interpolateQueryString state u, interpolateQueryString state p] in case interpolated of- Right [u, p] -> Right [Header "Authorization" ("Basic " <> (makeBasicAuthToken u p))]+ Right [u', p'] -> Right [Header "Authorization" ("Basic " <> (makeBasicAuthToken u' p'))] Left l -> Left l- Nothing -> Right []+ _ -> Left $ QueryValidationError "FIXME Pattern match error in interpolatingHeaders"+ _ -> Right [] mainHeaders <- (mapM (\(Header k v) -> case sequence [interpolateQueryString state k, interpolateQueryString state v] of Left err -> Left err- Right [k', v'] -> Right $ Header k' v')+ Right [k', v'] -> Right $ Header k' v'+ _ -> Left $ QueryValidationError "FIXME Pattern match error in interpolatingHeaders") headerList) Right . HeaderSet $ mainHeaders <> authHeaders @@ -282,7 +277,8 @@ runSuite :: CurlSuite -> LogLevel -> TLSCheckType -> IO [CaseResult] runSuite (CurlSuite cases filterRegex) logLevel tlsType = do fullEnv <- getEnvironment- let envMap = H.fromList $ map (\(x, y) -> (T.pack x, T.pack y)) fullEnv+ let envMap :: A.MapType T.Text+ envMap = A.fromList $ map (\(x, y) -> (fromString x :: A.KeyType, T.pack y)) fullEnv :: A.MapType T.Text filterNameByRegexp curlCase = maybe True@@ -389,19 +385,19 @@ -- | runReplacements runReplacements :: CurlRunningsState -> Value -> Either QueryError Value runReplacements state (Object o) =- let keys = H.keys o+ let keys = A.keys o keysWithUpdatedKeyVal = map (\key ->- let value = fromJust $ H.lookup key o+ let value = fromJust $ A.lookup key o -- (old key, new key, new value) in ( key- , interpolateQueryString state key+ , interpolateQueryString state (A.toText key) , runReplacements state value)) keys in mapRight Object $ foldr- (\((key, eKeyResult, eValueResult) :: ( T.Text+ (\((key, eKeyResult, eValueResult) :: ( A.KeyType , Either QueryError T.Text , Either QueryError Value)) (eObjectToUpdate :: Either QueryError Object) -> case (eKeyResult, eValueResult, eObjectToUpdate)@@ -412,11 +408,12 @@ (_, Left queryErr, _) -> Left queryErr (_, _, Left queryErr) -> Left queryErr (Right newKey, Right newValue, Right objectToUpdate) ->- if key /= newKey- then let inserted = H.insert newKey newValue objectToUpdate- deleted = H.delete key inserted+ let newKey' = A.fromText newKey+ in if key /= newKey'+ then let inserted = A.insert newKey' newValue objectToUpdate+ deleted = A.delete key inserted in Right deleted- else Right $ H.insert key newValue objectToUpdate)+ else Right $ A.insert key newValue objectToUpdate) (Right o) keysWithUpdatedKeyVal runReplacements p (Array a) =@@ -442,9 +439,9 @@ interpolateViaJSON :: (ToJSON a, FromJSON a) => CurlRunningsState -> a -> Either QueryError a interpolateViaJSON state i = do replaced <- runReplacements state $ toJSON i- resultToEither $ fromJSON replaced where- resultToEither (Error e) = Left $ QueryValidationError $ T.pack e- resultToEither (Success a) = Right a+ resultToEither' $ fromJSON replaced where+ resultToEither' (Error e) = Left $ QueryValidationError $ T.pack e+ resultToEither' (Success a) = Right a -- | Given a query string, return some text with interpolated values. Type -- errors will be returned if queries don't resolve to strings@@ -474,7 +471,7 @@ Right (String s) -> Right $ rawText <> s (Right o) -> Left $ QueryTypeMismatch "Expected a string" o getStringValueForQuery (CurlRunningsState env _ _ _) (InterpolatedQuery rawText (EnvironmentVariable v)) =- Right $ rawText <> H.lookupDefault "" v env+ Right $ rawText <> A.findWithDefault "" (A.fromText v) env -- | Lookup the value for the specified query getValueForQuery ::@@ -499,7 +496,7 @@ case (eitherVal, index) of (Left l, _) -> Left l (Right (Object o), KeyIndex k) ->- Right $ H.lookupDefault Null k o+ Right $ A.findWithDefault Null (A.fromText k :: A.KeyType) o (Right (Array a), ArrayIndex i') -> maybe (Left $@@ -524,7 +521,7 @@ "'$< ... >' queries must start with a RESPONSES[index] query: " ++ show full getValueForQuery (CurlRunningsState env _ _ _) (NonInterpolatedQuery (EnvironmentVariable var)) =- Right . String $ H.lookupDefault "" var env+ Right . String $ A.findWithDefault "" (A.fromText var) env getValueForQuery state (InterpolatedQuery _ q) = case getValueForQuery state (NonInterpolatedQuery q) of Right (String s) -> Right . String $ s@@ -539,12 +536,12 @@ -> Bool jsonContains f jsonValue = let traversedValue = traverseValue jsonValue- in f $ \match ->- case match of+ in f $ \match' ->+ case match' of ValueMatch subval -> subval `elem` traversedValue- KeyMatch key -> any (`containsKey` key) traversedValue+ KeyMatch key -> any (`containsKey` (A.fromText key)) traversedValue KeyValueMatch key subval ->- any (\o -> containsKeyVal o key subval) traversedValue+ any (\o -> containsKeyVal o (A.fromText key) subval) traversedValue -- | Does the json value contain all of these sub-values? jsonContainsAll :: Value -> [JsonSubExpr] -> Bool@@ -555,24 +552,24 @@ jsonContainsAny = jsonContains any -- | Does the json value contain the given key value pair?-containsKeyVal :: Value -> T.Text -> Value -> Bool+containsKeyVal :: Value -> A.KeyType -> Value -> Bool containsKeyVal jsonValue key val = case jsonValue of- Object o -> H.lookup key o == Just val+ Object o -> A.lookup key o == Just val _ -> False -- | Does the json value contain the given key value pair?-containsKey :: Value -> T.Text -> Bool+containsKey :: Value -> A.KeyType -> Bool containsKey jsonValue key = case jsonValue of- Object o -> isJust $ H.lookup key o+ Object o -> isJust $ A.lookup key o _ -> False -- | Fully traverse the json and return a list of all the values traverseValue :: Value -> [Value] traverseValue val = case val of- Object o -> val : concatMap traverseValue (H.elems o)+ Object o -> val : concatMap traverseValue (A.elems o) Array o -> val : concatMap traverseValue o n@(Number _) -> [n] s@(String _) -> [s]
src/Testing/CurlRunnings/Internal.hs view
@@ -20,15 +20,12 @@ ) where import Control.Monad-import Data.Monoid import qualified Data.Text as T import qualified Data.Text.Lazy as TL import Debug.Trace import System.Clock import qualified Text.Pretty.Simple as P-import Text.Printf - makeGreen :: T.Text -> T.Text makeGreen s = "\x1B[32m" <> s <> "\x1B[0m" @@ -83,9 +80,6 @@ nowMillis = do t <- getTime Realtime return $ (toNanoSecs t) `div` 1000000--roundToStr :: (PrintfArg a, Floating a) => a -> String-roundToStr = printf "%0.2f" millisToS :: Integer -> Double millisToS t = (fromIntegral t :: Double) / 1000.0
+ src/Testing/CurlRunnings/Internal/Aeson.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE CPP #-}++-- | Module for the purpose of Aeson compatibility+module Testing.CurlRunnings.Internal.Aeson+ ( module Map+ , KeyType+ , MapType+ , findWithDefault+ , fromText+ , toText+ ) where++#if MIN_VERSION_aeson(2,0,0)++import Data.Aeson.KeyMap as Map+import Data.Aeson.Key (Key, fromText, toText)++type MapType v = Map.KeyMap v+type KeyType = Key++#else++import Data.Text (Text)+import Data.HashMap.Strict as Map hiding (findWithDefault)++type MapType v = Map.HashMap Text v+type KeyType = Text++fromText :: Text -> Text+fromText = id++toText :: Text -> Text+toText = id++#endif++findWithDefault :: a -> KeyType -> MapType a -> a+findWithDefault def k kv = case Map.lookup k kv of+ Just v -> v+ _ -> def+{-# INLINABLE findWithDefault #-}
src/Testing/CurlRunnings/Internal/KeyValuePairs.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StandaloneDeriving #-} -- | A module defining the KeyValuePairs type. This type may be used to -- represent a structure in a specification that is a collection of@@ -17,24 +18,31 @@ import Data.Aeson import Data.Aeson.Types import qualified Data.ByteString.Lazy as LBS-import Data.HashMap.Strict as H+import Data.List ((\\)) import qualified Data.Text as T import Data.Text.Encoding as T+import qualified Testing.CurlRunnings.Internal.Aeson as A -- | A container for a list of key-value pairs newtype KeyValuePairs = KeyValuePairs [KeyValuePair] deriving Show -- | A representation of a single key-value pair-data KeyValuePair = KeyValuePair T.Text T.Text deriving Show+data KeyValuePair = KeyValuePair A.KeyType T.Text deriving Show +deriving instance Eq KeyValuePair++-- KeyValuePairs should be considered equal if they contain the same elements.+instance Eq KeyValuePairs where+ (==) (KeyValuePairs x) (KeyValuePairs y) = null (x \\ y) && null (y \\ x)+ instance ToJSON KeyValuePairs where toJSON (KeyValuePairs qs) = object (fmap (\(KeyValuePair k v) -> k .= toJSON v) qs) instance FromJSON KeyValuePairs where parseJSON = withObject "keyValuePairs" parseKeyValuePairs where- parseKeyValuePairs o = KeyValuePairs <$> traverse parseKeyValuePair (H.toList o)- parseKeyValuePair (t, v) = KeyValuePair t <$> parseSingleValueType v+ parseKeyValuePairs o = KeyValuePairs <$> traverse parseKeyValuePair (A.toList o)+ parseKeyValuePair (k, v) = KeyValuePair k <$> parseSingleValueType v parseSingleValueType :: Value -> Parser T.Text parseSingleValueType (Bool b) = parseToText b
src/Testing/CurlRunnings/Internal/Parser.hs view
@@ -34,9 +34,9 @@ parseQuery ) where -import Data.Bifunctor (Bifunctor (..))-import Data.Char (isAscii)-import Data.List+import Data.Bifunctor ()+import Data.Char ()+import Data.List (isInfixOf) import qualified Data.Text as T import Data.Void import Testing.CurlRunnings.Types
src/Testing/CurlRunnings/Internal/Payload.hs view
@@ -53,28 +53,28 @@ import Data.Aeson import qualified Data.Char as C-import qualified Data.HashMap.Strict as H import qualified Data.Text as T import GHC.Generics+import qualified Testing.CurlRunnings.Internal.Aeson as A import Testing.CurlRunnings.Internal.KeyValuePairs data Payload = JSON Value | URLEncoded KeyValuePairs deriving Generic instance Show Payload where show (JSON v) = show v- show (URLEncoded (KeyValuePairs xs)) = T.unpack $ T.intercalate "&" $ fmap (\(KeyValuePair k v) -> k <> "=" <> v) xs+ show (URLEncoded (KeyValuePairs xs)) = T.unpack $ T.intercalate "&" $ fmap (\(KeyValuePair k v) -> A.toText k <> "=" <> v) xs -payloadTagFieldName :: T.Text+payloadTagFieldName :: A.KeyType payloadTagFieldName = "bodyType" -payloadContentsFieldName :: T.Text+payloadContentsFieldName :: A.KeyType payloadContentsFieldName = "content" instance FromJSON Payload where parseJSON v = withObject "payload" parsePayload v where- parsePayload o = if not (H.member payloadTagFieldName o) then return (JSON v) else genericParseJSON payloadOptions v- payloadOptions = defaultOptions { sumEncoding = TaggedObject { tagFieldName = T.unpack payloadTagFieldName- , contentsFieldName = T.unpack payloadContentsFieldName+ parsePayload o = if not (A.member payloadTagFieldName o) then return (JSON v) else genericParseJSON payloadOptions v+ payloadOptions = defaultOptions { sumEncoding = TaggedObject { tagFieldName = T.unpack . A.toText $ payloadTagFieldName+ , contentsFieldName = T.unpack . A.toText $ payloadContentsFieldName } , constructorTagModifier = fmap C.toLower }
src/Testing/CurlRunnings/Types.hs view
@@ -40,15 +40,12 @@ import Data.Aeson import Data.Aeson.Types-import qualified Data.Char as C-import Data.Hashable (Hashable)-import qualified Data.HashMap.Strict as H import Data.Maybe-import Data.Monoid import qualified Data.Text as T import qualified Data.Vector as V import GHC.Generics import Testing.CurlRunnings.Internal+import qualified Testing.CurlRunnings.Internal.Aeson as A import Testing.CurlRunnings.Internal.Headers import Testing.CurlRunnings.Internal.KeyValuePairs import Testing.CurlRunnings.Internal.Payload@@ -92,9 +89,9 @@ | justAndNotEmpty "notContains" v = NotContains <$> v .: "notContains" parseJSON invalid = typeMismatch "JsonMatcher" invalid -justAndNotEmpty :: (Eq k, Hashable k) => k -> H.HashMap k Value -> Bool+justAndNotEmpty :: A.KeyType -> A.MapType Value -> Bool justAndNotEmpty key obj =- (isJust $ H.lookup key obj) && (H.lookup key obj /= Just Null)+ isJust (A.lookup key obj) && A.lookup key obj /= Just Null -- | Simple predicate to check value constructor type isContains :: JsonMatcher -> Bool@@ -177,12 +174,12 @@ instance FromJSON JsonSubExpr where parseJSON (Object v) | justAndNotEmpty "keyValueMatch" v =- let toParse = fromJust $ H.lookup "keyValueMatch" v+ let toParse = fromJust $ A.lookup "keyValueMatch" v in case toParse of Object o -> KeyValueMatch <$> o .: "key" <*> o .: "value" _ -> typeMismatch "JsonSubExpr" toParse | justAndNotEmpty "keyMatch" v =- let toParse = fromJust $ H.lookup "keyMatch" v+ let toParse = fromJust $ A.lookup "keyMatch" v in case toParse of String s -> return $ KeyMatch s _ -> typeMismatch "JsonSubExpr" toParse@@ -403,7 +400,7 @@ isFailing = not . isPassing -- | A map of the system environment-type Environment = H.HashMap T.Text T.Text+type Environment = A.MapType T.Text data TLSCheckType = SkipTLSCheck | DoTLSCheck deriving (Show, Eq) @@ -428,11 +425,6 @@ | ArrayIndex Integer deriving (Show) -printOriginalQuery :: Index -> String-printOriginalQuery (CaseResultIndex t) = "RESPONSES[" ++ show t ++ "]"-printOriginalQuery (KeyIndex key) = "." ++ T.unpack key-printOriginalQuery (ArrayIndex i) = printf "[%d]" i- -- | A single entity to be queries from a json value data Query = -- | A single query contains a list of discrete index operations@@ -451,12 +443,6 @@ -- | Just a query, no leading text | NonInterpolatedQuery Query deriving (Show)--printQueryString :: InterpolatedQuery -> String-printQueryString (LiteralText t) = show t-printQueryString (InterpolatedQuery raw (Query indexes)) =- printf "%s$<%s>" raw $ concatMap show indexes-printQueryString (NonInterpolatedQuery (Query indexes)) = printf "$<%s>" (concatMap show indexes) -- | The full string in which a query appears, eg "prefix-${{RESPONSES[0].key.another_key[0].last_key}}" type FullQueryText = T.Text
test/Spec.hs view
@@ -1,18 +1,17 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE StandaloneDeriving #-} module Main where import Data.Aeson import Data.ByteString (ByteString) import Data.Either-import Data.List import qualified Data.Text as T import System.Directory import Test.Hspec import Testing.CurlRunnings import Testing.CurlRunnings.Internal+import qualified Testing.CurlRunnings.Internal.Aeson as A import Testing.CurlRunnings.Internal.Headers import Testing.CurlRunnings.Internal.KeyValuePairs import Testing.CurlRunnings.Internal.Parser@@ -92,12 +91,12 @@ it "arrayGet should handle positive and negative indexes correctly" $ do let a = [1, 2, 3] b = [] :: [Int]- (arrayGet a 0) `shouldBe` Just 1- (arrayGet a 1) `shouldBe` Just 2- (arrayGet a 2) `shouldBe` Just 3- (arrayGet a (-1)) `shouldBe` Just 3- (arrayGet a (-2)) `shouldBe` Just 2- (arrayGet a (-3)) `shouldBe` Just 1+ (arrayGet a 0) `shouldBe` Just (1 :: Integer)+ (arrayGet a 1) `shouldBe` Just (2 :: Integer)+ (arrayGet a 2) `shouldBe` Just (3 :: Integer)+ (arrayGet a (-1)) `shouldBe` Just (3 :: Integer)+ (arrayGet a (-2)) `shouldBe` Just (2 :: Integer)+ (arrayGet a (-3)) `shouldBe` Just (1 :: Integer) (arrayGet a (-4)) `shouldBe` Nothing (arrayGet a 3) `shouldBe` Nothing (arrayGet b 0) `shouldBe` Nothing@@ -156,16 +155,10 @@ let expectedHeaders = Right . HeaderSet $ uncurry Header <$> expected in actual `shouldBe` expectedHeaders -shouldBeKeyValuePairs :: ByteString -> [(T.Text, T.Text)] -> Expectation-shouldBeKeyValuePairs json expected = actual `shouldBe` expectedKeyValuePairs where- actual = eitherDecodeStrict json+shouldBeKeyValuePairs :: ByteString -> [(A.KeyType, T.Text)] -> Expectation+shouldBeKeyValuePairs j expected = actual `shouldBe` expectedKeyValuePairs where+ actual = eitherDecodeStrict j expectedKeyValuePairs = Right . KeyValuePairs $ uncurry KeyValuePair <$> expected--deriving instance Eq KeyValuePair---- KeyValuePairs should be considered equal if they contain the same elements.-instance Eq KeyValuePairs where- (==) (KeyValuePairs x) (KeyValuePairs y) = null (x \\ y) && null (y \\ x) isURLEncoded :: Either String Payload -> Bool isURLEncoded (Right (URLEncoded _)) = True