2022-01-17 10:39:59 +03:00
|
|
|
from ruamel.yaml import YAML
|
2022-10-21 20:32:58 +03:00
|
|
|
import pytest
|
|
|
|
|
|
|
|
from conftest import extract_server_address_from
|
Fix remote relationship invalid type name issue (fix hasura/graphql-engine#8002)
## Description
When setting up a remote relationship to a remote schema, values coming from the left-hand side are given as _arguments_ to the targeted field of the remote schema. In turn, that means we need to adjust the arguments to that remote field; in the case of input objects, it means creating a brand new input object in which the relevant fields have been removed.
To both avoid conflicts, and be explicit, we give a pretty verbose name to such an input object: its original name, followed by "remote_rel", followed by the full name of the field (table name + relationship name). The bug there was introduced when working on extending remote relationships to other backends: we changed the code that translates the table name to a graphql identifier to be generic, and use the table's `ToTxt` instance instead. However, when a table is not in the default schema, the character used by that instance is `.`, which is not a valid GraphQL name.
This PR fixes it, by doing two things:
- it defines a safe function to translate LHS identifiers to graphql names (by replacing all invalid characters by `_`)
- it doesn't use `unsafeMkName` anymore, and checks at validation time that the type name is correct
## Further work
On this PR:
- [x] add a test
- [x] write a Changelog entry
Beyond this PR, we might want to:
- prioritize #1747
- analyze all calls to `unsafeMkName` and remove as many as possible
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3363
GitOrigin-RevId: fe98eb1d34157b2c8323af453f5c369de616af38
2022-01-27 17:32:55 +03:00
|
|
|
from remote_server import NodeGraphQL
|
2022-10-21 20:32:58 +03:00
|
|
|
from validate import check_query_f, check_query
|
2018-09-18 14:31:16 +03:00
|
|
|
|
2022-01-17 10:39:59 +03:00
|
|
|
yaml=YAML(typ='safe', pure=True)
|
|
|
|
|
2022-09-28 12:19:47 +03:00
|
|
|
@pytest.fixture(scope='class')
|
|
|
|
@pytest.mark.early
|
2023-04-25 15:49:31 +03:00
|
|
|
def graphql_service(worker_id: str, hge_fixture_env: dict[str, str]):
|
2022-10-21 20:32:58 +03:00
|
|
|
(_, port) = extract_server_address_from('GRAPHQL_SERVICE_HANDLER')
|
2023-04-25 15:49:31 +03:00
|
|
|
server = NodeGraphQL(worker_id, 'remote_schemas/nodejs/index.js', port=port)
|
2022-10-21 20:32:58 +03:00
|
|
|
server.start()
|
|
|
|
print(f'{graphql_service.__name__} server started on {server.url}')
|
|
|
|
hge_fixture_env['GRAPHQL_SERVICE_HANDLER'] = server.url
|
|
|
|
yield server
|
|
|
|
server.stop()
|
Fix remote relationship invalid type name issue (fix hasura/graphql-engine#8002)
## Description
When setting up a remote relationship to a remote schema, values coming from the left-hand side are given as _arguments_ to the targeted field of the remote schema. In turn, that means we need to adjust the arguments to that remote field; in the case of input objects, it means creating a brand new input object in which the relevant fields have been removed.
To both avoid conflicts, and be explicit, we give a pretty verbose name to such an input object: its original name, followed by "remote_rel", followed by the full name of the field (table name + relationship name). The bug there was introduced when working on extending remote relationships to other backends: we changed the code that translates the table name to a graphql identifier to be generic, and use the table's `ToTxt` instance instead. However, when a table is not in the default schema, the character used by that instance is `.`, which is not a valid GraphQL name.
This PR fixes it, by doing two things:
- it defines a safe function to translate LHS identifiers to graphql names (by replacing all invalid characters by `_`)
- it doesn't use `unsafeMkName` anymore, and checks at validation time that the type name is correct
## Further work
On this PR:
- [x] add a test
- [x] write a Changelog entry
Beyond this PR, we might want to:
- prioritize #1747
- analyze all calls to `unsafeMkName` and remove as many as possible
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3363
GitOrigin-RevId: fe98eb1d34157b2c8323af453f5c369de616af38
2022-01-27 17:32:55 +03:00
|
|
|
|
2020-02-13 12:14:02 +03:00
|
|
|
@pytest.mark.usefixtures('per_class_tests_db_state')
|
|
|
|
class TestGraphqlIntrospection:
|
2018-09-18 14:31:16 +03:00
|
|
|
|
|
|
|
def test_introspection(self, hge_ctx):
|
2018-10-28 21:27:49 +03:00
|
|
|
with open(self.dir() + "/introspection.yaml") as c:
|
2022-01-17 10:39:59 +03:00
|
|
|
conf = yaml.load(c)
|
2019-09-04 18:02:35 +03:00
|
|
|
resp, _ = check_query(hge_ctx, conf)
|
2018-10-28 21:27:49 +03:00
|
|
|
hasArticle = False
|
|
|
|
hasArticleAuthorFKRel = False
|
|
|
|
hasArticleAuthorManualRel = False
|
|
|
|
for t in resp['data']['__schema']['types']:
|
|
|
|
if t['name'] == 'article':
|
|
|
|
hasArticle = True
|
|
|
|
for fld in t['fields']:
|
|
|
|
if fld['name'] == 'author_obj_rel_manual':
|
|
|
|
hasArticleAuthorManualRel = True
|
|
|
|
assert fld['type']['kind'] == 'OBJECT'
|
|
|
|
elif fld['name'] == 'author_obj_rel_fk':
|
|
|
|
hasArticleAuthorFKRel = True
|
|
|
|
assert fld['type']['kind'] == 'NON_NULL'
|
|
|
|
assert hasArticle
|
|
|
|
assert hasArticleAuthorFKRel
|
|
|
|
assert hasArticleAuthorManualRel
|
2018-09-18 14:31:16 +03:00
|
|
|
|
|
|
|
def test_introspection_user(self, hge_ctx):
|
2018-10-28 21:27:49 +03:00
|
|
|
check_query_f(hge_ctx, self.dir() + "/introspection_user_role.yaml")
|
2018-09-18 14:31:16 +03:00
|
|
|
|
2018-10-28 21:27:49 +03:00
|
|
|
@classmethod
|
|
|
|
def dir(cls):
|
|
|
|
return "queries/graphql_introspection"
|
2020-10-29 15:48:45 +03:00
|
|
|
|
2021-03-10 11:54:53 +03:00
|
|
|
|
|
|
|
@pytest.mark.usefixtures('per_class_tests_db_state')
|
|
|
|
class TestNullableObjectRelationshipInSchema:
|
|
|
|
@classmethod
|
|
|
|
def dir(cls):
|
|
|
|
return "queries/graphql_introspection/nullable_object_relationship"
|
|
|
|
|
server: fix the nullability of object relationships (fix hasura/graphql-engine#7201)
When adding object relationships, we set the nullability of the generated GraphQL field based on whether the database backend enforces that the referenced data always exists. For manual relationships (corresponding to `manual_configuration`), the database backend is unaware of any relationship between data, and hence such fields are always set to be nullable.
For relationships generated from foreign key constraints (corresponding to `foreign_key_constraint_on`), we distinguish between two cases:
1. The "forward" object relationship from a referencing table (i.e. which has the foreign key constraint) to a referenced table. This should be set to be non-nullable when all referencing columns are non-nullable. But in fact, it used to set it to be non-nullable if *any* referencing column is non-nullable, which is only correct in Postgres when `MATCH FULL` is set (a flag we don't consider). This fixes that by changing a boolean conjunction to a disjunction.
2. The "reverse" object relationship from a referenced table to a referencing table which has the foreign key constraint. This should always be set to be nullable. But in fact, it used to always be set to non-nullable, as was reported in hasura/graphql-engine#7201. This fixes that.
Moreover, we have moved the computation of the nullability from `Hasura.RQL.DDL.Relationship` to `Hasura.GraphQL.Schema.Select`: this nullability used to be passed through the `riIsNullable` field of `RelInfo`, but for array relationships this information is not actually used, and moreover the remaining fields of `RelInfo` are already enough to deduce the nullability.
This also adds regression tests for both (1) and (2) above.
https://github.com/hasura/graphql-engine-mono/pull/2159
GitOrigin-RevId: 617f12765614f49746d18d3368f41dfae2f3e6ca
2021-08-26 18:26:43 +03:00
|
|
|
def test_introspection_both_directions_both_nullabilities(self, hge_ctx):
|
|
|
|
check_query_f(hge_ctx, self.dir() + "/nullability.yaml")
|
2021-03-10 11:54:53 +03:00
|
|
|
|
2020-10-29 15:48:45 +03:00
|
|
|
def getTypeNameFromType(typeObject):
|
|
|
|
if typeObject['name'] != None:
|
|
|
|
return typeObject['name']
|
|
|
|
elif isinstance(typeObject['ofType'],dict):
|
|
|
|
return getTypeNameFromType(typeObject['ofType'])
|
|
|
|
else:
|
|
|
|
raise Exception("typeObject doesn't have name and ofType is not an object")
|
|
|
|
|
Fix remote relationship invalid type name issue (fix hasura/graphql-engine#8002)
## Description
When setting up a remote relationship to a remote schema, values coming from the left-hand side are given as _arguments_ to the targeted field of the remote schema. In turn, that means we need to adjust the arguments to that remote field; in the case of input objects, it means creating a brand new input object in which the relevant fields have been removed.
To both avoid conflicts, and be explicit, we give a pretty verbose name to such an input object: its original name, followed by "remote_rel", followed by the full name of the field (table name + relationship name). The bug there was introduced when working on extending remote relationships to other backends: we changed the code that translates the table name to a graphql identifier to be generic, and use the table's `ToTxt` instance instead. However, when a table is not in the default schema, the character used by that instance is `.`, which is not a valid GraphQL name.
This PR fixes it, by doing two things:
- it defines a safe function to translate LHS identifiers to graphql names (by replacing all invalid characters by `_`)
- it doesn't use `unsafeMkName` anymore, and checks at validation time that the type name is correct
## Further work
On this PR:
- [x] add a test
- [x] write a Changelog entry
Beyond this PR, we might want to:
- prioritize #1747
- analyze all calls to `unsafeMkName` and remove as many as possible
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3363
GitOrigin-RevId: fe98eb1d34157b2c8323af453f5c369de616af38
2022-01-27 17:32:55 +03:00
|
|
|
@pytest.mark.usefixtures('per_class_tests_db_state', 'graphql_service')
|
|
|
|
class TestRemoteRelationshipsGraphQLNames:
|
|
|
|
@classmethod
|
|
|
|
def dir(cls):
|
|
|
|
return "queries/graphql_introspection/remote_relationships"
|
|
|
|
|
|
|
|
def test_relation_from_custom_schema_has_correct_name(self, hge_ctx):
|
|
|
|
check_query_f(hge_ctx, self.dir() + "/relation_custom_schema_has_correct_name.yaml")
|
|
|
|
|
2020-10-29 15:48:45 +03:00
|
|
|
@pytest.mark.usefixtures('per_class_tests_db_state')
|
|
|
|
class TestGraphqlIntrospectionWithCustomTableName:
|
|
|
|
|
|
|
|
# test to check some of the type names that are generated
|
|
|
|
# while tracking a table with a custom name
|
|
|
|
def test_introspection(self, hge_ctx):
|
|
|
|
with open(self.dir() + "/introspection.yaml") as c:
|
2022-01-17 10:39:59 +03:00
|
|
|
conf = yaml.load(c)
|
2020-10-29 15:48:45 +03:00
|
|
|
resp, _ = check_query(hge_ctx, conf)
|
|
|
|
hasMultiSelect = False
|
|
|
|
hasAggregate = False
|
|
|
|
hasSelectByPk = False
|
|
|
|
hasQueryRoot = False
|
2022-09-07 19:24:43 +03:00
|
|
|
hasMultiInsert = False
|
|
|
|
hasUpdateByPk = False
|
2020-10-29 15:48:45 +03:00
|
|
|
for t in resp['data']['__schema']['types']:
|
|
|
|
if t['name'] == 'query_root':
|
|
|
|
hasQueryRoot = True
|
|
|
|
for field in t['fields']:
|
|
|
|
if field['name'] == 'user_address':
|
|
|
|
hasMultiSelect = True
|
|
|
|
assert 'args' in field
|
|
|
|
for args in field['args']:
|
|
|
|
if args['name'] == 'distinct_on':
|
|
|
|
assert "user_address_select_column" == getTypeNameFromType(args['type'])
|
|
|
|
elif args['name'] == 'order_by':
|
|
|
|
assert "user_address_order_by" == getTypeNameFromType(args['type'])
|
|
|
|
elif args['name'] == 'where':
|
|
|
|
assert 'user_address_bool_exp' == getTypeNameFromType(args['type'])
|
|
|
|
elif field['name'] == 'user_address_aggregate':
|
|
|
|
hasAggregate = True
|
|
|
|
assert "user_address_aggregate" == getTypeNameFromType(field['type'])
|
|
|
|
elif field['name'] == 'user_address_by_pk':
|
|
|
|
assert "user_address" == getTypeNameFromType(field['type'])
|
|
|
|
hasSelectByPk = True
|
|
|
|
elif t['name'] == 'mutation_root':
|
|
|
|
for field in t['fields']:
|
|
|
|
if field['name'] == 'insert_user_address':
|
|
|
|
hasMultiInsert = True
|
|
|
|
assert "user_address_mutation_response" == getTypeNameFromType(field['type'])
|
|
|
|
for args in field['args']:
|
|
|
|
if args['name'] == 'object':
|
|
|
|
assert "user_address_insert_input" == getTypeNameFromType(args['type'])
|
|
|
|
elif field['name'] == 'update_user_address_by_pk':
|
|
|
|
hasUpdateByPk = True
|
|
|
|
assert "user_address" == getTypeNameFromType(field['type'])
|
|
|
|
for args in field['args']:
|
|
|
|
if args['name'] == 'object':
|
|
|
|
assert "user_address" == getTypeNameFromType(args['type'])
|
|
|
|
assert hasQueryRoot
|
|
|
|
assert hasMultiSelect
|
|
|
|
assert hasAggregate
|
|
|
|
assert hasSelectByPk
|
|
|
|
assert hasMultiInsert
|
|
|
|
assert hasUpdateByPk
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dir(cls):
|
|
|
|
return "queries/graphql_introspection/custom_table_name"
|
2021-05-05 15:25:27 +03:00
|
|
|
|
2021-07-01 12:20:53 +03:00
|
|
|
@pytest.mark.usefixtures('per_class_tests_db_state', 'pro_tests_fixtures')
|
2021-05-05 15:25:27 +03:00
|
|
|
class TestDisableGraphQLIntrospection:
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dir(cls):
|
|
|
|
return "queries/graphql_introspection/disable_introspection"
|
|
|
|
|
2021-07-01 12:20:53 +03:00
|
|
|
setup_metadata_api_version = "v2"
|
|
|
|
|
2021-05-05 15:25:27 +03:00
|
|
|
def test_disable_introspection(self, hge_ctx):
|
|
|
|
check_query_f(hge_ctx, self.dir() + "/disable_introspection.yaml")
|
2022-02-10 09:31:44 +03:00
|
|
|
|
|
|
|
@pytest.mark.usefixtures('per_class_tests_db_state')
|
|
|
|
class TestGraphQlIntrospectionDescriptions:
|
|
|
|
|
|
|
|
setup_metadata_api_version = "v2"
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dir(cls):
|
|
|
|
return "queries/graphql_introspection/descriptions"
|
|
|
|
|
|
|
|
def test_automatic_comment_in_db(self, hge_ctx):
|
|
|
|
check_query_f(hge_ctx, self.dir() + "/automatic_comment_in_db.yaml")
|
|
|
|
|
|
|
|
def test_automatic_no_comment_in_db(self, hge_ctx):
|
|
|
|
check_query_f(hge_ctx, self.dir() + "/automatic_no_comment_in_db.yaml")
|
|
|
|
|
|
|
|
def test_explicit_comment_in_metadata(self, hge_ctx):
|
|
|
|
check_query_f(hge_ctx, self.dir() + "/explicit_comment_in_metadata.yaml")
|
|
|
|
|
|
|
|
def test_explicit_no_comment_in_metadata(self, hge_ctx):
|
|
|
|
check_query_f(hge_ctx, self.dir() + "/explicit_no_comment_in_metadata.yaml")
|
2022-02-28 10:49:13 +03:00
|
|
|
|
|
|
|
def test_root_fields(self, hge_ctx):
|
|
|
|
check_query_f(hge_ctx, self.dir() + "/root_fields.yaml")
|