mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-21 14:31:55 +03:00
4885a3fd9a
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8948 GitOrigin-RevId: d70c4a50b94ffe7d42a1fb1017051d351f236acc
290 lines
8.1 KiB
Python
290 lines
8.1 KiB
Python
import os
|
|
import ruamel.yaml as yaml
|
|
import textwrap
|
|
from abc import ABC, abstractmethod
|
|
|
|
# https://yaml.readthedocs.io/en/latest/example.html#output-of-dump-as-a-string
|
|
def dump_to_string(v):
|
|
import io
|
|
s = io.StringIO()
|
|
yaml.YAML().dump(v, stream=s)
|
|
return s.getvalue()
|
|
|
|
class Fixtures():
|
|
setups = {} # Map backend [Setup]
|
|
specs = {} # Map desc [Spec]
|
|
|
|
def add_setup(self, backend, setup):
|
|
if not backend in self.setups.keys():
|
|
self.setups[backend] = []
|
|
self.setups[backend].append(setup)
|
|
|
|
def add_spec(self, desc, spec):
|
|
if not desc in self.specs.keys():
|
|
self.specs[desc] = []
|
|
self.specs[desc].append(spec)
|
|
|
|
def render_gql(query, headers):
|
|
if headers is None:
|
|
return f"""\
|
|
GraphqlEngine.postGraphql
|
|
testEnvironment
|
|
[graphql|
|
|
{textwrap.indent(query, ' ')}
|
|
|]"""
|
|
else:
|
|
return f"""\
|
|
GraphqlEngine.postGraphqlWithHeaders
|
|
testEnvironment
|
|
{textwrap.indent(render_headers(headers), ' ')}
|
|
[graphql|
|
|
{textwrap.indent(query, ' ')}
|
|
|]"""
|
|
|
|
def render_v2query(expected_status, query, headers):
|
|
if headers is None:
|
|
return f"""\
|
|
GraphqlEngine.postV2Query
|
|
{expected_status}
|
|
testEnvironment
|
|
[interpolateYaml|
|
|
{textwrap.indent(dump_to_string(query), ' ')}
|
|
|]"""
|
|
else:
|
|
return f"""
|
|
GraphqlEngine.postV2QueryWithHeaders
|
|
{expected_status}
|
|
{textwrap.indent(render_headers(headers), ' ')}
|
|
testEnvironment
|
|
[interpolateYaml|
|
|
{textwrap.indent(dump_to_string(query), ' ')}
|
|
|]"""
|
|
|
|
def render_v1query(expected_status, query, headers):
|
|
if headers is None:
|
|
return f"""\
|
|
GraphqlEngine.postV1Query
|
|
{expected_status}
|
|
testEnvironment
|
|
[interpolateYaml|
|
|
{textwrap.indent(dump_to_string(query), ' ')}
|
|
|]"""
|
|
else:
|
|
return f"""\
|
|
GraphqlEngine.postV1QueryWithHeaders
|
|
{expected_status}
|
|
{textwrap.indent(render_headers(headers), ' ')}
|
|
testEnvironment
|
|
[interpolateYaml|
|
|
{textwrap.indent(dump_to_string(query), ' ')}
|
|
|]"""
|
|
|
|
def render_v1metadata(expected_status, query, headers):
|
|
if headers is None:
|
|
return f"""\
|
|
GraphqlEngine.postMetadataWithStatus
|
|
{expected_status}
|
|
testEnvironment
|
|
[interpolateYaml|
|
|
{textwrap.indent(dump_to_string(query), ' ')}
|
|
|]"""
|
|
else:
|
|
return f"""\
|
|
GraphqlEngine.postMetadataWithHeaders
|
|
testEnvironment
|
|
{textwrap.indent(render_headers(headers), ' ')}
|
|
[interpolateYaml|
|
|
{textwrap.indent(dump_to_string(query), ' ')}
|
|
|]"""
|
|
|
|
def render_headers(headers):
|
|
res = "(\n"
|
|
for key, val in headers.items():
|
|
res += f' ("{key}", "{val}"):\n'
|
|
res += "[])"
|
|
|
|
return res
|
|
|
|
class Setup():
|
|
def __init__(self, setup_name, original_file, url, value):
|
|
self.setup_name = setup_name
|
|
self.original_file = original_file
|
|
self.url = url
|
|
self.value = value
|
|
|
|
def render_define(self, backend):
|
|
return f"""-- original file: {self.original_file}
|
|
{self.setup_name}_{backend} :: J.Value
|
|
{self.setup_name}_{backend} =
|
|
[interpolateYaml|
|
|
{textwrap.indent(self.value, 4*' ')}
|
|
|]
|
|
"""
|
|
|
|
def render_run(self, backend):
|
|
res = ""
|
|
if self.url == "/v2/query":
|
|
res = f"GraphqlEngine.postV2Query 200 testEnvironment {self.setup_name}_{backend}"
|
|
elif self.url == "/v1/query":
|
|
res = f"GraphqlEngine.postV1Query 200 testEnvironment {self.setup_name}_{backend}"
|
|
elif self.url == "/v1/graphql":
|
|
res = f"GraphqlEngine.postGraphql testEnvironment {self.setup_name}_{backend}"
|
|
elif self.url == "/v1/metadata":
|
|
res = f"""GraphqlEngine.postMetadata_ testEnvironment {self.setup_name}_{backend}"""
|
|
|
|
else:
|
|
raise Exception(f"Unknown query url: {self.url}, in file {self.original_file}")
|
|
|
|
return res
|
|
|
|
class Spec(ABC):
|
|
|
|
@abstractmethod
|
|
def render(self):
|
|
pass
|
|
|
|
class PostSpec(Spec):
|
|
"""
|
|
Specs that post something to HGE and make assertions against status code and
|
|
response.
|
|
"""
|
|
def __init__(self, desc, file, url, headers, query, expected_status, expected_response):
|
|
self.desc = desc
|
|
self.file = file
|
|
self.url = url
|
|
self.headers = headers
|
|
self.query = query
|
|
self.expected_status = expected_status
|
|
self.expected_response = expected_response
|
|
|
|
def render(self):
|
|
actual = ""
|
|
if self.url == "/v1/query":
|
|
actual = render_v1query(self.expected_status, self.query, self.headers)
|
|
elif self.url == "/v2/query":
|
|
actual = render_v2query(self.expected_status, self.query, self.headers)
|
|
elif self.url == "/v1/graphql":
|
|
actual = render_gql(self.query, self.headers)
|
|
elif self.url == "/v1/metadata":
|
|
actual = render_v1metadata(self.expected_status, self.query, self.headers)
|
|
else:
|
|
raise Exception(f"Unknown query url: {self.url}, in file {self.file}")
|
|
|
|
head =f"""-- from: {self.file}
|
|
it "{self.desc}" \\testEnvironment -> do"""
|
|
|
|
expected = ""
|
|
assertion = ""
|
|
if self.expected_response is not None:
|
|
expected = f"""
|
|
let expected :: J.Value
|
|
expected =
|
|
[interpolateYaml|
|
|
{textwrap.indent(dump_to_string(self.expected_response), ' ')}
|
|
|]
|
|
"""
|
|
assertion = "actual `shouldBe` expected"
|
|
else:
|
|
assertion = "void actual"
|
|
|
|
return f"""{head}
|
|
{expected}
|
|
let actual :: IO J.Value
|
|
actual =
|
|
{textwrap.indent(actual, ' ')}
|
|
|
|
{assertion}"""
|
|
|
|
|
|
tests_to_port = {} # Map classname Fixtures
|
|
|
|
def with_test(name):
|
|
|
|
if not name in tests_to_port.keys():
|
|
tests_to_port[name] = Fixtures()
|
|
|
|
return tests_to_port[name]
|
|
|
|
|
|
def write_tests_to_port():
|
|
global tests_to_port
|
|
|
|
os.makedirs("../lib/api-tests/src/Test/PortedFromPytest", exist_ok=True)
|
|
|
|
for name, fixtures in tests_to_port.items():
|
|
backends = fixtures.setups.keys()
|
|
# Rather than directly make a hspec-discover'ed spec we include ported specs manually
|
|
# for now.
|
|
# This enables us to bulk-update the ported specs if we make changes to this porting script.
|
|
#hspecified_name = name.removeprefix("Test") + "Spec"
|
|
|
|
hspecified_name = name
|
|
with open(f"../lib/api-tests/src/Test/PortedFromPytest/{hspecified_name}.hs", 'w') as ported_hs:
|
|
ported_hs.write("{-# OPTIONS_GHC -Wno-deprecations #-}\n")
|
|
ported_hs.write("{- HLINT ignore -}\n")
|
|
ported_hs.write(f"""\
|
|
-- | GENERATED BY 'server/tests-py/PortToHaskell.py'.
|
|
-- Please avoid editing this file manually.
|
|
module Test.PortedFromPytest.{hspecified_name} (spec) where
|
|
|
|
import Data.Aeson qualified as J
|
|
import Data.List.NonEmpty qualified as NE
|
|
""" + str.join('\n', [
|
|
f"import Harness.Backend.{backend} qualified as {backend}"
|
|
for backend in backends
|
|
|
|
]) +
|
|
"""
|
|
import Harness.GraphqlEngine qualified as GraphqlEngine
|
|
import Harness.PytestPortedCompat (compatSetup)
|
|
import Harness.Quoter.Graphql (graphql)
|
|
import Harness.Quoter.Yaml
|
|
import Harness.Test.Fixture qualified as Fixture
|
|
import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment (..))
|
|
import Harness.Yaml (shouldReturnYaml)
|
|
import Hasura.Prelude
|
|
import Test.Hspec
|
|
|
|
"""
|
|
)
|
|
|
|
for backend, setup_values in fixtures.setups.items():
|
|
for setup in setup_values:
|
|
ported_hs.write(setup.render_define(backend) + '\n')
|
|
|
|
ported_hs.write(
|
|
f"""
|
|
fixture_{backend} :: Fixture.Fixture ()
|
|
fixture_{backend} =
|
|
(Fixture.fixture $ Fixture.Backend Fixture.{backend})""" +
|
|
"""
|
|
{ Fixture.setupTeardown = \\(testEnvironment, _) ->
|
|
[ Fixture.SetupAction
|
|
{ Fixture.setupAction = do""" + f"""
|
|
compatSetup testEnvironment Fixture.{backend}
|
|
""" + textwrap.indent(str.join('\n', map(lambda setup: setup.render_run(backend), setup_values)), 16*' ') + """,
|
|
Fixture.teardownAction = \\_ -> return ()
|
|
}
|
|
]
|
|
}
|
|
""")
|
|
|
|
ported_hs.write("""
|
|
spec :: SpecWith GlobalTestEnvironment
|
|
spec = Fixture.runSingleSetup (NE.fromList [""" + str.join(', ', ["fixture_" + backend for backend in backends]) + """]) tests
|
|
|
|
tests :: Fixture.Options -> SpecWith TestEnvironment
|
|
tests opts = do
|
|
let shouldBe :: IO J.Value -> J.Value -> IO ()
|
|
shouldBe = shouldReturnYaml opts
|
|
""")
|
|
|
|
for desc, specs in fixtures.specs.items():
|
|
ported_hs.write(f"""
|
|
describe "{desc}" do
|
|
""")
|
|
for spec in specs:
|
|
ported_hs.write('\n' + textwrap.indent(spec.render(), 4*' ') + '\n')
|
|
|
|
tests_to_port = {}
|