graphql-engine/server/src-test/Hasura/RQL/WebhookTransformsSpec.hs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

154 lines
6.4 KiB
Haskell
Raw Normal View History

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
server: use kriti template to generate query param from list ## Description ✍️ This PR adds support to generate query params directly using a kriti template which can be used to flatten a list of parameter arguments as well. ### Changes in the Metadata API Earlier the `query_params` key inside `request_transform` used to take in an object of key/value pairs where the `key` represents the query parameter name and `value` points to the value of the parameter or a kriti template which could be resolved to the value. With this PR, we provide the user with more freedom to generate the complete query string using kriti template. The `query_params` can now take in a string as well which will be a kriti template. This new change needs to be incorporated on the console and CLI metadata import/export as well. - [x] CLI: Compatible, no changes required - [ ] Console ## Changelog ✍️ __Component__ : server __Type__: feature __Product__: community-edition ### Short Changelog use kriti template to generate query param from list of arguments ### Related Issues ✍ https://hasurahq.atlassian.net/browse/GS-243 ### Solution and Design ✍ We use a kriti template to generate the complete query parameter string. | Query Template | Output | |---|---| | `{{ concat ([concat({{ range _, x := [\"apple\", \"banana\"] }} \"tags={{x}}&\" {{ end }}), \"flag=smthng\"]) }}`| `tags=apple&tags=banana&flag=smthng` | | `{{ concat ([\"tags=\", concat({{ range _, x := $body.input }} \"{{x}},\" {{ end }})]) }}` | `tags=apple%2Cbanana%2C` | ### Steps to test and verify ✍ - start HGE and make the following request to `http://localhost:8080/v1/metadata`: ```json { "type": "test_webhook_transform", "args": { "webhook_url": "http://localhost:3000", "body": { "action": { "name": "actionName" }, "input": ["apple", "banana"] }, "request_transform": { "version": 2, "url": "{{$base_url}}", "query_params": "{{ concat ([concat({{ range _, x := $body.input }} \"tags={{x}}&\" {{ end }}), \"flag=smthng\"]) }}", "template_engine": "Kriti" } } } ``` - you should receive the following as output: ```json { "body": { "action": { "name": "actionName" }, "input": [ "apple", "banana" ] }, "headers": [], "method": "GET", "webhook_url": "http://localhost:3000?tags=apple&tags=banana&flag=smthng" } ``` PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6961 Co-authored-by: Tirumarai Selvan <8663570+tirumaraiselvan@users.noreply.github.com> GitOrigin-RevId: 712ba038f03009edc3e8eb0435e723304943399a
2022-11-29 23:26:04 +03:00
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