module Hasura.RQL.WebhookTransformsSpec ( spec, ) where ------------------------------------------------------------------------------- import Data.Aeson (FromJSON, ToJSON, eitherDecode, encode, fromJSON, toJSON) import Data.CaseInsensitive qualified as CI import Data.List (nubBy) import Data.Set qualified as S import Hasura.Prelude import Hasura.RQL.DDL.Webhook.Transform (RequestFields (..), RequestTransformFns, WithOptional (..), withOptional) import Hasura.RQL.DDL.Webhook.Transform.Body (BodyTransformFn) import Hasura.RQL.DDL.Webhook.Transform.Body qualified as Body import Hasura.RQL.DDL.Webhook.Transform.Class (Template (..), TemplatingEngine (..), UnescapedTemplate (..)) import Hasura.RQL.DDL.Webhook.Transform.Headers (HeadersTransformFn, TransformFn (..)) import Hasura.RQL.DDL.Webhook.Transform.Headers qualified as Headers import Hasura.RQL.DDL.Webhook.Transform.Method (Method (..), MethodTransformFn) import Hasura.RQL.DDL.Webhook.Transform.Method qualified as Method import Hasura.RQL.DDL.Webhook.Transform.QueryParams (QueryParamsTransformFn, TransformFn (..)) import Hasura.RQL.DDL.Webhook.Transform.QueryParams qualified as QueryParams import Hasura.RQL.DDL.Webhook.Transform.Url (UrlTransformFn) import Hasura.RQL.DDL.Webhook.Transform.Url qualified as Url import Hedgehog (Gen, MonadTest, forAll, tripping, (===)) import Hedgehog.Gen qualified as Gen import Hedgehog.Range qualified as Range import Test.Hspec (Spec, describe, it) import Test.Hspec.Hedgehog (hedgehog) ------------------------------------------------------------------------------- spec :: Spec spec = describe "WebhookTransform" do it "Method RoundTrip" . hedgehog $ forAll genMethod >>= trippingJSON it "StringTemplateText RoundTrip" . hedgehog $ forAll genUnescapedTemplate >>= trippingJSON it "Url RoundTrip" . hedgehog $ forAll genUrl >>= trippingJSON it "Template RoundTrip" . hedgehog $ forAll genTemplate >>= trippingJSON it "TemplateEngine RoundTrip" . hedgehog $ forAll genTemplatingEngine >>= trippingJSON it "TransformHeaders" . hedgehog $ do headers <- forAll genTransformHeaders let sortH (Headers.AddReplaceOrRemoveFields {..}) = Headers.AddReplaceOrRemoveFields (sort addOrReplaceHeaders) (sort removeHeaders) let headersMaybe = eitherDecode $ encode headers Right (sortH headers) === fmap sortH headersMaybe it "MetadataRequestTransform RoundTrip" . hedgehog $ do reqFields <- forAll genRequestTransformDefunc let sortH (WithOptional Nothing) = WithOptional Nothing sortH (WithOptional (Just (HeadersTransformFn_ (Headers.AddReplaceOrRemove (Headers.AddReplaceOrRemoveFields {..}))))) = WithOptional . Just . HeadersTransformFn_ . Headers.AddReplaceOrRemove $ Headers.AddReplaceOrRemoveFields (sort addOrReplaceHeaders) (sort removeHeaders) let sortQ (WithOptional Nothing) = WithOptional Nothing sortQ (WithOptional (Just (QueryParamsTransformFn_ (QueryParams.AddOrReplace qs)))) = WithOptional . Just . QueryParamsTransformFn_ . QueryParams.AddOrReplace $ sortOn fst qs sortQ (WithOptional (Just (QueryParamsTransformFn_ (QueryParams.ParamTemplate qs)))) = WithOptional . Just . QueryParamsTransformFn_ . QueryParams.ParamTemplate $ qs let sortRF rf@RequestFields {requestHeaders, queryParams} = rf {requestHeaders = sortH requestHeaders, queryParams = sortQ queryParams} let reqFieldsMaybe = eitherDecode $ encode reqFields Right (sortRF reqFields) === fmap sortRF reqFieldsMaybe ------------------------------------------------------------------------------- -- Generators genMethod :: Gen Method genMethod = (Method . CI.mk) <$> Gen.text (Range.constant 3 20) Gen.alphaNum genTemplatingEngine :: Gen TemplatingEngine genTemplatingEngine = Gen.enumBounded -- NOTE: This generator is strictly useful for roundtrip aeson testing -- and does not produce valid template snippets. genTemplate :: Gen Template genTemplate = Template . wrap <$> Gen.text (Range.constant 3 20) Gen.alphaNum where wrap txt = "\"" <> txt <> "\"" genUnescapedTemplate :: Gen UnescapedTemplate genUnescapedTemplate = UnescapedTemplate <$> Gen.text (Range.constant 3 20) Gen.alphaNum genTransformHeaders :: Gen Headers.AddReplaceOrRemoveFields genTransformHeaders = do numHeaders <- Gen.integral $ Range.constant 1 20 let genHeaderKey = CI.mk <$> Gen.text (Range.constant 1 20) Gen.alphaNum genHeaderValue = genUnescapedTemplate genKeys = S.toList <$> Gen.set (Range.singleton numHeaders) genHeaderKey genValues = S.toList <$> Gen.set (Range.singleton numHeaders) genHeaderValue removeHeaders <- Gen.list (Range.constant 1 10) genHeaderKey addOrReplaceHeaders <- liftA2 zip genKeys genValues pure $ Headers.AddReplaceOrRemoveFields {addOrReplaceHeaders, removeHeaders} genQueryParams :: Gen [(UnescapedTemplate, Maybe UnescapedTemplate)] genQueryParams = do numParams <- Gen.integral $ Range.constant 1 20 let keyGen = genUnescapedTemplate valueGen = Gen.maybe $ genUnescapedTemplate keys <- Gen.list (Range.singleton numParams) keyGen values <- Gen.list (Range.singleton numParams) valueGen pure $ nubBy (\a b -> fst a == fst b) $ zip keys values genUrl :: Gen UnescapedTemplate genUrl = do host <- Gen.text (Range.constant 3 20) Gen.alphaNum pure $ UnescapedTemplate $ "http://www." <> host <> ".com" genRequestTransformDefunc :: Gen RequestTransformFns genRequestTransformDefunc = do method <- Gen.maybe genMethod -- NOTE: At the moment no need to generate valid urls or templates -- but such instances maybe useful in the future. url <- Gen.maybe genUrl body <- Gen.maybe genTemplate queryParams <- Gen.maybe genQueryParams headers <- Gen.maybe genTransformHeaders pure RequestFields { method = withOptional @MethodTransformFn (fmap Method.Replace method), url = withOptional @UrlTransformFn (fmap Url.Modify url), body = withOptional @BodyTransformFn (fmap Body.ModifyAsJSON body), queryParams = withOptional @QueryParamsTransformFn (fmap QueryParams.AddOrReplace queryParams), requestHeaders = withOptional @HeadersTransformFn (fmap Headers.AddReplaceOrRemove headers) } ------------------------------------------------------------------------------- -- Helpers -- | TODO: Move this out to a common module! trippingJSON :: forall a m. (Show a, Eq a, ToJSON a, FromJSON a, MonadTest m) => a -> m () trippingJSON val = tripping val toJSON fromJSON