ucam-webauth-0.1.0.0: test/UcamWebauthSpec.hs
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE
PackageImports
, AllowAmbiguousTypes
, FlexibleInstances
, NamedFieldPuns
, OverloadedLists
, OverloadedStrings
, QuasiQuotes
, RecordWildCards
, ScopedTypeVariables
, TypeApplications
, TypeOperators
, TypeSynonymInstances
#-}
module UcamWebauthSpec (spec) where
import "aeson" Data.Aeson.Types hiding ((.=))
import "base" Data.Bits
import qualified "bytestring" Data.ByteString as BS
import "ucam-webauth-types" Data.ByteString.B64
import qualified "bytestring" Data.ByteString.Char8 as B8
import "base" Data.Char
import "base" Data.Maybe
import "here" Data.String.Here
import "text" Data.Text (Text)
import "text" Data.Text.Encoding
import qualified "text" Data.Text as T
import "time" Data.Time
import "time-qq" Data.Time.QQ as Q
import "generic-random" Generic.Random
import "microlens" Lens.Micro
import "microlens-mtl" Lens.Micro.Mtl
import "hspec" Test.Hspec
import "hspec" Test.Hspec.QuickCheck
import "QuickCheck" Test.QuickCheck
import "quickcheck-instances" Test.QuickCheck.Instances ()
import "this" UcamWebauth
import "ucam-webauth-types" UcamWebauth.Data.Internal
import "http-api-data" Web.HttpApiData
spec :: Spec
spec = do
describe "UcamWebauth" $ do
it "should parse example response" $ do
parseQueryParam @(AuthResponse Text) exampleResponseText `shouldBe` Right exampleResponse
it "should parse example signed response" $ do
parseQueryParam @(MaybeValidResponse Text) exampleSignedResponseText `shouldBe` Right exampleSignedResponse
it "should produce example response" $ do
toQueryParam @(AuthResponse Text) exampleResponse `shouldBe` exampleResponseText
it "should produce example signed response" $ do
toQueryParam @(MaybeValidResponse Text) exampleSignedResponse `shouldBe` exampleSignedResponseText
modifyMaxSuccess (const 1000) $ prop_HttpApiData @(AuthResponse Text)
modArgs $ prop_HttpApiData @(MaybeValidResponse Text)
modArgs :: SpecWith a -> SpecWith a
modArgs = modifyMaxSuccess (const 100) . modifyMaxDiscardRatio (const 1) . modifyMaxSize (const 1)
prop_HttpApiData
:: forall a .
( Arbitrary a
, Eq a
, Show a
, ToHttpApiData a
, FromHttpApiData a
)
=> Spec
prop_HttpApiData = prop "should serialize with HttpApiData correctly" $ \(h :: a) ->
let qp = toQueryParam h in counterexample (T.unpack qp) $
parseQueryParam qp === Right h
instance (ToJSON a, FromJSON a, Arbitrary a) => Arbitrary (MaybeValidResponse a) where
arbitrary = do
x <- genericArbitraryU `suchThat` \a -> and @[]
[ notElem @[] (a ^. ucamASig) [Just "", Nothing]
, isNothing (a ^. ucamASig) || isJust (a ^. ucamAKid)
]
pure $ x &~ do
ucamAToSign .= encodeUtf8 (toQueryParam (x ^. ucamAResponse))
shrink = genericShrink
instance (ToJSON a, FromJSON a, Arbitrary a) => Arbitrary (AuthResponse a) where
arbitrary = do
x <- genericArbitraryU `suchThat` \a -> and @[]
[ fromMaybe True $ a ^? ucamAPrincipal . _Just . to (T.all (/= '!'))
, fromMaybe True $ a ^? ucamAMsg . _Just . to (T.all (/= '!'))
, a ^. ucamAId . to (T.all (/= '!'))
, a ^. ucamAUrl . to (T.all (/= '!'))
, isJust (a ^. ucamAAuth) `xor` isJust (a ^. ucamASso)
, a ^. ucamAPrincipal /= Just ""
, a ^. ucamAMsg /= Just ""
]
pure $ x &~ do
ucamAIssue . dayTime %= fromInteger . round
shrink = genericShrink
dayTime :: UTCTime `Lens'` DiffTime
dayTime f UTCTime{..} = (\x -> UTCTime{utctDayTime = x, ..}) <$> f utctDayTime
{-# INLINE dayTime #-}
instance Arbitrary KeyID where
arbitrary = genericArbitraryU `suchThat` \(KeyID a) -> and @[]
[ BS.length a & \x -> x >= 1 && x <= 8
, B8.all isDigit a
, B8.head a /= '0'
]
shrink = genericShrink
instance Arbitrary WLSVersion where
arbitrary = pure WLS3
instance Arbitrary StatusCode where
arbitrary = pure Ok200
instance Arbitrary UcamBase64BS where
arbitrary = encodeUcamB64 <$> arbitrary
shrink = genericShrink
instance Arbitrary Ptag where
arbitrary = genericArbitraryU
shrink = genericShrink
instance Arbitrary AuthType where
arbitrary = genericArbitraryU
shrink = genericShrink
instance Arbitrary TimePeriod where
arbitrary = timePeriodFromSeconds . secondsFromTimePeriod <$> genericArbitraryU `suchThat` (>= 0)
shrink = genericShrink
exampleSignedResponse :: MaybeValidResponse Text
exampleSignedResponse = SignedAuthResponse
{ _ucamAResponse = AuthResponse
{ _ucamAVer = WLS3
, _ucamAStatus = Ok200
, _ucamAMsg = Nothing
, _ucamAIssue = [utcIso8601ms| 2017-05-15T17:23:11 |]
, _ucamAId = "oANAuhC9fZmMlZUPIm53y5vn"
, _ucamAUrl = "http://localhost:3000/foo/query"
, _ucamAPrincipal = Just "test0244"
, _ucamAPtags = [Current]
, _ucamAAuth = Nothing
, _ucamASso = Just [Pwd]
, _ucamALife = Just (timePeriodFromSeconds 30380)
, _ucamAParams = Just "This is 100% of the data! And it’s really quite cool"
}
, _ucamAToSign = "3!200!!20170515T172311Z!oANAuhC9fZmMlZUPIm53y5vn!http://localhost:3000/foo/query!test0244!current!!pwd!30380!IlRoaXMgaXMgMTAwJSBvZiB0aGUgZGF0YSEgQW5kIGl04oCZcyByZWFsbHkgcXVpdGUgY29vbCI_"
, _ucamAKid = Just "901"
, _ucamASig = Just
(UcamB64
{ unUcamB64 = "RzC9KZWALCSeK0n9885X4zzemHizuj8K.NOpt.n1hfRCTE2ZBgvJ-fBvT-PaL80cSFGpyCJgt9LvM4-peJzcidoKC6zhBEvG0QnlqWTLsphbIA0JmBRiOoeqyLYRVGwDEdLdacdsQRM.u7bik.enhbuN1-aIQCOdB5MutxtYiu4_"
}
)
}
exampleResponse :: AuthResponse Text
exampleResponse = AuthResponse
{ _ucamAVer = WLS3
, _ucamAStatus = Ok200
, _ucamAMsg = Nothing
, _ucamAIssue = [utcIso8601ms| 2017-05-15T17:23:11 |]
, _ucamAId = "oANAuhC9fZmMlZUPIm53y5vn"
, _ucamAUrl = "http://localhost:3000/foo/query"
, _ucamAPrincipal = Just "test0244"
, _ucamAPtags = [Current]
, _ucamAAuth = Nothing
, _ucamASso = Just [Pwd]
, _ucamALife = Just (timePeriodFromSeconds 30380)
, _ucamAParams = Just "This is 100% of the data! And it’s really quite cool"
}
exampleSignedResponseText :: Text
exampleSignedResponseText = exampleResponseText <> [here|
!901!RzC9KZWALCSeK0n9885X4zzemHizuj8K.NOpt.n1hfRCTE2ZBgvJ-fBvT-PaL80cSFGpyCJgt9LvM4-peJzcidoKC6zhBEvG0QnlqWTLsphbIA0JmBRiOoeqyLYRVGwDEdLdacdsQRM.u7bik.enhbuN1-aIQCOdB5MutxtYiu4_
|]
exampleResponseText :: Text
exampleResponseText = [here|
3!200!!20170515T172311Z!oANAuhC9fZmMlZUPIm53y5vn!http://localhost:3000/foo/query!test0244!current!!pwd!30380!IlRoaXMgaXMgMTAwJSBvZiB0aGUgZGF0YSEgQW5kIGl04oCZcyByZWFsbHkgcXVpdGUgY29vbCI_
|]