amazonka-core-2.0: test/Test/Amazonka/Sign/V2Header.hs
-- |
-- Module : Test.Amazonka.Sign.V2Header.BaseSpec
-- Stability : provisional
-- Portability : non-portable (GHC extensions)
module Test.Amazonka.Sign.V2Header (tests) where
import Amazonka.Data hiding (length)
import Amazonka.Prelude
import Amazonka.Sign.V2Header
import qualified Data.ByteString.Char8 as BS8
import qualified Data.CaseInsensitive as CI
import qualified Data.List as List
import Data.List.Ordered (subset)
import qualified Data.Text as Text
import qualified Network.HTTP.Types as HTTP
import qualified Test.QuickCheck as QC
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, (@?=))
import Test.Tasty.QuickCheck (Gen, Property, testProperty)
tests :: TestTree
tests =
testGroup
"v2Header.BaseSpec"
[ testGroup
"constructSigningQuery"
[ testProperty
"should always convert set QValues to Nothing"
prop_QValueEmpty,
testCase "should keep an unset QValue" $
constructSigningQuery (QValue (Nothing :: Maybe ByteString)) @?= QValue (Nothing :: Maybe ByteString),
testProperty
"should discard QPairs that are not interesting to AWS"
prop_UninterestingQPairs,
testProperty
"should keep all QPairs that are interesting to AWS"
prop_InterestingQPairs,
testCase "should keep an empty QList" $
constructSigningQuery (QList []) @?= QList [],
testCase "should keep a list of Nothing QValues" $
constructSigningQuery (QList [QValue Nothing]) @?= QList [QValue Nothing],
testProperty
"should discard the contents of an unintersting QList"
prop_UninterestingQLists,
testProperty
"should not discard all QC.elements in interesting QLists"
prop_InterestingQLists
],
testGroup
"constructSigningHeader"
[ testProperty
"should convert random headers to their header value"
prop_RandomHeaders,
testProperty
"should convert interesting headers to their header value"
prop_InterestingHeaders,
testProperty
"should convert aws headers to a canonical string"
prop_InterestingAwsHeaders
],
testGroup
"auxiliary headers functions"
[ testProperty
"should sort and preserve headers"
prop_SortedHeaders,
testCase "should contain empty md5 and empty content type headers if not present" $
[(HTTP.hContentMD5, ""), (HTTP.hContentType, "")] `subset` unionNecessaryHeaders [] @?= True,
testCase "should preserve a set md5 and contain an empty content type header if not present" $
[(HTTP.hContentMD5, "123"), (HTTP.hContentType, "")] `subset` unionNecessaryHeaders [(HTTP.hContentMD5, "123")] @?= True,
testCase "should preserve a set content type and preserve an empty md5 header if not present" $
[(HTTP.hContentType, "123"), (HTTP.hContentMD5, "")] `subset` unionNecessaryHeaders [(HTTP.hContentType, "123")] @?= True,
testCase "should preserve md5 and content type headers if set" $
[(HTTP.hContentType, "123"), (HTTP.hContentMD5, "456")] `subset` unionNecessaryHeaders [(HTTP.hContentType, "123"), (HTTP.hContentMD5, "456")] @?= True
],
testGroup
"toSingerQBS"
[ testCase "should convert an empty query string" $
toSignerQueryBS (QValue Nothing) @?= "",
testCase "should convert an empty value of QPair to just the key" $
toSignerQueryBS (QPair "key" (QValue Nothing)) @?= "key",
testCase "should convert an empty value of QPair followed by QValue to just the key and just the value" $
toSignerQueryBS
(QList [QPair "key" (QValue Nothing), QValue $ Just "key2"])
@?= "key&key2"
],
testGroup
"constructFullPath"
[ testCase "should convert an empty queryString to just the path" $
constructFullPath "path" "" @?= "path",
testCase "should convert an empty value of QPair and a path to just the path and the key" $
constructFullPath "path" (toSignerQueryBS (QPair "key" (QValue Nothing))) @?= "path?key"
],
testGroup
"should construct canonical headers"
[ testCase "should construct a canonical header from a base string" $
newSigner [] "GET" "\\" "" @?= "GET\n\n\n\\"
]
]
-- Properties for non-empty QValues
prop_QValueEmpty :: Property
prop_QValueEmpty =
QC.forAll uninterestingQValues $ \qv ->
constructSigningQuery qv
== QValue (Nothing :: Maybe ByteString)
-- Generators / properties for uninteresting query pairs
prop_UninterestingQPairs :: Property
prop_UninterestingQPairs =
QC.forAll uninterestingQPairs $ \qpair ->
constructSigningQuery qpair == QValue Nothing
uninterestingQPairs :: Gen QueryString
uninterestingQPairs =
toQuery <$> ((,) <$> nonEmptyText <*> uninterestingQValues)
-- Generators / properties for interesting query pairs
prop_InterestingQPairs :: Property
prop_InterestingQPairs =
QC.forAll interestingQPairs $ \qpair ->
constructSigningQuery qpair /= QValue Nothing
interestingQueryKey :: Gen ByteString
interestingQueryKey =
QC.elements
[ "acl",
"cors",
"defaultObjectAcl",
"location",
"logging",
"partNumber",
"policy",
"requestPayment",
"torrent",
"versioning",
"versionId",
"versions",
"website",
"uploads",
"uploadId",
"response-content-type",
"response-content-language",
"response-expires",
"response-cache-control",
"response-content-disposition",
"response-content-encoding",
"delete",
"lifecycle",
"tagging",
"restore",
"storageClass",
"websiteConfig",
"compose"
]
interestingQPairs :: Gen QueryString
interestingQPairs =
toQuery <$> ((,) <$> interestingQueryKey <*> uninterestingQValues)
-- Generators / properties for query lists
prop_UninterestingQLists :: Property
prop_UninterestingQLists =
QC.forAll uninterestingQLists $
not . containsAnything . constructSigningQuery
uninterestingQLists :: Gen QueryString
uninterestingQLists =
toQueryList
<$> nonEmptyByteString
<*> QC.listOf
( QC.frequency
[ (1, uninterestingQPairs),
(1, uninterestingQValues),
(1, uninterestingQLists)
]
)
containsAnything :: QueryString -> Bool
containsAnything = \case
QValue v -> isJust v
QPair {} -> True
QList qs -> any containsAnything qs
interestingQLists :: Gen QueryString
interestingQLists =
toQueryList
<$> interestingQueryKey
<*> QC.listOf
( QC.frequency
[ (1, interestingQLists),
(1, uninterestingQPairs),
(1, uninterestingQValues),
(1, uninterestingQLists)
]
)
-- this property should probably be expanded to check that all qpairs actually contain interesting query keys AND that uninteresting
-- query keys are actually preserved underneath an interesting one
prop_InterestingQLists :: Property
prop_InterestingQLists =
QC.forAll interestingQLists $
containsAnything . constructSigningQuery
prop_RandomHeaders :: Property
prop_RandomHeaders =
QC.forAll randomHeaderGenerator $ \hs ->
constructSigningHeader hs == snd hs
prop_InterestingHeaders :: Property
prop_InterestingHeaders =
QC.forAll interestingHeaderGenerator $ \hs ->
constructSigningHeader hs == snd hs
prop_InterestingAwsHeaders :: Property
prop_InterestingAwsHeaders =
QC.forAll interestingAwsHeaderGenerator $ \hs ->
constructSigningHeader hs
== (CI.foldedCase (fst hs) <> ":" <> snd hs)
-- Generator / Property for headers
randomHeaderGenerator :: Gen HTTP.Header
randomHeaderGenerator =
(,) <$> (CI.mk <$> nonEmptyByteString) <*> nonEmptyByteString
interestingAwsHeaderName :: Gen HTTP.HeaderName
interestingAwsHeaderName =
CI.mk . BS8.pack <$> fmap ("aws-" <>) nonEmptyString
interestingHeaderName :: Gen HTTP.HeaderName
interestingHeaderName =
QC.elements [HTTP.hContentMD5, HTTP.hContentType, HTTP.hDate]
interestingHeaderGenerator :: Gen HTTP.Header
interestingHeaderGenerator =
(,) <$> interestingHeaderName <*> nonEmptyByteString
interestingAwsHeaderGenerator :: Gen HTTP.Header
interestingAwsHeaderGenerator =
(,) <$> interestingAwsHeaderName <*> nonEmptyByteString
-- Generators / Properties for auxiliary header functions
prop_SortedHeaders :: Property
prop_SortedHeaders =
QC.forAll allHeadersGenerator $ \allHeaders -> testHeaders allHeaders
allHeadersGenerator :: Gen [HTTP.Header]
allHeadersGenerator =
QC.listOf $
QC.frequency
[ (1, interestingHeaderGenerator),
(1, interestingAwsHeaderGenerator)
]
allIncreasing :: [HTTP.Header] -> Bool
allIncreasing xs =
and $ zipWith (<=) mapped $ drop 1 mapped
where
mapped = map fst xs
testHeaders :: [HTTP.Header] -> Bool
testHeaders headers =
length headers == length sortedHeaders && allIncreasing sortedHeaders
where
sortedHeaders = List.sort headers
-- Generators for Text / QueryValues
nonEmptyByteString :: Gen ByteString
nonEmptyByteString = BS8.pack <$> nonEmptyString
uninterestingQValues :: Gen QueryString
uninterestingQValues = toQuery <$> nonEmptyText
nonEmptyText :: Gen Text.Text
nonEmptyText = Text.pack <$> nonEmptyString
nonEmptyString :: Gen String
nonEmptyString = QC.listOf1 QC.arbitrary