From 0ffb0478b9b7d2e94a03a8f0e4055eb3ceee5664 Mon Sep 17 00:00:00 2001 From: nizar-m Date: Sun, 28 Oct 2018 23:57:49 +0530 Subject: [PATCH] Tests for server with access control, and some more tests (#710) * 1) Tests for creating permissions 2) Test for constraint_on with GraphQL insert on_conflict * Run tests with access key and webhook * Tests for GraphQL query with quoted columns * Rewrite test-server.sh so that it can be run locally * JWT based tests * Tests with various postgres types * For tests on select queries, run setup only once per class * Tests for v1 count queries * Skip teardown for tests that does not modify data * Workaround for hpc 'parse error when reading .tix file' * Move GeoJson tests to the new structure * Basic tests for v1 queries * Tests for column, table or operator not found error cases on GraphQL queries * Skip test teardown for mutation tests which does not change database state, even when it returns 200. --- .circleci/config.yml | 31 +- .circleci/test-server.sh | 206 ++++++++ .gitignore | 1 + server/tests-py/.gitignore | 6 +- server/tests-py/conftest.py | 30 +- server/tests-py/context.py | 20 +- server/tests-py/pytest.ini | 2 + server/tests-py/queries/basic/setup.yaml | 26 - server/tests-py/queries/basic/teardown.yaml | 9 - .../insert/basic/author_article.yaml | 11 + ...nsert_into_array_col_with_array_input.yaml | 39 ++ .../basic/insert_various_postgres_types.yaml | 159 ++++++ .../insert/basic/order_col_shipped_null.yaml | 43 +- .../insert/basic/person_jsonb_variable.yaml | 51 +- .../basic/person_jsonb_variable_array.yaml | 57 ++- .../graphql_mutation/insert/basic/setup.yaml | 77 +++ .../insert/basic/teardown.yaml | 18 + .../insert/geojson/insert_area.yaml | 76 +-- .../insert/geojson/insert_compounds.yaml | 88 ++-- .../geojson/insert_drone_3d_location.yaml | 60 ++- .../geojson/insert_geometry_collection.yaml | 140 ++--- .../insert/geojson/insert_landmark.yaml | 88 ++-- .../insert/geojson/insert_road.yaml | 70 +-- .../insert/geojson/insert_route.yaml | 78 +-- .../geojson/insert_service_locations.yaml | 66 +-- ...onflict_constraint_on_user_role_error.yaml | 26 + ..._article_author_where_on_relationship.yaml | 31 ++ ...lect_query_author_col_not_present_err.yaml | 18 + .../basic/select_query_author_col_quoted.yaml | 21 + .../select_query_non_tracked_table_err.yaml | 17 + .../basic/select_query_test_types.yaml | 109 ++++ .../queries/graphql_query/basic/setup.yaml | 180 ++++++- .../queries/graphql_query/basic/teardown.yaml | 16 + ...hor_article_operator_ne_not_found_err.yaml | 23 + ...icle_unexpected_operator_in_where_err.yaml | 24 + .../v1/basic/query_args_as_string_err.yaml | 11 + .../v1/basic/query_string_input_err.yaml | 11 + .../v1/basic/query_unknown_type_err.yaml | 7 + server/tests-py/queries/v1/basic/setup.yaml | 94 ++++ .../tests-py/queries/v1/basic/teardown.yaml | 10 + ..._articles_with_non_registered_authors.yaml | 12 + ...ount_articles_with_registered_authors.yaml | 12 + .../queries/v1/count/basic/count_authors.yaml | 9 + ...tinct_authors_with_published_articles.yaml | 13 + ...nct_authors_with_unpublished_articles.yaml | 13 + .../count_distinct_col_not_present_err.yaml | 15 + .../count/basic/count_published_articles.yaml | 11 + .../basic/count_unpublished_articles.yaml | 11 + .../queries/v1/count/basic/setup.yaml | 94 ++++ .../queries/v1/count/basic/teardown.yaml | 10 + .../count_user_has_no_select_perm_error.yaml | 16 + .../count_users_unpublished_articles.yaml | 14 + .../queries/v1/count/permissions/setup.yaml | 185 +++++++ .../v1/count/permissions/teardown.yaml | 14 + ...e_article_permission_role_admin_error.yaml | 17 + .../create_article_permission_role_user.yaml | 26 + .../queries/v1/permissions/setup.yaml | 30 ++ .../queries/v1/permissions/teardown.yaml | 13 + ...select_query_article_author_rel_alias.yaml | 35 ++ .../select_article_table_not_present_err.yaml | 14 + server/tests-py/requirements.txt | 3 + server/tests-py/super_classes.py | 45 ++ server/tests-py/test_graphql_introspection.py | 60 +-- server/tests-py/test_graphql_mutations.py | 352 ++++++------- server/tests-py/test_graphql_queries.py | 268 +++++----- server/tests-py/test_jwt.py | 97 ++++ server/tests-py/test_subscriptions.py | 21 +- server/tests-py/test_v1_queries.py | 483 +++++++++--------- server/tests-py/test_webhook_insecure.py | 18 + server/tests-py/validate.py | 96 +++- server/tests-py/webhook.py | 64 +++ server/tests-py/webhook/insecure/setup.yaml | 170 ++++++ .../tests-py/webhook/insecure/teardown.yaml | 14 + ...y_other_users_published_articles_fail.yaml | 26 + ...elect_query_unpublished_articles_fail.yaml | 22 + 75 files changed, 3305 insertions(+), 1048 deletions(-) create mode 100755 .circleci/test-server.sh create mode 100644 server/tests-py/pytest.ini delete mode 100644 server/tests-py/queries/basic/setup.yaml delete mode 100644 server/tests-py/queries/basic/teardown.yaml create mode 100644 server/tests-py/queries/graphql_mutation/insert/basic/insert_into_array_col_with_array_input.yaml create mode 100644 server/tests-py/queries/graphql_mutation/insert/basic/insert_various_postgres_types.yaml create mode 100644 server/tests-py/queries/graphql_mutation/insert/permissions/article_on_conflict_constraint_on_user_role_error.yaml create mode 100644 server/tests-py/queries/graphql_query/basic/nested_select_query_article_author_where_on_relationship.yaml create mode 100644 server/tests-py/queries/graphql_query/basic/select_query_author_col_not_present_err.yaml create mode 100644 server/tests-py/queries/graphql_query/basic/select_query_author_col_quoted.yaml create mode 100644 server/tests-py/queries/graphql_query/basic/select_query_non_tracked_table_err.yaml create mode 100644 server/tests-py/queries/graphql_query/basic/select_query_test_types.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/basic/select_author_article_operator_ne_not_found_err.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/basic/select_author_article_unexpected_operator_in_where_err.yaml create mode 100644 server/tests-py/queries/v1/basic/query_args_as_string_err.yaml create mode 100644 server/tests-py/queries/v1/basic/query_string_input_err.yaml create mode 100644 server/tests-py/queries/v1/basic/query_unknown_type_err.yaml create mode 100644 server/tests-py/queries/v1/basic/setup.yaml create mode 100644 server/tests-py/queries/v1/basic/teardown.yaml create mode 100644 server/tests-py/queries/v1/count/basic/count_articles_with_non_registered_authors.yaml create mode 100644 server/tests-py/queries/v1/count/basic/count_articles_with_registered_authors.yaml create mode 100644 server/tests-py/queries/v1/count/basic/count_authors.yaml create mode 100644 server/tests-py/queries/v1/count/basic/count_distinct_authors_with_published_articles.yaml create mode 100644 server/tests-py/queries/v1/count/basic/count_distinct_authors_with_unpublished_articles.yaml create mode 100644 server/tests-py/queries/v1/count/basic/count_distinct_col_not_present_err.yaml create mode 100644 server/tests-py/queries/v1/count/basic/count_published_articles.yaml create mode 100644 server/tests-py/queries/v1/count/basic/count_unpublished_articles.yaml create mode 100644 server/tests-py/queries/v1/count/basic/setup.yaml create mode 100644 server/tests-py/queries/v1/count/basic/teardown.yaml create mode 100644 server/tests-py/queries/v1/count/permissions/count_user_has_no_select_perm_error.yaml create mode 100644 server/tests-py/queries/v1/count/permissions/count_users_unpublished_articles.yaml create mode 100644 server/tests-py/queries/v1/count/permissions/setup.yaml create mode 100644 server/tests-py/queries/v1/count/permissions/teardown.yaml create mode 100644 server/tests-py/queries/v1/permissions/create_article_permission_role_admin_error.yaml create mode 100644 server/tests-py/queries/v1/permissions/create_article_permission_role_user.yaml create mode 100644 server/tests-py/queries/v1/permissions/setup.yaml create mode 100644 server/tests-py/queries/v1/permissions/teardown.yaml create mode 100644 server/tests-py/queries/v1/select/basic/nested_select_query_article_author_rel_alias.yaml create mode 100644 server/tests-py/queries/v1/select/basic/select_article_table_not_present_err.yaml create mode 100644 server/tests-py/super_classes.py create mode 100644 server/tests-py/test_jwt.py create mode 100644 server/tests-py/test_webhook_insecure.py create mode 100644 server/tests-py/webhook.py create mode 100644 server/tests-py/webhook/insecure/setup.yaml create mode 100644 server/tests-py/webhook/insecure/teardown.yaml create mode 100644 server/tests-py/webhook/insecure/user_query_other_users_published_articles_fail.yaml create mode 100644 server/tests-py/webhook/insecure/user_select_query_unpublished_articles_fail.yaml diff --git a/.circleci/config.yml b/.circleci/config.yml index c6f4f0d9bd7..84b8793e193 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -90,34 +90,19 @@ refs: at: /build - *wait_for_postgres - run: - name: run the server in background - working_directory: ./server + name: Run Python tests environment: - HASURA_GRAPHQL_DATABASE_URL: postgres://gql_test:@localhost:5432/gql_test - EVENT_WEBHOOK_HEADER: MyEnvValue - background: true + HASURA_GRAPHQL_DATABASE_URL: 'postgres://gql_test:@localhost:5432/gql_test' + GRAPHQL_ENGINE: '/build/_server_output/graphql-engine' command: | - /build/_server_output/graphql-engine serve + apt-get update + apt install --yes jq + OUTPUT_FOLDER=/build/_server_test_output/$PG_VERSION .circleci/test-server.sh - run: - name: create test output dir - command: | - mkdir -p /build/_server_test_output/$PG_VERSION - - *wait_for_hge - - run: - name: pytest the server - working_directory: ./server/tests-py - environment: - DATABASE_URL: postgres://gql_test:@localhost:5432/gql_test - HGE_URL: http://localhost:8080 - command: | - pip3 install -r requirements.txt - pytest -vv --hge-url="$HGE_URL" --pg-url="$DATABASE_URL" - - run: - name: stop the server and generate coverage report + name: Generate coverage report working_directory: ./server command: | - kill -2 $(ps -e | grep graphql-engine | awk '{print $1}') - stack --system-ghc hpc report graphql-engine.tix --destdir /build/_server_test_output/$PG_VERSION + stack --system-ghc hpc report /build/_server_test_output/$PG_VERSION/graphql-engine.tix --destdir /build/_server_test_output/$PG_VERSION - store_artifacts: path: /build/_server_test_output destination: server_test diff --git a/.circleci/test-server.sh b/.circleci/test-server.sh new file mode 100755 index 00000000000..fb1b37989aa --- /dev/null +++ b/.circleci/test-server.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash +set -euo pipefail + +### Functions + +stop_services() { + kill -INT $PID + kill $WH_PID +} + +wait_for_port() { + local PORT=$1 + echo "waiting for $PORT" + for _ in $(seq 1 60); + do + nc -z localhost $PORT && echo "port $PORT is ready" && return + echo -n . + sleep 1 + done + echo "Failed waiting for $PORT" && exit 1 +} + +init_jwt() { + CUR_DIR="$PWD" + mkdir -p "$OUTPUT_FOLDER/ssl" + cd "$OUTPUT_FOLDER/ssl" + openssl genrsa -out jwt_private.key 2048 + openssl rsa -pubout -in jwt_private.key -out jwt_public.key + cd "$CUR_DIR" +} + +init_ssl() { + CUR_DIR="$PWD" + mkdir -p "$OUTPUT_FOLDER/ssl" + cd "$OUTPUT_FOLDER/ssl" + CNF_TEMPLATE='[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name + +[req_distinguished_name] + +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = localhost +IP.1 = 127.0.0.1' + + echo "$CNF_TEMPLATE" > webhook-req.cnf + + openssl genrsa -out ca-key.pem 2048 + openssl req -x509 -new -nodes -key ca-key.pem -days 10 -out ca.pem -subj "/CN=webhook-ca" + openssl genrsa -out webhook-key.pem 2048 + openssl req -new -key webhook-key.pem -out webhook.csr -subj "/CN=hge-webhook" -config webhook-req.cnf + openssl x509 -req -in webhook.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out webhook.pem -days 10 -extensions v3_req -extfile webhook-req.cnf + + cp ca.pem /etc/ssl/certs/webhook.crt + update-ca-certificates + cd "$CUR_DIR" +} + +combine_hpc_reports() { + (stack --allow-different-user exec -- hpc combine graphql-engine.tix graphql-engine-combined.tix --union > graphql-engine-combined.tix2 && mv graphql-engine-combined.tix2 graphql-engine-combined.tix ) || true + rm graphql-engine.tix || true +} + +if [ -z "${HASURA_GRAPHQL_DATABASE_URL:-}" ] ; then + echo "Env var HASURA_GRAPHQL_DATABASE_URL is not set" + exit 1 +fi + +if ! stack --allow-different-user exec which hpc ; then + echo "hpc not found; Install it with 'stack install hpc'" + exit 1 +fi + +CIRCLECI_FOLDER="${BASH_SOURCE[0]%/*}" +cd $CIRCLECI_FOLDER +CIRCLECI_FOLDER="$PWD" + +PYTEST_ROOT="$CIRCLECI_FOLDER/../server/tests-py" + +OUTPUT_FOLDER=${OUTPUT_FOLDER:-"$CIRCLECI_FOLDER/test-server-output"} +mkdir -p "$OUTPUT_FOLDER" + +cd $PYTEST_ROOT + +if ! stack --allow-different-user exec -- which graphql-engine > /dev/null && [ -z "${GRAPHQL_ENGINE:-}" ] ; then + echo "Do 'stack build' before tests, or export the location of executable in the GRAPHQL_ENGINE envirnoment variable" + exit 1 +fi +GRAPHQL_ENGINE=${GRAPHQL_ENGINE:-"$(stack --allow-different-user exec -- which graphql-engine)"} +if ! [ -x "$GRAPHQL_ENGINE" ] ; then + echo "$GRAPHQL_ENGINE is not present or is not an executable" + exit 1 +fi +RUN_WEBHOOK_TESTS=true + +echo -e "\nINFO: GraphQL Executable : $GRAPHQL_ENGINE" +echo -e "INFO: Logs Folder : $OUTPUT_FOLDER\n" + +pip3 install -r requirements.txt + +mkdir -p "$OUTPUT_FOLDER" + +export EVENT_WEBHOOK_HEADER="MyEnvValue" +export HGE_URL="http://localhost:8080" + +PID="" +WH_PID="" +trap stop_services ERR +trap stop_services INT + +echo -e "\n<########## TEST GRAPHQL-ENGINE WITHOUT ACCESS KEYS ###########################################>\n" + +"$GRAPHQL_ENGINE" serve > "$OUTPUT_FOLDER/graphql-engine.log" & PID=$! + +wait_for_port 8080 + +pytest -vv --hge-url="$HGE_URL" --pg-url="$HASURA_GRAPHQL_DATABASE_URL" + +kill -INT $PID +sleep 4 +mv graphql-engine.tix graphql-engine-combined.tix + +########## +echo -e "\n<########## TEST GRAPHQL-ENGINE WITH ACCESS KEY #####################################>\n" + +export HASURA_GRAPHQL_ACCESS_KEY="HGE$RANDOM$RANDOM" + +"$GRAPHQL_ENGINE" serve >> "$OUTPUT_FOLDER/graphql-engine.log" & PID=$! + +wait_for_port 8080 + +pytest -vv --hge-url="$HGE_URL" --pg-url="$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ACCESS_KEY" + +kill -INT $PID +sleep 4 +combine_hpc_reports + +########## +echo -e "\n<########## TEST GRAPHQL-ENGINE WITH ACCESS KEY AND JWT #####################################>\n" + +init_jwt + +export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key }')" + +"$GRAPHQL_ENGINE" serve >> "$OUTPUT_FOLDER/graphql-engine.log" & PID=$! + +pytest -vv --hge-url="$HGE_URL" --pg-url="$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ACCESS_KEY" --hge-jwt-key-file="$OUTPUT_FOLDER/ssl/jwt_private.key" + +kill -INT $PID +sleep 4 +combine_hpc_reports + +unset HASURA_GRAPHQL_JWT_SECRET + +########## + +if [ $EUID != 0 ] ; then + echo -e "SKIPPING webhook based tests, as \nroot permission is required for running webhook tests (inorder to trust certificate authority)." + RUN_WEBHOOK_TESTS=false +fi + +if [ "$RUN_WEBHOOK_TESTS" == "true" ] ; then + + echo -e "\n<########## TEST GRAPHQL-ENGINE WITH ACCESS KEY & WEBHOOK #########################>\n" + + export HASURA_GRAPHQL_AUTH_HOOK="https://localhost:9090/" + init_ssl + + "$GRAPHQL_ENGINE" serve >> "$OUTPUT_FOLDER/graphql-engine.log" 2>&1 & PID=$! + + python3 webhook.py 9090 "$OUTPUT_FOLDER/ssl/webhook-key.pem" "$OUTPUT_FOLDER/ssl/webhook.pem" > "$OUTPUT_FOLDER/webhook.log" 2>&1 & WH_PID=$! + + wait_for_port 8080 + + wait_for_port 9090 + + pytest -vv --hge-url="$HGE_URL" --pg-url="$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ACCESS_KEY" --hge-webhook="$HASURA_GRAPHQL_AUTH_HOOK" + + rm /etc/ssl/certs/webhook.crt + update-ca-certificates + + kill -INT $PID + sleep 4 + combine_hpc_reports + + echo -e "\n<########## TEST GRAPHQL-ENGINE WITH ACCESS KEY & HTTPS INSECURE WEBHOOK ########>\n" + + "$GRAPHQL_ENGINE" serve >> "$OUTPUT_FOLDER/graphql-engine.log" 2>&1 & PID=$! + + wait_for_port 8080 + + pytest -vv --hge-url="$HGE_URL" --pg-url="$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ACCESS_KEY" --hge-webhook="$HASURA_GRAPHQL_AUTH_HOOK" --test-webhook-insecure test_webhook_insecure.py + + kill -INT $PID + sleep 4 + combine_hpc_reports + + kill $WH_PID +fi + +mv graphql-engine-combined.tix "$OUTPUT_FOLDER/graphql-engine.tix" diff --git a/.gitignore b/.gitignore index 76d18d38198..08c06429a65 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ npm-debug.log *.temp *.DS_Store .tern-project +test-server-output diff --git a/server/tests-py/.gitignore b/server/tests-py/.gitignore index 216e9087c9e..3b372242ffb 100644 --- a/server/tests-py/.gitignore +++ b/server/tests-py/.gitignore @@ -128,5 +128,9 @@ dmypy.json pyvenv.cfg pip-selfcheck.json - # End of https://www.gitignore.io/api/python + +*.pem +ca.srl +webhook-req.cnf +webhook.csr diff --git a/server/tests-py/conftest.py b/server/tests-py/conftest.py index 7fa61e24d63..1a583a2183a 100644 --- a/server/tests-py/conftest.py +++ b/server/tests-py/conftest.py @@ -9,17 +9,45 @@ def pytest_addoption(parser): parser.addoption( "--pg-url", metavar="PG_URL", help="url for connecting to Postgres directly", required=True ) + parser.addoption( + "--hge-key", metavar="HGE_KEY", help="access key for graphql-engine", required=False + ) + parser.addoption( + "--hge-webhook", metavar="HGE_WEBHOOK", help="url for graphql-engine's access control webhook", required=False + ) + parser.addoption( + "--test-webhook-insecure", action="store_true", + help="Run Test cases for insecure https webhook" + ) + parser.addoption( + "--hge-jwt-key-file", metavar="HGE_JWT_KEY_FILE", help="File containting the private key used to encode jwt tokens using RS512 algorithm", required=False + ) @pytest.fixture(scope='session') def hge_ctx(request): print ("create hge_ctx") hge_url = request.config.getoption('--hge-url') pg_url = request.config.getoption('--pg-url') + hge_key = request.config.getoption('--hge-key') + hge_webhook = request.config.getoption('--hge-webhook') + webhook_insecure = request.config.getoption('--test-webhook-insecure') + hge_jwt_key_file = request.config.getoption('--hge-jwt-key-file') try: - hge_ctx = HGECtx(hge_url=hge_url, pg_url=pg_url) + hge_ctx = HGECtx(hge_url=hge_url, pg_url=pg_url, hge_key=hge_key, hge_webhook=hge_webhook, hge_jwt_key_file=hge_jwt_key_file, webhook_insecure = webhook_insecure ) except HGECtxError as e: pytest.exit(str(e)) yield hge_ctx # provide the fixture value print("teardown hge_ctx") hge_ctx.teardown() time.sleep(2) + +@pytest.fixture(scope='class') +def setup_ctrl(request, hge_ctx): + """ + This fixure is used to store the state of test setup in some test classes. + Used primarily when teardown is skipped in some test cases in the class where the test is not expected to change the database state. + """ + setup_ctrl = { "setupDone" : False } + yield setup_ctrl + hge_ctx.may_skip_test_teardown = False + request.cls().do_teardown(setup_ctrl, hge_ctx) diff --git a/server/tests-py/context.py b/server/tests-py/context.py index 27a9ece5d51..5397b80463c 100644 --- a/server/tests-py/context.py +++ b/server/tests-py/context.py @@ -53,7 +53,7 @@ class WebhookServer(http.server.HTTPServer): self.socket.bind(self.server_address) class HGECtx: - def __init__(self, hge_url, pg_url): + def __init__(self, hge_url, pg_url, hge_key, hge_webhook, hge_jwt_key_file, webhook_insecure): server_address = ('0.0.0.0', 5592) self.resp_queue = queue.Queue(maxsize=1) @@ -69,6 +69,15 @@ class HGECtx: self.http = requests.Session() self.hge_url = hge_url + self.hge_key = hge_key + self.hge_webhook = hge_webhook + if hge_jwt_key_file is None: + self.hge_jwt_key = None + else: + with open(hge_jwt_key_file) as f: + self.hge_jwt_key = f.read() + self.webhook_insecure = webhook_insecure + self.may_skip_test_teardown = False self.ws_url = urlparse(hge_url) self.ws_url = self.ws_url._replace(scheme='ws') @@ -78,7 +87,7 @@ class HGECtx: self.wst.daemon = True self.wst.start() - result = subprocess.run(['../../scripts/get-version.sh'], shell=True, stdout=subprocess.PIPE, check=True) + result = subprocess.run(['../../scripts/get-version.sh'], shell=False, stdout=subprocess.PIPE, check=True) self.version = result.stdout.decode('utf-8').strip() try: st_code, resp = self.v1q_f('queries/clear_db.yaml') @@ -108,6 +117,7 @@ class HGECtx: def reflect_tables(self): self.meta.reflect(bind=self.engine) + def anyq(self, u, q, h): resp = self.http.post( self.hge_url + u, @@ -117,9 +127,13 @@ class HGECtx: return resp.status_code, resp.json() def v1q(self, q): + h = dict() + if self.hge_key is not None: + h['X-Hasura-Access-Key'] = self.hge_key resp = self.http.post( self.hge_url + "/v1/query", - json=q + json=q, + headers=h ) return resp.status_code, resp.json() diff --git a/server/tests-py/pytest.ini b/server/tests-py/pytest.ini new file mode 100644 index 00000000000..1b6eec80c23 --- /dev/null +++ b/server/tests-py/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +norecursedirs = queries webhook diff --git a/server/tests-py/queries/basic/setup.yaml b/server/tests-py/queries/basic/setup.yaml deleted file mode 100644 index 200e42aa40a..00000000000 --- a/server/tests-py/queries/basic/setup.yaml +++ /dev/null @@ -1,26 +0,0 @@ -type: bulk -args: -- type: run_sql - args: - sql: | - create table hge_tests.test_t1( - c1 int, - c2 text - ); -- type: track_table - args: - schema: hge_tests - name: test_t1 -- type: create_event_trigger - args: - name: t1_all - table: - schema: hge_tests - name: test_t1 - insert: - columns: "*" - update: - columns: "*" - delete: - columns: "*" - webhook: http://127.0.0.1:5592 diff --git a/server/tests-py/queries/basic/teardown.yaml b/server/tests-py/queries/basic/teardown.yaml deleted file mode 100644 index a0766f4d43c..00000000000 --- a/server/tests-py/queries/basic/teardown.yaml +++ /dev/null @@ -1,9 +0,0 @@ -type: bulk -args: -- type: delete_event_trigger - args: - name: t1_all -- type: run_sql - args: - sql: | - drop table hge_tests.test_t1 diff --git a/server/tests-py/queries/graphql_mutation/insert/basic/author_article.yaml b/server/tests-py/queries/graphql_mutation/insert/basic/author_article.yaml index 6721b6d2a73..4be1580abcc 100644 --- a/server/tests-py/queries/graphql_mutation/insert/basic/author_article.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/basic/author_article.yaml @@ -76,3 +76,14 @@ } } } +- description: Delete the inserted articles + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from article; + delete from author; + SELECT setval('article_id_seq', 1, FALSE); + SELECT setval('author_id_seq', 1, FALSE); diff --git a/server/tests-py/queries/graphql_mutation/insert/basic/insert_into_array_col_with_array_input.yaml b/server/tests-py/queries/graphql_mutation/insert/basic/insert_into_array_col_with_array_input.yaml new file mode 100644 index 00000000000..e5a4dfc2415 --- /dev/null +++ b/server/tests-py/queries/graphql_mutation/insert/basic/insert_into_array_col_with_array_input.yaml @@ -0,0 +1,39 @@ + #Inserting data into test_types table +- description: Inserts Array into an Array column + url: /v1alpha1/graphql + response: + data: + insert_test_types: + returning: + c34_text_array: ["a","b","c"] + status: 200 + query: + variables: + textArray: ["a","b","c"] + query: | + mutation insert_test_types + ($textArray: [String]) + { + insert_test_types( + objects: [ + { + c34_text_array: $textArray + } + ] + ) { + returning { + c34_text_array + } + } + } +- description: Delete the inserted test_types rows + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from test_types; + SELECT setval('test_types_c10_bigserial_seq', 1, FALSE); + SELECT setval('test_types_c9_serial_seq', 1, FALSE); + SELECT setval('test_types_c8_smallserial_seq', 1, FALSE); diff --git a/server/tests-py/queries/graphql_mutation/insert/basic/insert_various_postgres_types.yaml b/server/tests-py/queries/graphql_mutation/insert/basic/insert_various_postgres_types.yaml new file mode 100644 index 00000000000..6fa428d3f21 --- /dev/null +++ b/server/tests-py/queries/graphql_mutation/insert/basic/insert_various_postgres_types.yaml @@ -0,0 +1,159 @@ + #Inserting data into test_types table +- description: Inserts data into test_types table with various postgres types + url: /v1alpha1/graphql + response: + data: + insert_test_types: + returning: + - c1_smallint: 32767 + c2_integer: 2147483647 + c3_bigint: "9223372036854775807" + c4_decimal: 123.45 + c5_numeric: 1.234 + c6_real: 0.00390625 + c7_double_precision: 16.0001220703125 + c8_smallserial: 1 + c9_serial: 1 + c10_bigserial: "1" + c11_varchar_3: "abc" + c12_char_4: "baaz" + c13_text: "foo bar baz" + c14_timestamp: "2004-10-19T10:23:54" + c15_timestamptz: "2015-10-17T14:42:43+00:00" + c16_date: '2014-09-14' + c17_time: '11:09:23' + c18_time_with_zone: '15:22:23+00' + c19_interval: '01:03:02' + c20_boolean: true + c21_point: '(1,2)' + c22_line: '{2,3,-1}' + c23_lseg: '[(4,2),(3,1)]' + c24_box: '(31,12),(14,11)' + c25_closed_path: '((0,0),(0,3),(1,0))' + c26_open_path: '[(0,0),(0,-1),(-3,0)]' + c27_polygon: '((0,0),(0,6),(2,0))' + c28_circle: '<(-2,-3),3>' + c29_cidr: '192.168.100.128/25' + c30_inet: '198.24.10.0' + c31_macaddr: '08:00:2b:01:02:03' + c32_json: + a: b + c33_jsonb: + c: d + c36_uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 + c39_range_integer: '[123,456)' + c40_range_bigint: '[1147483647,2147483647)' + c41_range_numeric: '[1.23,4.56]' + c42_range_timestamp: '["2010-01-01 14:30:00","2010-01-01 15:30:02")' + c43_range_timestamptz: '("2011-02-05 12:03:00+00","2012-03-04 16:40:04+00"]' + c44_xml: 'bar' + status: 200 + query: + variables: + json: + a: b + jsonb: + c: d + query: | + mutation insert_test_types + ( $json: json + , $jsonb: jsonb + , $compositeComplex: complex + ) { + insert_test_types( + objects: [ + { c1_smallint: 32767 + , c2_integer: 2147483647 + , c3_bigint: "9223372036854775807" + , c4_decimal: 123.45 + , c5_numeric: 1.234 + , c6_real: 0.00390625 + , c7_double_precision: 16.0001220703125 + , c11_varchar_3: "abc" + , c12_char_4: "baaz" + , c13_text: "foo bar baz" + , c14_timestamp: "2004-10-19T10:23:54" + , c15_timestamptz: "2015-10-17T14:42:43+00:00" + , c16_date: "2014-09-14" + , c17_time: "11:09:23" + , c18_time_with_zone: "15:22:23+00" + , c19_interval: "01:03:02" + , c20_boolean: true + , c21_point: "(1,2)" + , c22_line: "{2,3,-1}" + , c23_lseg: "[(4,2),(3,1)]" + , c24_box: "(31,12),(14,11)" + , c25_closed_path: "((0,0),(0,3),(1,0))" + , c26_open_path: "[(0,0),(0,-1),(-3,0)]" + , c27_polygon: "((0,0),(0,6),(2,0))" + , c28_circle: "<(-2,-3),3>" + , c29_cidr: "192.168.100.128/25" + , c30_inet: "198.24.10.0" + , c31_macaddr: "08:00:2b:01:02:03" + , c32_json: $json + , c33_jsonb: $jsonb + , c36_uuid: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11" + , c39_range_integer: "[123,456)" + , c40_range_bigint: "[1147483647,2147483647)" + , c41_range_numeric: "[1.23,4.56]" + , c42_range_timestamp: "[\"2010-01-01 14:30:00\",\"2010-01-01 15:30:02\")" + , c43_range_timestamptz: "(\"2011-02-05 12:03:00+00\",\"2012-03-04 16:40:04+00\"]" + , c44_xml: "bar" + } + ] + ) { + returning { + c1_smallint + c2_integer + c3_bigint + c4_decimal + c5_numeric + c6_real + c7_double_precision + c8_smallserial + c9_serial + c10_bigserial + c11_varchar_3 + c12_char_4 + c13_text + c14_timestamp + c15_timestamptz + c16_date + c17_time + c18_time_with_zone + c19_interval + c20_boolean + c21_point + c22_line + c23_lseg + c24_box + c25_closed_path + c26_open_path + c27_polygon + c28_circle + c29_cidr + c30_inet + c31_macaddr + c32_json + c33_jsonb + c36_uuid + c39_range_integer + c40_range_bigint + c41_range_numeric + c42_range_timestamp + c43_range_timestamptz + c44_xml + } + } + } +- description: Delete the inserted rows in test_types table + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from test_types; + SELECT setval('test_types_c10_bigserial_seq', 1, FALSE); + SELECT setval('test_types_c9_serial_seq', 1, FALSE); + SELECT setval('test_types_c8_smallserial_seq', 1, FALSE); diff --git a/server/tests-py/queries/graphql_mutation/insert/basic/order_col_shipped_null.yaml b/server/tests-py/queries/graphql_mutation/insert/basic/order_col_shipped_null.yaml index 64ec7609350..088fafdf186 100644 --- a/server/tests-py/queries/graphql_mutation/insert/basic/order_col_shipped_null.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/basic/order_col_shipped_null.yaml @@ -1,20 +1,29 @@ -description: Insert into order table with a null value -url: /v1alpha1/graphql -status: 200 -query: - query: | - mutation insert_orders{ - insert_orders( - objects: [ - { - placed: "2017-08-19 14:22:11.802755+02", - shipped: null +- description: Insert into order table with a null value + url: /v1alpha1/graphql + status: 200 + query: + query: | + mutation insert_orders{ + insert_orders( + objects: [ + { + placed: "2017-08-19 14:22:11.802755+02", + shipped: null + } + ] + ) { + returning { + id } - ] - ) { - returning { - id + affected_rows } - affected_rows } - } +- description: Delete the inserted orders + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from orders; + SELECT setval('orders_id_seq', 1, FALSE); diff --git a/server/tests-py/queries/graphql_mutation/insert/basic/person_jsonb_variable.yaml b/server/tests-py/queries/graphql_mutation/insert/basic/person_jsonb_variable.yaml index 19333d2b709..3554234bb5a 100644 --- a/server/tests-py/queries/graphql_mutation/insert/basic/person_jsonb_variable.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/basic/person_jsonb_variable.yaml @@ -1,24 +1,33 @@ -description: Inserts person data via GraphQL mutation -url: /v1alpha1/graphql -status: 200 -query: - variables: - value: - name: - first: john - last: murphy - query: | - mutation insert_person($value: jsonb) { - insert_person( - objects: [ - { - details: $value +- description: Inserts person data via GraphQL mutation + url: /v1alpha1/graphql + status: 200 + query: + variables: + value: + name: + first: john + last: murphy + query: | + mutation insert_person($value: jsonb) { + insert_person( + objects: [ + { + details: $value + } + ] + ) { + returning { + id + details } - ] - ) { - returning { - id - details } } - } +- description: Delete the inserted persons + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from person; + SELECT setval('person_id_seq', 1, FALSE); diff --git a/server/tests-py/queries/graphql_mutation/insert/basic/person_jsonb_variable_array.yaml b/server/tests-py/queries/graphql_mutation/insert/basic/person_jsonb_variable_array.yaml index ad84a17e3ea..6d238810812 100644 --- a/server/tests-py/queries/graphql_mutation/insert/basic/person_jsonb_variable_array.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/basic/person_jsonb_variable_array.yaml @@ -1,27 +1,36 @@ -description: Inserts persons data via GraphQL mutation -url: /v1alpha1/graphql -status: 200 -query: - variables: - value: - - name: - first: thelonious - last: jaha - - name: - first: clarke - last: griffin - query: | - mutation insert_person($value: jsonb) { - insert_person( - objects: [ - { - details: $value +- description: Inserts persons data via GraphQL mutation + url: /v1alpha1/graphql + status: 200 + query: + variables: + value: + - name: + first: thelonious + last: jaha + - name: + first: clarke + last: griffin + query: | + mutation insert_person($value: jsonb) { + insert_person( + objects: [ + { + details: $value + } + ] + ) { + returning { + id + details } - ] - ) { - returning { - id - details } } - } +- description: Delete the inserted persons + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from person; + SELECT setval('person_id_seq', 1, FALSE); diff --git a/server/tests-py/queries/graphql_mutation/insert/basic/setup.yaml b/server/tests-py/queries/graphql_mutation/insert/basic/setup.yaml index 27e5bdcff59..73ef7433cd8 100644 --- a/server/tests-py/queries/graphql_mutation/insert/basic/setup.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/basic/setup.yaml @@ -77,3 +77,80 @@ args: table: article column: author_id +- type: run_sql + args: + sql: | + CREATE TYPE complex AS ( + r double precision, + i double precision + ); + +- type: run_sql + args: + sql: | + CREATE TYPE inventory_item AS ( + name text, + supplier_id integer, + price numeric + ); + +#Test table with different types +- type: run_sql + args: + sql: | + create table test_types ( + c1_smallint smallint, + c2_integer integer, + c3_bigint bigint, + c4_decimal decimal (5, 2), + c5_numeric numeric (4, 3), + c6_real real, + c7_double_precision double precision, + c8_smallserial smallserial primary key, + c9_serial serial, + c10_bigserial bigserial, + c11_varchar_3 varchar(3), + c12_char_4 char(4), + c13_text text, + c14_timestamp timestamp, + c15_timestamptz timestamptz, + c16_date date, + c17_time time, + c18_time_with_zone time with time zone, + c19_interval interval, + c20_boolean boolean, + c21_point point, + c22_line line, + c23_lseg lseg, + c24_box box, + c25_closed_path path, + c26_open_path path, + c27_polygon polygon, + c28_circle circle, + c29_cidr cidr, + c30_inet inet, + c31_macaddr macaddr, + c32_json json, + c33_jsonb jsonb, + c34_text_array text[], + c35_integer_2d_array integer[][], + c36_uuid uuid, + c37_composite_type_complex complex, + c38_composite_type_inventory inventory_item, + c39_range_integer int4range, + c40_range_bigint int8range, + c41_range_numeric numrange, + c42_range_timestamp tsrange, + c43_range_timestamptz tstzrange, + c44_xml xml + ); +- type: track_table + args: + schema: public + name: test_types + +#Set timezone +- type: run_sql + args: + sql: | + SET TIME ZONE 'UTC'; diff --git a/server/tests-py/queries/graphql_mutation/insert/basic/teardown.yaml b/server/tests-py/queries/graphql_mutation/insert/basic/teardown.yaml index eb80d35dcc6..1dec73a0cd2 100644 --- a/server/tests-py/queries/graphql_mutation/insert/basic/teardown.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/basic/teardown.yaml @@ -12,15 +12,33 @@ args: args: sql: | drop table person + - type: run_sql args: sql: | drop table orders + - type: run_sql args: sql: | drop table article + - type: run_sql args: sql: | drop table author + +- type: run_sql + args: + sql: | + drop table test_types + +- type: run_sql + args: + sql: | + drop type complex + +- type: run_sql + args: + sql: | + drop type inventory_item diff --git a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_area.yaml b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_area.yaml index 7cc0e94a11f..daf93b119f7 100644 --- a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_area.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_area.yaml @@ -1,36 +1,44 @@ -description: Insert area as a polygon -url: /v1alpha1/graphql -status: 200 -response: - data: - insert_area: - returning: - - id: 1 - name: Foo - area: &area - coordinates: &coords - - - - [43.75049, 11.03207] - - [43.80009, 11.03208] - - [42.60009, 10.13248] - - [43.75049, 11.03207] - type: Polygon - crs: &crs - type: name - properties: - name: 'urn:ogc:def:crs:EPSG::4326' -query: - variables: - areas: - - name: Foo - area: *area - query: | - mutation insertArea($areas: [area_insert_input!]!) { - insert_area(objects: $areas) { - returning{ - id - name - area +- description: Insert area as a polygon + url: /v1alpha1/graphql + status: 200 + response: + data: + insert_area: + returning: + - id: 1 + name: Foo + area: &area + coordinates: &coords + - + - [43.75049, 11.03207] + - [43.80009, 11.03208] + - [42.60009, 10.13248] + - [43.75049, 11.03207] + type: Polygon + crs: &crs + type: name + properties: + name: 'urn:ogc:def:crs:EPSG::4326' + query: + variables: + areas: + - name: Foo + area: *area + query: | + mutation insertArea($areas: [area_insert_input!]!) { + insert_area(objects: $areas) { + returning{ + id + name + area + } } } - } +- description: Delete the inserted area + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from area diff --git a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_compounds.yaml b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_compounds.yaml index a0124c9e6a8..7a68099da73 100644 --- a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_compounds.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_compounds.yaml @@ -1,44 +1,52 @@ -description: Insert the path for a curved road -url: /v1alpha1/graphql -status: 200 -response: - data: - insert_compounds: - returning: +- description: Insert the path for a road + url: /v1alpha1/graphql + status: 200 + response: + data: + insert_compounds: + returning: + - user_id: 1 + name: foo + areas: &areas + type: MultiPolygon + coordinates: + - - - [43.75049, 11.03207] + - [43.80009, 11.03208] + - [43.90009, 11.03308] + - [43.75049, 11.03207] + - - [43.75049, 11.03207] + - [41.60009, 21.03208] + - [41.70009, 21.03308] + - [43.75049, 11.03207] + - - - [23.75049, 31.03207] + - [23.80009, 31.03208] + - [23.80009, 31.03308] + - [23.75049, 31.03207] + crs: &crs + type: name + properties: + name: 'urn:ogc:def:crs:EPSG::4326' + query: + variables: + compounds: - user_id: 1 name: foo - areas: &areas - type: MultiPolygon - coordinates: - - - - [43.75049, 11.03207] - - [43.80009, 11.03208] - - [43.90009, 11.03308] - - [43.75049, 11.03207] - - - [43.75049, 11.03207] - - [41.60009, 21.03208] - - [41.70009, 21.03308] - - [43.75049, 11.03207] - - - - [23.75049, 31.03207] - - [23.80009, 31.03208] - - [23.80009, 31.03308] - - [23.75049, 31.03207] - crs: &crs - type: name - properties: - name: 'urn:ogc:def:crs:EPSG::4326' -query: - variables: - compounds: - - user_id: 1 - name: foo - areas: *areas - query: | - mutation insertCompounds($compounds: [compounds_insert_input!]!) { - insert_compounds(objects: $compounds) { - returning{ - user_id - name - areas + areas: *areas + query: | + mutation insertCompounds($compounds: [compounds_insert_input!]!) { + insert_compounds(objects: $compounds) { + returning{ + user_id + name + areas + } } } - } +- description: Delete the inserted compounds + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from compounds diff --git a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_drone_3d_location.yaml b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_drone_3d_location.yaml index 668bfb89e16..1875ce41963 100644 --- a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_drone_3d_location.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_drone_3d_location.yaml @@ -1,29 +1,37 @@ -description: Insert location of drone as geojson point -url: /v1alpha1/graphql -status: 200 -response: - data: - insert_drone_3d_location: - returning: +- description: Insert location of drone as geojson point + url: /v1alpha1/graphql + status: 200 + response: + data: + insert_drone_3d_location: + returning: + - drone_id: 1 + location: &loc + coordinates: [43.75049, 11.03207, 1.234] + type: Point + crs: + type: name + properties: + name: 'urn:ogc:def:crs:EPSG::4326' + query: + variables: + location: - drone_id: 1 - location: &loc - coordinates: [43.75049, 11.03207, 1.234] - type: Point - crs: - type: name - properties: - name: 'urn:ogc:def:crs:EPSG::4326' -query: - variables: - location: - - drone_id: 1 - location: *loc - query: | - mutation insertDroneLoc($location: [drone_3d_location_insert_input!]!) { - insert_drone_3d_location(objects: $location) { - returning{ - drone_id - location + location: *loc + query: | + mutation insertDroneLoc($location: [drone_3d_location_insert_input!]!) { + insert_drone_3d_location(objects: $location) { + returning{ + drone_id + location + } } } - } +- description: Delete the inserted drone location + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from drone_3d_location diff --git a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_geometry_collection.yaml b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_geometry_collection.yaml index 5a228f19122..27f1a806fa1 100644 --- a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_geometry_collection.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_geometry_collection.yaml @@ -1,71 +1,79 @@ -description: Insert a geometry collection -url: /v1alpha1/graphql -status: 200 -response: - data: - insert_geometry_collection: - returning: - - id: 1 - geometries: &geometry - type: GeometryCollection - geometries: - - type: Point - coordinates: [43.75049, 11.03207] - - - type: MultiPoint - coordinates: - - [43.75049, 11.03207] - - [43.80009, 11.03208] - - [43.80009, 11.03308] - - - type: LineString - coordinates: - - [43.75049, 11.03207] - - [43.80009, 11.03208] - - - type: MultiLineString - coordinates: - - - [43.75049, 11.03207] - - [43.80009, 11.03208] - - - [43.80009, 11.03208] - - [42.60009, 10.13248] - - - type: Polygon - coordinates: &coords - - - [43.75049, 11.03207] - - [43.80009, 11.03208] - - [42.60009, 10.13248] +- description: Insert a geometry collection + url: /v1alpha1/graphql + status: 200 + response: + data: + insert_geometry_collection: + returning: + - id: 1 + geometries: &geometry + type: GeometryCollection + geometries: + - type: Point + coordinates: [43.75049, 11.03207] + + - type: MultiPoint + coordinates: - [43.75049, 11.03207] - - - type: MultiPolygon - coordinates: - - - - [43.75049, 11.03207] - - [43.80009, 11.03208] - - [43.90009, 11.03308] - - [43.75049, 11.03207] + - [43.80009, 11.03208] + - [43.80009, 11.03308] + + - type: LineString + coordinates: + - [43.75049, 11.03207] + - [43.80009, 11.03208] + + - type: MultiLineString + coordinates: - - [43.75049, 11.03207] - - [41.60009, 21.03208] - - [41.70009, 21.03308] + - [43.80009, 11.03208] + - - [43.80009, 11.03208] + - [42.60009, 10.13248] + + - type: Polygon + coordinates: &coords + - - [43.75049, 11.03207] + - [43.80009, 11.03208] + - [42.60009, 10.13248] - [43.75049, 11.03207] - - - - [23.75049, 31.03207] - - [23.80009, 31.03208] - - [23.80009, 31.03308] - - [23.75049, 31.03207] - - crs: &crs - type: name - properties: - name: 'urn:ogc:def:crs:EPSG::4326' -query: - variables: - geometries: - - geometries: *geometry - query: | - mutation insertGeometries($geometries: [geometry_collection_insert_input!]!) { - insert_geometry_collection(objects: $geometries) { - returning{ - id - geometries + + - type: MultiPolygon + coordinates: + - - - [43.75049, 11.03207] + - [43.80009, 11.03208] + - [43.90009, 11.03308] + - [43.75049, 11.03207] + - - [43.75049, 11.03207] + - [41.60009, 21.03208] + - [41.70009, 21.03308] + - [43.75049, 11.03207] + - - - [23.75049, 31.03207] + - [23.80009, 31.03208] + - [23.80009, 31.03308] + - [23.75049, 31.03207] + + crs: &crs + type: name + properties: + name: 'urn:ogc:def:crs:EPSG::4326' + query: + variables: + geometries: + - geometries: *geometry + query: | + mutation insertGeometries($geometries: [geometry_collection_insert_input!]!) { + insert_geometry_collection(objects: $geometries) { + returning{ + id + geometries + } } } - } +- description: Delete the inserted geometry collection + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + DELETE from geometry_collection diff --git a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_landmark.yaml b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_landmark.yaml index e4e88c3a173..77f7d11869a 100644 --- a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_landmark.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_landmark.yaml @@ -1,44 +1,52 @@ -description: Insert landmarks -url: /v1alpha1/graphql -status: 200 -response: - data: - insert_landmark: - returning: - - id: 1 - name: Baz +- description: Insert landmarks + url: /v1alpha1/graphql + status: 200 + response: + data: + insert_landmark: + returning: + - id: 1 + name: Baz + type: river + location: &loc1 + coordinates: [43.75049, 11.03207] + type: Point + crs: &crs + type: name + properties: + name: 'urn:ogc:def:crs:EPSG::4326' + - id: 2 + name: Foo Bar + type: park + location: &loc2 + coordinates: [43.76417, 11.25869] + type: Point + crs: *crs + query: + variables: + landmarks: + - name: Baz type: river - location: &loc1 - coordinates: [43.75049, 11.03207] - type: Point - crs: &crs - type: name - properties: - name: 'urn:ogc:def:crs:EPSG::4326' - - id: 2 - name: Foo Bar + location: *loc1 + - name: Foo Bar type: park - location: &loc2 - coordinates: [43.76417, 11.25869] - type: Point - crs: *crs -query: - variables: - landmarks: - - name: Baz - type: river - location: *loc1 - - name: Foo Bar - type: park - location: *loc2 - query: | - mutation insertLandmark($landmarks: [landmark_insert_input!]!) { - insert_landmark(objects: $landmarks) { - returning{ - id - name - location - type + location: *loc2 + query: | + mutation insertLandmark($landmarks: [landmark_insert_input!]!) { + insert_landmark(objects: $landmarks) { + returning{ + id + name + location + type + } } } - } +- description: Delete the inserted landmarks + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from landmark diff --git a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_road.yaml b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_road.yaml index a00d09c37d4..f46b3ae9691 100644 --- a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_road.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_road.yaml @@ -1,33 +1,41 @@ -description: Insert a straight road as a geojson LineString -url: /v1alpha1/graphql -status: 200 -response: - data: - insert_road: - returning: - - id: 1 - name: Foo - path: &path - coordinates: - - [43.75049, 11.03207] - - [43.80009, 11.03208] - type: LineString - crs: - type: name - properties: - name: 'urn:ogc:def:crs:EPSG::4326' -query: - variables: - roads: - - name: Foo - path: *path - query: | - mutation insertRoad($roads: [road_insert_input!]!) { - insert_road(objects: $roads) { - returning{ - id - name - path +- description: Insert a straight road as a geojson LineString + url: /v1alpha1/graphql + status: 200 + response: + data: + insert_road: + returning: + - id: 1 + name: Foo + path: &path + coordinates: + - [43.75049, 11.03207] + - [43.80009, 11.03208] + type: LineString + crs: + type: name + properties: + name: 'urn:ogc:def:crs:EPSG::4326' + query: + variables: + roads: + - name: Foo + path: *path + query: | + mutation insertRoad($roads: [road_insert_input!]!) { + insert_road(objects: $roads) { + returning{ + id + name + path + } } } - } +- description: Delete the inserted road + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from road diff --git a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_route.yaml b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_route.yaml index 9fe90e6ca63..c96837178cc 100644 --- a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_route.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_route.yaml @@ -1,37 +1,45 @@ -description: Insert a route as a geojson MultiLineString -url: /v1alpha1/graphql -status: 200 -response: - data: - insert_route: - returning: - - id: 1 - name: Foo - route: &route - coordinates: - - - - [43.75049, 11.03207] - - [43.80009, 11.03208] - - - - [43.80009, 11.03208] - - [42.60009, 10.13248] - type: MultiLineString - crs: - type: name - properties: - name: 'urn:ogc:def:crs:EPSG::4326' -query: - variables: - routes: - - name: Foo - route: *route - query: | - mutation insertRoute($routes: [route_insert_input!]!) { - insert_route(objects: $routes) { - returning{ - id - name - route +- description: Insert a route as a geojson MultiLineString + url: /v1alpha1/graphql + status: 200 + response: + data: + insert_route: + returning: + - id: 1 + name: Foo + route: &route + coordinates: + - + - [43.75049, 11.03207] + - [43.80009, 11.03208] + - + - [43.80009, 11.03208] + - [42.60009, 10.13248] + type: MultiLineString + crs: + type: name + properties: + name: 'urn:ogc:def:crs:EPSG::4326' + query: + variables: + routes: + - name: Foo + route: *route + query: | + mutation insertRoute($routes: [route_insert_input!]!) { + insert_route(objects: $routes) { + returning{ + id + name + route + } } } - } +- description: Delete the inserted route + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from route diff --git a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_service_locations.yaml b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_service_locations.yaml index 75a15890b8b..3b24f91c4c1 100644 --- a/server/tests-py/queries/graphql_mutation/insert/geojson/insert_service_locations.yaml +++ b/server/tests-py/queries/graphql_mutation/insert/geojson/insert_service_locations.yaml @@ -1,31 +1,39 @@ -description: Insert the path for a curved road -url: /v1alpha1/graphql -status: 200 -response: - data: - insert_service_locations: - returning: - - id: 1 - locations: &locs - coordinates: - - [43.75049, 11.03207] - - [43.80009, 11.03208] - - [43.80009, 11.03308] - type: MultiPoint - crs: - type: name - properties: - name: 'urn:ogc:def:crs:EPSG::4326' -query: - variables: - locations: - - locations: *locs - query: | - mutation insertServLoc($locations: [service_locations_insert_input!]!) { - insert_service_locations(objects: $locations) { - returning{ - id - locations +- description: Insert the path for a curved road + url: /v1alpha1/graphql + status: 200 + response: + data: + insert_service_locations: + returning: + - id: 1 + locations: &locs + coordinates: + - [43.75049, 11.03207] + - [43.80009, 11.03208] + - [43.80009, 11.03308] + type: MultiPoint + crs: + type: name + properties: + name: 'urn:ogc:def:crs:EPSG::4326' + query: + variables: + locations: + - locations: *locs + query: | + mutation insertServLoc($locations: [service_locations_insert_input!]!) { + insert_service_locations(objects: $locations) { + returning{ + id + locations + } } } - } +- description: Delete the inserted service locations + url: /v1/query + status: 200 + query: + type: run_sql + args: + sql: | + delete from service_locations diff --git a/server/tests-py/queries/graphql_mutation/insert/permissions/article_on_conflict_constraint_on_user_role_error.yaml b/server/tests-py/queries/graphql_mutation/insert/permissions/article_on_conflict_constraint_on_user_role_error.yaml new file mode 100644 index 00000000000..15d24af99c5 --- /dev/null +++ b/server/tests-py/queries/graphql_mutation/insert/permissions/article_on_conflict_constraint_on_user_role_error.yaml @@ -0,0 +1,26 @@ +description: Upserts article data via GraphQL mutation as User role +url: /v1alpha1/graphql +status: 400 +header: + X-Hasura-Role: user + X-Hasura-User-Id: 1 +query: + query: | + mutation insert_article { + insert_article ( + objects: [ + { + content: "Updated Article 1 content", + id: 1 + } + ], + on_conflict: { + constraint_on: id + } + ) { + returning { + title + content + } + } + } diff --git a/server/tests-py/queries/graphql_query/basic/nested_select_query_article_author_where_on_relationship.yaml b/server/tests-py/queries/graphql_query/basic/nested_select_query_article_author_where_on_relationship.yaml new file mode 100644 index 00000000000..66312320917 --- /dev/null +++ b/server/tests-py/queries/graphql_query/basic/nested_select_query_article_author_where_on_relationship.yaml @@ -0,0 +1,31 @@ +description: Nested select on article +url: /v1alpha1/graphql +status: 200 +response: + data: + article: + - id: 1 + title: Article 1 + content: Sample article content 1 + author: + id: 1 + name: Author 1 + - id: 2 + title: Article 2 + content: Sample article content 2 + author: + id: 1 + name: Author 1 +query: + query: | + query { + article (where: {author: {name: {_eq: "Author 1"}}} ) { + id + title + content + author { + id + name + } + } + } diff --git a/server/tests-py/queries/graphql_query/basic/select_query_author_col_not_present_err.yaml b/server/tests-py/queries/graphql_query/basic/select_query_author_col_not_present_err.yaml new file mode 100644 index 00000000000..2fae077e59c --- /dev/null +++ b/server/tests-py/queries/graphql_query/basic/select_query_author_col_not_present_err.yaml @@ -0,0 +1,18 @@ +description: Simple GraphQL object query on author querying a column which does not exist +url: /v1alpha1/graphql +status: 400 +response: + errors: + - path: $.selectionSet.author.selectionSet.notPresentCol + error: |- + field "notPresentCol" not found in type: 'author' + code: validation-failed +query: + query: | + query { + author { + id + name + notPresentCol + } + } diff --git a/server/tests-py/queries/graphql_query/basic/select_query_author_col_quoted.yaml b/server/tests-py/queries/graphql_query/basic/select_query_author_col_quoted.yaml new file mode 100644 index 00000000000..b5712678821 --- /dev/null +++ b/server/tests-py/queries/graphql_query/basic/select_query_author_col_quoted.yaml @@ -0,0 +1,21 @@ +description: Simple GraphQL object query on author +url: /v1alpha1/graphql +status: 200 +response: + data: + author: + - name: Author 1 + id: 1 + createdAt: '2017-09-21T09:39:44+00:00' + - name: Author 2 + id: 2 + createdAt: '2017-09-21T09:50:44+00:00' +query: + query: | + query { + author { + id + name + createdAt + } + } diff --git a/server/tests-py/queries/graphql_query/basic/select_query_non_tracked_table_err.yaml b/server/tests-py/queries/graphql_query/basic/select_query_non_tracked_table_err.yaml new file mode 100644 index 00000000000..5445641cdcc --- /dev/null +++ b/server/tests-py/queries/graphql_query/basic/select_query_non_tracked_table_err.yaml @@ -0,0 +1,17 @@ +description: Select query on table which is not tracked +url: /v1alpha1/graphql +status: 400 +response: + errors: + - path: $.selectionSet.random + error: |- + field "random" not found in type: 'query_root' + code: validation-failed +query: + query: | + query { + random { + id + name + } + } diff --git a/server/tests-py/queries/graphql_query/basic/select_query_test_types.yaml b/server/tests-py/queries/graphql_query/basic/select_query_test_types.yaml new file mode 100644 index 00000000000..41d763c9eb8 --- /dev/null +++ b/server/tests-py/queries/graphql_query/basic/select_query_test_types.yaml @@ -0,0 +1,109 @@ +description: GraphQL query to test different data types of Postgres +url: /v1alpha1/graphql +status: 200 +response: + data: + test_types: + - c1_smallint: 32767 + c2_integer: 2147483647 + c3_bigint: '9223372036854775807' + c4_decimal: 123.45 + c5_numeric: 1.234 + c6_real: 0.00390625 + c7_double_precision: 16.0001220703125 + c8_smallserial: 1 + c9_serial: 1 + c10_bigserial: '1' + c11_varchar_3: 'abc' + c12_char_4: 'baaz' + c13_text: 'foo bar baz' + c14_timestamp: '2004-10-19T10:23:54' + c15_timestamptz: '2015-10-17T14:42:43+00:00' + c16_date: '2014-09-14' + c17_time: '11:09:23' + c18_time_with_zone: '15:22:23+00' + c19_interval: '01:03:02' + c20_boolean: true + c21_point: '(1,2)' + c22_line: '{2,3,-1}' + c23_lseg: '[(4,2),(3,1)]' + c24_box: '(31,12),(14,11)' + c25_closed_path: '((0,0),(0,3),(1,0))' + c26_open_path: '[(0,0),(0,-1),(-3,0)]' + c27_polygon: '((0,0),(0,6),(2,0))' + c28_circle: '<(-2,-3),3>' + c29_cidr: '192.168.100.128/25' + c30_inet: '198.24.10.0' + c31_macaddr: '08:00:2b:01:02:03' + c32_json: + a: b + c33_jsonb: + c: d + c34_text_array: ["a","b","c"] + c35_integer_2d_array: + - [4,5,6] + - [7,8,9] + c36_uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 + c37_composite_type_complex: + r: 1.23 + i: -3.456 + c38_composite_type_inventory: + name: fuzzy dice + supplier_id: 42 + price: 1.99 + c39_range_integer: '[123,456)' + c40_range_bigint: '[1147483647,2147483647)' + c41_range_numeric: '[1.23,4.56]' + c42_range_timestamp: '["2010-01-01 14:30:00","2010-01-01 15:30:02")' + c43_range_timestamptz: '("2011-02-05 12:03:00+00","2012-03-04 16:40:04+00"]' + c44_xml: 'bar' +query: + query: | + query { + test_types { + c1_smallint + c2_integer + c3_bigint + c4_decimal + c5_numeric + c6_real + c7_double_precision + c8_smallserial + c9_serial + c10_bigserial + c11_varchar_3 + c12_char_4 + c13_text + c14_timestamp + c15_timestamptz + c16_date + c17_time + c18_time_with_zone + c19_interval + c20_boolean + c21_point + c22_line + c23_lseg + c24_box + c25_closed_path + c26_open_path + c27_polygon + c28_circle + c29_cidr + c30_inet + c31_macaddr + c32_json + c33_jsonb + c34_text_array + c35_integer_2d_array + c36_uuid + c37_composite_type_complex + c38_composite_type_inventory + c39_range_integer + c40_range_bigint + c41_range_numeric + c42_range_timestamp + c43_range_timestamptz + c44_xml + } + } diff --git a/server/tests-py/queries/graphql_query/basic/setup.yaml b/server/tests-py/queries/graphql_query/basic/setup.yaml index 9e75f34dc61..c184fc56c9d 100644 --- a/server/tests-py/queries/graphql_query/basic/setup.yaml +++ b/server/tests-py/queries/graphql_query/basic/setup.yaml @@ -1,13 +1,87 @@ type: bulk args: +- type: run_sql + args: + sql: | + CREATE TYPE complex AS ( + r double precision, + i double precision + ); + +- type: run_sql + args: + sql: | + CREATE TYPE inventory_item AS ( + name text, + supplier_id integer, + price numeric + ); + +#Test table with different types +- type: run_sql + args: + sql: | + create table test_types ( + c1_smallint smallint, + c2_integer integer, + c3_bigint bigint, + c4_decimal decimal (5, 2), + c5_numeric numeric (4, 3), + c6_real real, + c7_double_precision double precision, + c8_smallserial smallserial, + c9_serial serial, + c10_bigserial bigserial, + c11_varchar_3 varchar(3), + c12_char_4 char(4), + c13_text text, + c14_timestamp timestamp, + c15_timestamptz timestamptz, + c16_date date, + c17_time time, + c18_time_with_zone time with time zone, + c19_interval interval, + c20_boolean boolean, + c21_point point, + c22_line line, + c23_lseg lseg, + c24_box box, + c25_closed_path path, + c26_open_path path, + c27_polygon polygon, + c28_circle circle, + c29_cidr cidr, + c30_inet inet, + c31_macaddr macaddr, + c32_json json, + c33_jsonb jsonb, + c34_text_array text[], + c35_integer_2d_array integer[][], + c36_uuid uuid, + c37_composite_type_complex complex, + c38_composite_type_inventory inventory_item, + c39_range_integer int4range, + c40_range_bigint int8range, + c41_range_numeric numrange, + c42_range_timestamp tsrange, + c43_range_timestamptz tstzrange, + c44_xml xml + ); +- type: track_table + args: + schema: public + name: test_types + + #Author table - type: run_sql args: sql: | create table author( - id serial primary key, - name text unique + id serial primary key, + name text unique, + "createdAt" timestamptz ); - type: track_table args: @@ -51,15 +125,105 @@ args: table: article column: author_id +#Insert values +- type: run_sql + args: + sql: | + insert into test_types + ( c1_smallint + , c2_integer + , c3_bigint + , c4_decimal + , c5_numeric + , c6_real + , c7_double_precision + , c11_varchar_3 + , c12_char_4 + , c13_text + , c14_timestamp + , c15_timestamptz + , c16_date + , c17_time + , c18_time_with_zone + , c19_interval + , c20_boolean + , c21_point + , c22_line + , c23_lseg + , c24_box + , c25_closed_path + , c26_open_path + , c27_polygon + , c28_circle + , c29_cidr + , c30_inet + , c31_macaddr + , c32_json + , c33_jsonb + , c34_text_array + , c35_integer_2d_array + , c36_uuid + , c37_composite_type_complex + , c38_composite_type_inventory + , c39_range_integer + , c40_range_bigint + , c41_range_numeric + , c42_range_timestamp + , c43_range_timestamptz + , c44_xml + ) + values + ( 32767 -- c1_smallint + , 2147483647 -- c2_integer + , 9223372036854775807 -- c3_bigint + , 123.45 -- c4_decimal + , 1.234 -- c5_numeric + , 0.00390625 -- c6_real + , 16.0001220703125 -- c7_double_precision + , 'abc' -- c11_varchar_3 + , 'baaz' -- c12_char_4 + , 'foo bar baz' -- c13_text + , '2004-10-19T10:23:54' -- c14_timestamp + , '2015-10-17T14:42:43+00:00' -- c15_timestamptz + , '2014-09-14' -- c16_date + , '11:09:23' -- c17_time + , '15:22:23+00' -- c18_time_with_zone + , '01:03:02' -- c19_interval + , true -- c20_boolean + , '(1,2)' -- c21_point + , '{2,3,-1}' -- c22_line + , '[(4,2),(3,1)]' -- c23_line + , '((31,12),(14,11))' -- c24_box + , '((0,0),(0,3),(1,0))' -- c25_closed_path + , '[(0,0),(0,-1),(-3,0)]' -- c26_open_path + , '((0,0),(0,6),(2,0))' -- c27_polygon + , '<(-2,-3),3>' -- c28_circle + , '192.168.100.128/25' -- c29_cidr + , '198.24.10.0' -- c30_inet + , '08:00:2b:01:02:03' -- c31_macaddr + , '{ "a" : "b" }' -- c32_json + , '{ "c" : "d" }' -- c33_jsonb + , '{"a","b","c"}' -- c34_text_array + , '{{4,5,6},{7,8,9}}' -- c35_integer_2d_array + , 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11' -- c36_uuid + , '(1.23,-3.456)' -- c37_composite_type_complexa + , '("fuzzy dice",42,1.99)' -- c38_composite_type_inventory + , '[123,456)' -- c39_range_integer + , '[1147483647, 2147483647)' -- c40_range_bigint + , '[1.23, 4.56]' -- c41_range_numeric + , '[2010-01-01 14:30:00, 2010-01-01 15:30:02)' -- c42_range_timestamp + , '(2011-02-05T12:03:00+00:00, 2012-03-04T16:40:04+00:00]' -- c43_range_timestamptz + , 'bar' -- c44_xml + ) #Insert values - type: run_sql args: sql: | - insert into author (name) + insert into author (name, "createdAt") values - ('Author 1'), - ('Author 2') + ('Author 1', '2017-09-21T09:39:44Z'), + ('Author 2', '2017-09-21T09:50:44Z') - type: run_sql args: @@ -106,3 +270,9 @@ args: number: '123456789' - name: User 2 number: '123456780' + +#Set timezone +- type: run_sql + args: + sql: | + SET TIME ZONE 'UTC'; diff --git a/server/tests-py/queries/graphql_query/basic/teardown.yaml b/server/tests-py/queries/graphql_query/basic/teardown.yaml index 640ce6f4eb2..ca2c603b7db 100644 --- a/server/tests-py/queries/graphql_query/basic/teardown.yaml +++ b/server/tests-py/queries/graphql_query/basic/teardown.yaml @@ -1,5 +1,11 @@ type: bulk args: + +- type: run_sql + args: + sql: | + drop table test_types + #Drop relationship first - type: drop_relationship args: @@ -22,3 +28,13 @@ args: args: sql: | drop table "user" + +- type: run_sql + args: + sql: | + drop type complex + +- type: run_sql + args: + sql: | + drop type inventory_item diff --git a/server/tests-py/queries/graphql_query/boolexp/basic/select_author_article_operator_ne_not_found_err.yaml b/server/tests-py/queries/graphql_query/boolexp/basic/select_author_article_operator_ne_not_found_err.yaml new file mode 100644 index 00000000000..9b57a1392db --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/basic/select_author_article_operator_ne_not_found_err.yaml @@ -0,0 +1,23 @@ +description: Select author and their articles +url: /v1alpha1/graphql +status: 400 +response: + errors: + - path: $.selectionSet.author.args.where.name._ne + error: |- + field "_ne" not found in type: 'text_comparison_exp' + code: validation-failed +query: + query: | + query { + author (where: {name: {_ne: "Author 1"}}) { + id + name + articles { + id + title + content + is_published + } + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/basic/select_author_article_unexpected_operator_in_where_err.yaml b/server/tests-py/queries/graphql_query/boolexp/basic/select_author_article_unexpected_operator_in_where_err.yaml new file mode 100644 index 00000000000..2cfdb92bdbb --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/basic/select_author_article_unexpected_operator_in_where_err.yaml @@ -0,0 +1,24 @@ +description: Select author and their articles +url: /v1alpha1/graphql +status: 400 +response: + errors: + - path: $.selectionSet.author.args.where.id._unexpected + error: |- + field "_unexpected" not found in type: 'integer_comparison_exp' + code: validation-failed +query: + query: | + query { + author ( + where: {id: {_unexpected: 2}} + ) { + id + name + articles{ + id + title + content + } + } + } diff --git a/server/tests-py/queries/v1/basic/query_args_as_string_err.yaml b/server/tests-py/queries/v1/basic/query_args_as_string_err.yaml new file mode 100644 index 00000000000..057ad57e985 --- /dev/null +++ b/server/tests-py/queries/v1/basic/query_args_as_string_err.yaml @@ -0,0 +1,11 @@ +description: Count number of authors +url: /v1/query +status: 400 +response: + path: $ + error: When parsing Hasura.Server.Query.RQLQuery expected Object but got String. + code: parse-failed +query: | + type: count + args: | + table: author diff --git a/server/tests-py/queries/v1/basic/query_string_input_err.yaml b/server/tests-py/queries/v1/basic/query_string_input_err.yaml new file mode 100644 index 00000000000..b3260848674 --- /dev/null +++ b/server/tests-py/queries/v1/basic/query_string_input_err.yaml @@ -0,0 +1,11 @@ +description: Count number of authors +url: /v1/query +status: 400 +response: + path: $ + error: When parsing Hasura.Server.Query.RQLQuery expected Object but got String. + code: parse-failed +query: | + type: count + args: + table: author diff --git a/server/tests-py/queries/v1/basic/query_unknown_type_err.yaml b/server/tests-py/queries/v1/basic/query_unknown_type_err.yaml new file mode 100644 index 00000000000..d2eda31b545 --- /dev/null +++ b/server/tests-py/queries/v1/basic/query_unknown_type_err.yaml @@ -0,0 +1,7 @@ +description: Count number of authors +url: /v1/query +status: 400 +query: + type: random + args: + foo: bar diff --git a/server/tests-py/queries/v1/basic/setup.yaml b/server/tests-py/queries/v1/basic/setup.yaml new file mode 100644 index 00000000000..d30c70b2781 --- /dev/null +++ b/server/tests-py/queries/v1/basic/setup.yaml @@ -0,0 +1,94 @@ +type: bulk +args: + +#Author table +- type: run_sql + args: + sql: | + create table author( + id serial primary key, + name text unique, + is_registered boolean + ); +- type: track_table + args: + schema: public + name: author + +#Article table +- type: run_sql + args: + sql: | + CREATE TABLE article ( + id SERIAL PRIMARY KEY, + title TEXT, + content TEXT, + author_id INTEGER REFERENCES author(id), + is_published BOOLEAN, + published_on TIMESTAMP + ) +- type: track_table + args: + schema: public + name: article +#Object relationship +- type: create_object_relationship + args: + table: article + name: author + using: + foreign_key_constraint_on: author_id + +#Insert values +- type: run_sql + args: + sql: | + insert into author (name, is_registered) + values + ('Author 1', false), + ('Author 2', true), + ('Author 3', true), + ('Author 4', false) + +- type: run_sql + args: + sql: | + insert into article (title, content, author_id, is_published) + values + ( + 'Article 1', + 'Sample article content 1', + 1, + false + ), + ( + 'Article 2', + 'Sample article content 2', + 1, + true + ), + ( + 'Article 3', + 'Sample article content 3', + 2, + false + ), + ( + 'Article 4', + 'Sample article content 4', + 3, + false + ), + ( + 'Article 5', + 'Sample article content 5', + 3, + false + ), + ( + 'Article 6', + 'Sample article content 6', + 3, + true + ) + diff --git a/server/tests-py/queries/v1/basic/teardown.yaml b/server/tests-py/queries/v1/basic/teardown.yaml new file mode 100644 index 00000000000..5a09757e417 --- /dev/null +++ b/server/tests-py/queries/v1/basic/teardown.yaml @@ -0,0 +1,10 @@ +type: bulk +args: +- type: run_sql + args: + sql: | + drop table article +- type: run_sql + args: + sql: | + drop table author diff --git a/server/tests-py/queries/v1/count/basic/count_articles_with_non_registered_authors.yaml b/server/tests-py/queries/v1/count/basic/count_articles_with_non_registered_authors.yaml new file mode 100644 index 00000000000..26de9fb3484 --- /dev/null +++ b/server/tests-py/queries/v1/count/basic/count_articles_with_non_registered_authors.yaml @@ -0,0 +1,12 @@ +description: Count articles of registered authors +url: /v1/query +status: 200 +response: + count: 2 +query: + type: count + args: + table: article + where: + author: + is_registered: false diff --git a/server/tests-py/queries/v1/count/basic/count_articles_with_registered_authors.yaml b/server/tests-py/queries/v1/count/basic/count_articles_with_registered_authors.yaml new file mode 100644 index 00000000000..ca81d9fd932 --- /dev/null +++ b/server/tests-py/queries/v1/count/basic/count_articles_with_registered_authors.yaml @@ -0,0 +1,12 @@ +description: Count articles of registered authors +url: /v1/query +status: 200 +response: + count: 4 +query: + type: count + args: + table: article + where: + author: + is_registered: true diff --git a/server/tests-py/queries/v1/count/basic/count_authors.yaml b/server/tests-py/queries/v1/count/basic/count_authors.yaml new file mode 100644 index 00000000000..05d8718d3f8 --- /dev/null +++ b/server/tests-py/queries/v1/count/basic/count_authors.yaml @@ -0,0 +1,9 @@ +description: Count number of authors +url: /v1/query +status: 200 +response: + count: 4 +query: + type: count + args: + table: author diff --git a/server/tests-py/queries/v1/count/basic/count_distinct_authors_with_published_articles.yaml b/server/tests-py/queries/v1/count/basic/count_distinct_authors_with_published_articles.yaml new file mode 100644 index 00000000000..9011dcd27a5 --- /dev/null +++ b/server/tests-py/queries/v1/count/basic/count_distinct_authors_with_published_articles.yaml @@ -0,0 +1,13 @@ +description: Count number of authors with atleast one published article +url: /v1/query +status: 200 +response: + count: 2 +query: + type: count + args: + table: article + distinct: + - author_id + where: + is_published: true diff --git a/server/tests-py/queries/v1/count/basic/count_distinct_authors_with_unpublished_articles.yaml b/server/tests-py/queries/v1/count/basic/count_distinct_authors_with_unpublished_articles.yaml new file mode 100644 index 00000000000..bbae0ba5ce7 --- /dev/null +++ b/server/tests-py/queries/v1/count/basic/count_distinct_authors_with_unpublished_articles.yaml @@ -0,0 +1,13 @@ +description: Count number of authors with atleast one published article +url: /v1/query +status: 200 +response: + count: 3 +query: + type: count + args: + table: article + distinct: + - author_id + where: + is_published: false diff --git a/server/tests-py/queries/v1/count/basic/count_distinct_col_not_present_err.yaml b/server/tests-py/queries/v1/count/basic/count_distinct_col_not_present_err.yaml new file mode 100644 index 00000000000..11b4203803b --- /dev/null +++ b/server/tests-py/queries/v1/count/basic/count_distinct_col_not_present_err.yaml @@ -0,0 +1,15 @@ +description: Count number of authors with atleast one published article +url: /v1/query +status: 400 +response: + path: $.args.distinct[0] + error: ! 'no such column exists : "author_name"' + code: permission-denied +query: + type: count + args: + table: article + distinct: + - author_name + where: + is_published: true diff --git a/server/tests-py/queries/v1/count/basic/count_published_articles.yaml b/server/tests-py/queries/v1/count/basic/count_published_articles.yaml new file mode 100644 index 00000000000..d57c1c6b020 --- /dev/null +++ b/server/tests-py/queries/v1/count/basic/count_published_articles.yaml @@ -0,0 +1,11 @@ +description: Count number of authors +url: /v1/query +status: 200 +response: + count: 2 +query: + type: count + args: + table: article + where: + is_published: true diff --git a/server/tests-py/queries/v1/count/basic/count_unpublished_articles.yaml b/server/tests-py/queries/v1/count/basic/count_unpublished_articles.yaml new file mode 100644 index 00000000000..5c7238dcf8b --- /dev/null +++ b/server/tests-py/queries/v1/count/basic/count_unpublished_articles.yaml @@ -0,0 +1,11 @@ +description: Count number of authors +url: /v1/query +status: 200 +response: + count: 4 +query: + type: count + args: + table: article + where: + is_published: false diff --git a/server/tests-py/queries/v1/count/basic/setup.yaml b/server/tests-py/queries/v1/count/basic/setup.yaml new file mode 100644 index 00000000000..d30c70b2781 --- /dev/null +++ b/server/tests-py/queries/v1/count/basic/setup.yaml @@ -0,0 +1,94 @@ +type: bulk +args: + +#Author table +- type: run_sql + args: + sql: | + create table author( + id serial primary key, + name text unique, + is_registered boolean + ); +- type: track_table + args: + schema: public + name: author + +#Article table +- type: run_sql + args: + sql: | + CREATE TABLE article ( + id SERIAL PRIMARY KEY, + title TEXT, + content TEXT, + author_id INTEGER REFERENCES author(id), + is_published BOOLEAN, + published_on TIMESTAMP + ) +- type: track_table + args: + schema: public + name: article +#Object relationship +- type: create_object_relationship + args: + table: article + name: author + using: + foreign_key_constraint_on: author_id + +#Insert values +- type: run_sql + args: + sql: | + insert into author (name, is_registered) + values + ('Author 1', false), + ('Author 2', true), + ('Author 3', true), + ('Author 4', false) + +- type: run_sql + args: + sql: | + insert into article (title, content, author_id, is_published) + values + ( + 'Article 1', + 'Sample article content 1', + 1, + false + ), + ( + 'Article 2', + 'Sample article content 2', + 1, + true + ), + ( + 'Article 3', + 'Sample article content 3', + 2, + false + ), + ( + 'Article 4', + 'Sample article content 4', + 3, + false + ), + ( + 'Article 5', + 'Sample article content 5', + 3, + false + ), + ( + 'Article 6', + 'Sample article content 6', + 3, + true + ) + diff --git a/server/tests-py/queries/v1/count/basic/teardown.yaml b/server/tests-py/queries/v1/count/basic/teardown.yaml new file mode 100644 index 00000000000..5a09757e417 --- /dev/null +++ b/server/tests-py/queries/v1/count/basic/teardown.yaml @@ -0,0 +1,10 @@ +type: bulk +args: +- type: run_sql + args: + sql: | + drop table article +- type: run_sql + args: + sql: | + drop table author diff --git a/server/tests-py/queries/v1/count/permissions/count_user_has_no_select_perm_error.yaml b/server/tests-py/queries/v1/count/permissions/count_user_has_no_select_perm_error.yaml new file mode 100644 index 00000000000..764fac2a118 --- /dev/null +++ b/server/tests-py/queries/v1/count/permissions/count_user_has_no_select_perm_error.yaml @@ -0,0 +1,16 @@ +description: Count number of authors +url: /v1/query +status: 400 +headers: + X-Hasura-Role: contractor +response: + path: $.args + error: select on "article" for role "contractor" is not allowed. ; "count" is only + allowed if the role has "select" permissions on the table + code: permission-denied +query: + type: count + args: + table: article + where: + is_published: false diff --git a/server/tests-py/queries/v1/count/permissions/count_users_unpublished_articles.yaml b/server/tests-py/queries/v1/count/permissions/count_users_unpublished_articles.yaml new file mode 100644 index 00000000000..969372eb8e1 --- /dev/null +++ b/server/tests-py/queries/v1/count/permissions/count_users_unpublished_articles.yaml @@ -0,0 +1,14 @@ +description: Count number of authors +url: /v1/query +status: 200 +headers: + X-Hasura-Role: user + X-Hasura-User-Id: '3' +response: + count: 3 +query: + type: count + args: + table: article + where: + is_published: false diff --git a/server/tests-py/queries/v1/count/permissions/setup.yaml b/server/tests-py/queries/v1/count/permissions/setup.yaml new file mode 100644 index 00000000000..0ea473c032c --- /dev/null +++ b/server/tests-py/queries/v1/count/permissions/setup.yaml @@ -0,0 +1,185 @@ +type: bulk +args: + +#Author table +- type: run_sql + args: + sql: | + create table author( + id serial primary key, + name text unique, + is_registered boolean not null default false, + remarks_internal text + ); +- type: track_table + args: + schema: public + name: author + +#Article table +- type: run_sql + args: + sql: | + CREATE TABLE article ( + id SERIAL PRIMARY KEY, + title TEXT, + content TEXT, + author_id INTEGER NOT NULL REFERENCES author(id), + is_published BOOLEAN NOT NULL default FALSE, + published_on TIMESTAMP + ) +- type: track_table + args: + schema: public + name: article + +#Object relationship +- type: create_object_relationship + args: + name: author + table: article + using: + foreign_key_constraint_on: author_id + +#Array relationship +- type: create_array_relationship + args: + table: author + name: articles + using: + foreign_key_constraint_on: + table: article + column: author_id + +#Article select permission for user +- type: create_select_permission + args: + table: article + role: user + permission: + columns: + - id + - title + - content + - is_published + filter: + $or: + - author_id: X-HASURA-USER-ID + - is_published: true + +#Article select permission for anonymous (only published articles) +- type: create_select_permission + args: + table: article + role: anonymous + permission: + columns: + - id + - title + - content + - is_published + filter: + is_published: true + +#Article insert permission for user +- type: create_insert_permission + args: + table: article + role: user + permission: + check: + author_id: X-Hasura-User-Id + +#Author select permission for user +- type: create_select_permission + args: + table: author + role: user + permission: + columns: + - id + - name + - is_registered + filter: + _or: + - id: X-HASURA-USER-ID + - articles: + is_published: + _eq: true + +#Author select permission for anonymous users +#Only authors with atleast one article will be shown +- type: create_select_permission + args: + table: author + role: anonymous + permission: + columns: + - id + - name + filter: + articles: + is_published: + _eq: true + +#Author insert permission for user +- type: create_insert_permission + args: + table: author + role: user + permission: + check: + id: X-HASURA-USER-ID + allow_upsert: true + +#Insert Author values +- type: insert + args: + table: author + objects: + - name: Author 1 + remarks_internal: remark 1 + - name: Author 2 + remarks_internal: remark 2 + - name: Author 3 + remarks_internal: remark 3 + +#Insert Article values +- type: insert + args: + table: article + objects: + - title: Article 1 + content: Sample article content 1 + author_id: 1 + is_published: false + + - title: Article 2 + content: Sample article content 2 + author_id: 1 + is_published: true + + - title: Article 3 + content: Sample article content 3 + author_id: 2 + is_published: true + + - title: Article 4 + content: Sample article content 4 + author_id: 3 + is_published: false + + - title: Article 5 + content: Sample article content 5 + author_id: 3 + is_published: true + + - title: Article 6 + content: Sample article content 6 + author_id: 3 + is_published: false + + - title: Article 7 + content: Sample article content 7 + author_id: 3 + is_published: false diff --git a/server/tests-py/queries/v1/count/permissions/teardown.yaml b/server/tests-py/queries/v1/count/permissions/teardown.yaml new file mode 100644 index 00000000000..2b72c359b5a --- /dev/null +++ b/server/tests-py/queries/v1/count/permissions/teardown.yaml @@ -0,0 +1,14 @@ +type: bulk +args: + +- type: run_sql + args: + sql: | + drop table article + cascade: true + +- type: run_sql + args: + sql: | + drop table author + cascade: true diff --git a/server/tests-py/queries/v1/permissions/create_article_permission_role_admin_error.yaml b/server/tests-py/queries/v1/permissions/create_article_permission_role_admin_error.yaml new file mode 100644 index 00000000000..746a26f7da9 --- /dev/null +++ b/server/tests-py/queries/v1/permissions/create_article_permission_role_admin_error.yaml @@ -0,0 +1,17 @@ +description: Create permission with admin as role (error) +url: /v1/query +status: 400 +response: + path: $.args + error: >- + 'select' permission on "author" for role "admin" already exists + code: already-exists +query: + type: create_select_permission + args: + table: author + role: admin + permission: + columns: '*' + filter: + id: X-Hasura-User-Id diff --git a/server/tests-py/queries/v1/permissions/create_article_permission_role_user.yaml b/server/tests-py/queries/v1/permissions/create_article_permission_role_user.yaml new file mode 100644 index 00000000000..28710e31543 --- /dev/null +++ b/server/tests-py/queries/v1/permissions/create_article_permission_role_user.yaml @@ -0,0 +1,26 @@ +- description: Create permission with user as role + url: /v1/query + status: 200 + response: + message: success + query: + type: create_select_permission + args: + table: author + role: user + permission: + columns: '*' + filter: + id: X-Hasura-User-Id + +- description: Drop select permission + url: /v1/query + status: 200 + response: + message: success + query: + type: drop_select_permission + args: + table: author + role: user + diff --git a/server/tests-py/queries/v1/permissions/setup.yaml b/server/tests-py/queries/v1/permissions/setup.yaml new file mode 100644 index 00000000000..56c4a39a398 --- /dev/null +++ b/server/tests-py/queries/v1/permissions/setup.yaml @@ -0,0 +1,30 @@ +type: bulk +args: + +#Author table +- type: run_sql + args: + sql: | + create table author( + id serial primary key, + name text unique, + remarks text + ); +- type: track_table + args: + schema: public + name: author + +#Article table +- type: run_sql + args: + sql: | + CREATE TABLE article ( + id SERIAL PRIMARY KEY, + title TEXT, + content TEXT, + author_id INTEGER REFERENCES author(id), + is_published BOOLEAN, + published_on TIMESTAMP + ) + diff --git a/server/tests-py/queries/v1/permissions/teardown.yaml b/server/tests-py/queries/v1/permissions/teardown.yaml new file mode 100644 index 00000000000..78d2141d7b1 --- /dev/null +++ b/server/tests-py/queries/v1/permissions/teardown.yaml @@ -0,0 +1,13 @@ +type: bulk +args: + +- type: run_sql + args: + sql: | + drop table article + +- type: run_sql + args: + sql: | + drop table author + diff --git a/server/tests-py/queries/v1/select/basic/nested_select_query_article_author_rel_alias.yaml b/server/tests-py/queries/v1/select/basic/nested_select_query_article_author_rel_alias.yaml new file mode 100644 index 00000000000..886b4302e7a --- /dev/null +++ b/server/tests-py/queries/v1/select/basic/nested_select_query_article_author_rel_alias.yaml @@ -0,0 +1,35 @@ +description: Nested select on article +url: /v1/query +status: 200 +response: + - id: 1 + title: Article 1 + content: Sample article content 1 + author_info: + id: 1 + name: Author 1 + - id: 2 + title: Article 2 + content: Sample article content 2 + author_info: + id: 1 + name: Author 1 + - id: 3 + title: Article 3 + content: Sample article content 3 + author_info: + id: 2 + name: Author 2 +query: + type: select + args: + table: article + columns: + - id + - title + - content + - name: author + alias: author_info + columns: + - id + - name diff --git a/server/tests-py/queries/v1/select/basic/select_article_table_not_present_err.yaml b/server/tests-py/queries/v1/select/basic/select_article_table_not_present_err.yaml new file mode 100644 index 00000000000..90f5413aeb1 --- /dev/null +++ b/server/tests-py/queries/v1/select/basic/select_article_table_not_present_err.yaml @@ -0,0 +1,14 @@ +url: /v1/query +status: 400 +response: + code: not-exists + error: table "foobar" does not exist + path: $.args.table +query: + type: select + args: + table: foobar + columns: + - id + - name + - user diff --git a/server/tests-py/requirements.txt b/server/tests-py/requirements.txt index 2285857ff7b..f8c615ead62 100644 --- a/server/tests-py/requirements.txt +++ b/server/tests-py/requirements.txt @@ -4,3 +4,6 @@ pytest requests pyyaml websocket-client +pyjwt >= 1.5.3 +jsondiff +cryptography diff --git a/server/tests-py/super_classes.py b/server/tests-py/super_classes.py new file mode 100644 index 00000000000..96dbfbba24c --- /dev/null +++ b/server/tests-py/super_classes.py @@ -0,0 +1,45 @@ +import pytest +from abc import ABC, abstractmethod + +class DefaultTestQueries(ABC): + + def do_setup(self, setup_ctrl, hge_ctx): + if not setup_ctrl['setupDone']: + st_code, resp = hge_ctx.v1q_f(self.dir() + '/setup.yaml') + assert st_code == 200, resp + setup_ctrl['setupDone'] = True + + def do_teardown(self, setup_ctrl, hge_ctx): + if setup_ctrl['setupDone'] and not hge_ctx.may_skip_test_teardown: + st_code, resp = hge_ctx.v1q_f(self.dir() + '/teardown.yaml') + assert st_code == 200, resp + setup_ctrl['setupDone'] = False + + @pytest.fixture(autouse=True) + def transact(self, setup_ctrl, hge_ctx): + self.do_setup(setup_ctrl, hge_ctx) + yield + self.do_teardown(setup_ctrl, hge_ctx); + + @abstractmethod + def dir(self): + pass + + +class DefaultTestSelectQueries(ABC): + + @pytest.fixture(scope='class') + def transact(self, request, hge_ctx): + st_code, resp = hge_ctx.v1q_f(self.dir() + '/setup.yaml') + assert st_code == 200, resp + yield + st_code, resp = hge_ctx.v1q_f(self.dir() + '/teardown.yaml') + assert st_code == 200, resp + + @pytest.fixture(autouse=True) + def ensure_transact(self, transact): + pass + + @abstractmethod + def dir(self): + pass diff --git a/server/tests-py/test_graphql_introspection.py b/server/tests-py/test_graphql_introspection.py index 1f2a3c4bddd..009a7c51c77 100644 --- a/server/tests-py/test_graphql_introspection.py +++ b/server/tests-py/test_graphql_introspection.py @@ -1,40 +1,34 @@ - -import pytest import yaml -from validate import check_query_f +from validate import check_query_f, check_query +from super_classes import DefaultTestSelectQueries -class TestGraphqlIntrospection: +class TestGraphqlIntrospection(DefaultTestSelectQueries): def test_introspection(self, hge_ctx): - with open(self.dir + "/introspection.yaml") as c: - conf = yaml.load(c) - code, resp = hge_ctx.anyq( conf['url'], conf['query'], {}) - assert code == 200 - 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 + with open(self.dir() + "/introspection.yaml") as c: + conf = yaml.load(c) + code, resp = check_query(hge_ctx, conf) + assert code == 200, resp + 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 def test_introspection_user(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/introspection_user_role.yaml") + check_query_f(hge_ctx, self.dir() + "/introspection_user_role.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_introspection" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_introspection" diff --git a/server/tests-py/test_graphql_mutations.py b/server/tests-py/test_graphql_mutations.py index 140d0b6819e..c0a63321672 100644 --- a/server/tests-py/test_graphql_mutations.py +++ b/server/tests-py/test_graphql_mutations.py @@ -1,372 +1,340 @@ - import pytest import yaml from validate import check_query_f +from super_classes import DefaultTestQueries -class TestGraphQLInsert(object): + +class TestGraphQLInsert(DefaultTestQueries): def test_inserts_author_article(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_article.yaml") + check_query_f(hge_ctx, self.dir() + "/author_article.yaml") + hge_ctx.may_skip_test_teardown = True + + def test_inserts_various_postgres_types(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/insert_various_postgres_types.yaml") + hge_ctx.may_skip_test_teardown = True + + @pytest.mark.xfail(reason="Refer https://github.com/hasura/graphql-engine/issues/348") + def test_insert_into_array_col_with_array_input(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/insert_into_array_col_with_array_input.yaml") + hge_ctx.may_skip_test_teardown = True def test_insert_using_variable(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_jsonb_variable.yaml") + check_query_f(hge_ctx, self.dir() + "/person_jsonb_variable.yaml") + hge_ctx.may_skip_test_teardown = True def test_insert_using_array_variable(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_jsonb_variable_array.yaml") + check_query_f(hge_ctx, self.dir() + "/person_jsonb_variable_array.yaml") + hge_ctx.may_skip_test_teardown = True def test_insert_null_col_value(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/order_col_shipped_null.yaml") + check_query_f(hge_ctx, self.dir() + "/order_col_shipped_null.yaml") + hge_ctx.may_skip_test_teardown = True - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/insert/basic" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/insert/basic" -class TestGraphqlInsertOnConflict(object): +class TestGraphqlInsertOnConflict(DefaultTestQueries): def test_on_conflict_update(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_on_conflict_update.yaml") + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_update.yaml") def test_on_conflict_no_action_specified(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_on_conflict_no_action_specified.yaml") + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_no_action_specified.yaml") def test_on_conflict_ignore(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_on_conflict_ignore_constraint.yaml") + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_ignore_constraint.yaml") + hge_ctx.may_skip_test_teardown = True def test_on_conflict_update_empty_cols(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_on_conflict_empty_update_columns.yaml") + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_empty_update_columns.yaml") + hge_ctx.may_skip_test_teardown = True def test_err_missing_article_constraint(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_on_conflict_error_missing_article_constraint.yaml") + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_error_missing_article_constraint.yaml") def test_err_unexpected_action(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_unexpected_on_conflict_action.yaml") + check_query_f(hge_ctx, self.dir() + "/article_unexpected_on_conflict_action.yaml") def test_err_unexpected_constraint(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_unexpected_on_conflict_constraint_error.yaml") + check_query_f(hge_ctx, self.dir() + "/article_unexpected_on_conflict_constraint_error.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/insert/onconflict" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/insert/onconflict" - -class TestGraphqlInsertPermission(object): +class TestGraphqlInsertPermission(DefaultTestQueries): def test_user_role_on_conflict_update(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_on_conflict_user_role.yaml") + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_user_role.yaml") + + def test_user_role_on_conflict_constraint_on_error(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_constraint_on_user_role_error.yaml") - @pytest.mark.xfail(reason="Refer https://github.com/hasura/graphql-engine/issues/563") def test_user_role_on_conflict_ignore(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_on_conflict_ignore_user_role.yaml") + check_query_f(hge_ctx, self.dir() + "/author_on_conflict_ignore_user_role.yaml") + hge_ctx.may_skip_test_teardown = True def test_user_on_conflict_err_no_action_specified(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_article_on_conflict_err_no_action_specified.yaml") + check_query_f(hge_ctx, self.dir() + "/user_article_on_conflict_err_no_action_specified.yaml") def test_user_err_missing_article_constraint(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_article_on_conflict_error_missing_article_constraint.yaml") + check_query_f(hge_ctx, self.dir() + "/user_article_on_conflict_error_missing_article_constraint.yaml") def test_user_err_unexpected_action(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_article_error_unexpected_on_conflict_action.yaml") + check_query_f(hge_ctx, self.dir() + "/user_article_error_unexpected_on_conflict_action.yaml") def test_user_err_unexpected_constraint(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_article_unexpected_on_conflict_constraint_error.yaml") + check_query_f(hge_ctx, self.dir() + "/user_article_unexpected_on_conflict_constraint_error.yaml") def test_role_has_no_permissions_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/address_permission_error.yaml") + check_query_f(hge_ctx, self.dir() + "/address_permission_error.yaml") def test_author_user_role_insert_check_perm_success(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_perm_success.yaml") + check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_perm_success.yaml") def test_user_role_insert_check_is_registered_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_is_registered_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_is_registered_fail.yaml") def test_user_role_insert_check_user_id_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_user_id_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_user_id_fail.yaml") def test_student_role_insert_check_bio_success(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_student_role_insert_check_bio_success.yaml") + check_query_f(hge_ctx, self.dir() + "/author_student_role_insert_check_bio_success.yaml") def test_student_role_insert_check_bio_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_student_role_insert_check_bio_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/author_student_role_insert_check_bio_fail.yaml") def test_company_user_role_insert(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/company_user_role.yaml") + check_query_f(hge_ctx, self.dir() + "/company_user_role.yaml") def test_company_user_role_insert_on_conflict(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/company_user_role_on_conflict.yaml") + check_query_f(hge_ctx, self.dir() + "/company_user_role_on_conflict.yaml") def test_resident_user_role_insert(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/resident_user.yaml") + check_query_f(hge_ctx, self.dir() + "/resident_user.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/insert/permissions" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/insert/permissions" -class TestGraphqlInsertConstraints(object): +class TestGraphqlInsertConstraints(DefaultTestQueries): def test_address_not_null_constraint_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/address_not_null_constraint_error.yaml") + check_query_f(hge_ctx, self.dir() + "/address_not_null_constraint_error.yaml") def test_insert_unique_constraint_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_unique_constraint_error.yaml") + check_query_f(hge_ctx, self.dir() + "/author_unique_constraint_error.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/insert/constraints" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/insert/constraints" -class TestGraphqlInsertGeoJson(): +class TestGraphqlInsertGeoJson(DefaultTestQueries): def test_insert_point_landmark(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_landmark.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_landmark.yaml") + hge_ctx.may_skip_test_teardown = True def test_insert_3d_point_drone_loc(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_drone_3d_location.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_drone_3d_location.yaml") + hge_ctx.may_skip_test_teardown = True def test_insert_landmark_single_position_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_landmark_single_position_err.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_landmark_single_position_err.yaml") def test_insert_line_string_road(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_road.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_road.yaml") + hge_ctx.may_skip_test_teardown = True def test_insert_road_single_point_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_road_single_point_err.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_road_single_point_err.yaml") def test_insert_multi_point_service_locations(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_service_locations.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_service_locations.yaml") + hge_ctx.may_skip_test_teardown = True def test_insert_multi_line_string_route(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_route.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_route.yaml") + hge_ctx.may_skip_test_teardown = True def test_insert_polygon(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_area.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_area.yaml") + hge_ctx.may_skip_test_teardown = True def test_insert_linear_ring_less_than_4_points_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_area_less_than_4_points_err.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_area_less_than_4_points_err.yaml") def test_insert_linear_ring_last_point_not_equal_to_first_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_linear_ring_last_point_not_equal_to_first_err.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_linear_ring_last_point_not_equal_to_first_err.yaml") def test_insert_multi_polygon_compounds(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_compounds.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_compounds.yaml") + hge_ctx.may_skip_test_teardown = True def test_insert_geometry_collection(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_geometry_collection.yaml") - + check_query_f(hge_ctx, self.dir() + "/insert_geometry_collection.yaml") + hge_ctx.may_skip_test_teardown = True + def test_insert_unexpected_geometry_type_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_geometry_unexpected_type_err.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_geometry_unexpected_type_err.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/insert/geojson" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/insert/geojson" -class TestGraphqlNestedInserts(object): +class TestGraphqlNestedInserts(DefaultTestQueries): def test_author_with_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_with_articles.yaml") + check_query_f(hge_ctx, self.dir() + "/author_with_articles.yaml") def test_author_with_articles_author_id_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_with_articles_author_id_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/author_with_articles_author_id_fail.yaml") def test_articles_with_author(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/articles_with_author.yaml") + check_query_f(hge_ctx, self.dir() + "/articles_with_author.yaml") def test_articles_with_author_author_id_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/articles_with_author_author_id_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/articles_with_author_author_id_fail.yaml") def test_author_upsert_articles_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_upsert_articles_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/author_upsert_articles_fail.yaml") def test_articles_author_upsert_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/articles_author_upsert_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/articles_author_upsert_fail.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/insert/nested" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/insert/nested" -class TestGraphqlInsertViews(object): + +class TestGraphqlInsertViews(DefaultTestQueries): def test_insert_view_author_simple(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_view_author_simple.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_view_author_simple.yaml") def test_insert_view_author_complex_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/insert_view_author_complex_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/insert_view_author_complex_fail.yaml") def test_nested_insert_article_author_simple_view(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/nested_insert_article_author_simple_view.yaml") + check_query_f(hge_ctx, self.dir() + "/nested_insert_article_author_simple_view.yaml") def test_nested_insert_article_author_complex_view_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/nested_insert_article_author_complex_view_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/nested_insert_article_author_complex_view_fail.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/insert/views" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/insert/views" -class TestGraphqlUpdateBasic: + +class TestGraphqlUpdateBasic(DefaultTestQueries): def test_set_author_name(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_set_name.yaml") + check_query_f(hge_ctx, self.dir() + "/author_set_name.yaml") def test_set_person_details(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_set_details.yaml") + check_query_f(hge_ctx, self.dir() + "/person_set_details.yaml") def test_person_id_inc(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_inc.yaml") + check_query_f(hge_ctx, self.dir() + "/person_inc.yaml") def test_no_operator_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_error_no_operator.yaml") + check_query_f(hge_ctx, self.dir() + "/person_error_no_operator.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/update/basic" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/update/basic" -class TestGraphqlUpdateJsonB: +class TestGraphqlUpdateJsonB(DefaultTestQueries): def test_jsonb_append_object(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_append_object.yaml") + check_query_f(hge_ctx, self.dir() + "/person_append_object.yaml") def test_jsonb_append_array(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_append_array.yaml") + check_query_f(hge_ctx, self.dir() + "/person_append_array.yaml") def test_jsonb_prepend_array(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_prepend_array.yaml") + check_query_f(hge_ctx, self.dir() + "/person_prepend_array.yaml") def test_jsonb_delete_at_path(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_delete_at_path.yaml") + check_query_f(hge_ctx, self.dir() + "/person_delete_at_path.yaml") def test_jsonb_delete_array_element(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_delete_array_element.yaml") + check_query_f(hge_ctx, self.dir() + "/person_delete_array_element.yaml") def test_jsonb_delete_key(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_delete_key.yaml") + check_query_f(hge_ctx, self.dir() + "/person_delete_key.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/update/jsonb" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/update/jsonb" -class TestGraphqlUpdatePermissions: +class TestGraphqlUpdatePermissions(DefaultTestQueries): def test_user_can_update_unpublished_article(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_can_update_unpublished_article.yaml") + check_query_f(hge_ctx, self.dir() + "/user_can_update_unpublished_article.yaml") def test_user_cannot_update_published_version_col(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_cannot_update_published_article_version.yaml") + check_query_f(hge_ctx, self.dir() + "/user_cannot_update_published_article_version.yaml") + hge_ctx.may_skip_test_teardown = True def test_user_cannot_update_another_users_article(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_cannot_update_another_users_article.yaml") + check_query_f(hge_ctx, self.dir() + "/user_cannot_update_another_users_article.yaml") + hge_ctx.may_skip_test_teardown = True def test_user_cannot_update_id_col(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_cannot_update_id_col_article.yaml") + check_query_f(hge_ctx, self.dir() + "/user_cannot_update_id_col_article.yaml") + hge_ctx.may_skip_test_teardown = True - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/update/permissions" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/update/permissions" -class TestGraphqlDeleteBasic: +class TestGraphqlDeleteBasic(DefaultTestQueries): def test_article_delete(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article.yaml") + check_query_f(hge_ctx, self.dir() + "/article.yaml") def test_article_delete_returning(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_returning.yaml") + check_query_f(hge_ctx, self.dir() + "/article_returning.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/delete/basic" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/delete/basic" -class TestGraphqlDeleteConstraints: +class TestGraphqlDeleteConstraints(DefaultTestQueries): def test_author_delete_foreign_key_violation(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_foreign_key_violation.yaml") + check_query_f(hge_ctx, self.dir() + "/author_foreign_key_violation.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/delete/constraints" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/delete/constraints" -class TestGraphqlDeletePermissions: +class TestGraphqlDeletePermissions(DefaultTestQueries): def test_author_can_delete_his_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_can_delete_his_articles.yaml") + check_query_f(hge_ctx, self.dir() + "/author_can_delete_his_articles.yaml") def test_author_cannot_delete_other_users_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_cannot_delete_other_users_articles.yaml") + check_query_f(hge_ctx, self.dir() + "/author_cannot_delete_other_users_articles.yaml") + hge_ctx.may_skip_test_teardown = True def test_resident_delete_without_select_perm_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/resident_delete_without_select_perm_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/resident_delete_without_select_perm_fail.yaml") + hge_ctx.may_skip_test_teardown = True - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/graphql_mutation/delete/permissions" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/graphql_mutation/delete/permissions" diff --git a/server/tests-py/test_graphql_queries.py b/server/tests-py/test_graphql_queries.py index 96087874fac..011e20552ad 100644 --- a/server/tests-py/test_graphql_queries.py +++ b/server/tests-py/test_graphql_queries.py @@ -1,277 +1,255 @@ -import pytest import yaml from validate import check_query_f +from super_classes import DefaultTestSelectQueries -class TestGraphQLQueryBasic: + +class TestGraphQLQueryBasic(DefaultTestSelectQueries): def test_select_query_author(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_author.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_author.yaml') + + def test_select_various_postgres_types(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/select_query_test_types.yaml') + + def test_select_query_author_quoted_col(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/select_query_author_col_quoted.yaml') def test_select_query_author_pk(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_author_by_pkey.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_author_by_pkey.yaml') def test_select_query_where(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_author_where.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_author_where.yaml') def test_nested_select_query_article_author(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/nested_select_query_article_author.yaml') + check_query_f(hge_ctx, self.dir() + '/nested_select_query_article_author.yaml') def test_nested_select_query_deep(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/nested_select_query_deep.yaml') + check_query_f(hge_ctx, self.dir() + '/nested_select_query_deep.yaml') def test_nested_select_query_where(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/nested_select_where_query_author_article.yaml') + check_query_f(hge_ctx, self.dir() + '/nested_select_where_query_author_article.yaml') + + def test_nested_select_query_where_on_relationship(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/nested_select_query_article_author_where_on_relationship.yaml') def test_select_query_user(self, hge_ctx): - check_query_f(hge_ctx, "queries/graphql_query/basic/select_query_user.yaml") + check_query_f(hge_ctx, self.dir() + "/select_query_user.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/graphql_query/basic' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + def test_select_query_non_tracked_table(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/select_query_non_tracked_table_err.yaml") -class TestGraphQLQueryAgg: + def test_select_query_col_not_present_err(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/select_query_author_col_not_present_err.yaml") + + @classmethod + def dir(cls): + return 'queries/graphql_query/basic' + + +class TestGraphQLQueryAgg(DefaultTestSelectQueries): def test_article_agg_count_sum_avg_max_min_with_aliases(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/article_agg_count_sum_avg_max_min_with_aliases.yaml') + check_query_f(hge_ctx, self.dir() + '/article_agg_count_sum_avg_max_min_with_aliases.yaml') def test_article_agg_where(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/article_agg_where.yaml') + check_query_f(hge_ctx, self.dir() + '/article_agg_where.yaml') def test_author_agg_with_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/author_agg_with_articles.yaml') + check_query_f(hge_ctx, self.dir() + '/author_agg_with_articles.yaml') def test_author_agg_with_articles_where(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/author_agg_with_articles_where.yaml') + check_query_f(hge_ctx, self.dir() + '/author_agg_with_articles_where.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/graphql_query/aggregations' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/graphql_query/aggregations' -class TestGraphQLQueryAggPerm: + +class TestGraphQLQueryAggPerm(DefaultTestSelectQueries): def test_author_agg_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/author_agg_articles.yaml') + check_query_f(hge_ctx, self.dir() + '/author_agg_articles.yaml') def test_article_agg_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/article_agg_fail.yaml') + check_query_f(hge_ctx, self.dir() + '/article_agg_fail.yaml') def test_author_articles_agg_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/author_articles_agg_fail.yaml') + check_query_f(hge_ctx, self.dir() + '/author_articles_agg_fail.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/graphql_query/agg_perm' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/graphql_query/agg_perm' -class TestGraphQLQueryLimits: + +class TestGraphQLQueryLimits(DefaultTestSelectQueries): def test_limit_1(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_limit_1.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_limit_1.yaml') def test_limit_2(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_limit_2.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_limit_2.yaml') def test_err_str_limit_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_string_limit_error.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_string_limit_error.yaml') def test_err_neg_limit_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_neg_limit_error.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_neg_limit_error.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/graphql_query/limits' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/graphql_query/limits' -class TestGraphQLQueryOffsets: + +class TestGraphQLQueryOffsets(DefaultTestSelectQueries): def test_offset_1_limit_2(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_offset_1_limit_2.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_offset_1_limit_2.yaml') def test_offset_2_limit_1(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_offset_2_limit_1.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_offset_2_limit_1.yaml') def test_int_as_string_offset(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_string_offset.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_string_offset.yaml') def test_err_neg_offset_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_neg_offset_error.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_neg_offset_error.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/graphql_query/offset' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/graphql_query/offset' -class TestGraphQLQueryBoolExpBasic: +class TestGraphQLQueryBoolExpBasic(DefaultTestSelectQueries): def test_author_article_where_not_equal(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_neq.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_neq.yaml') + + def test_author_article_operator_ne_not_found_err(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/select_author_article_operator_ne_not_found_err.yaml') def test_author_article_where_greater_than(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_gt.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_gt.yaml') def test_author_article_where_greater_than_or_equal(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_gte.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_gte.yaml') def test_author_article_where_less_than(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_lt.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_lt.yaml') def test_author_article_where_less_than_or_equal(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_lte.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_lte.yaml') def test_author_article_where_in(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_in.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_in.yaml') def test_author_article_where_nin(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_nin.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_nin.yaml') def test_order_delivered_at_is_null(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_order_delivered_at_is_null.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_order_delivered_at_is_null.yaml') def test_order_delivered_at_is_not_null(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_order_delivered_at_is_not_null.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_order_delivered_at_is_not_null.yaml') def test_author_article_where_not_less_than(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_not_lt.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_not_lt.yaml') def test_article_author_is_published_and_registered(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_article_author_is_published_and_registered.yaml') + check_query_f(hge_ctx, self.dir() + '/select_article_author_is_published_and_registered.yaml') def test_article_author_not_published_nor_registered(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_article_author_not_published_or_not_registered.yaml') + check_query_f(hge_ctx, self.dir() + '/select_article_author_not_published_or_not_registered.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/graphql_query/boolexp/basic' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + def test_article_author_unexpected_operator_in_where_err(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/select_author_article_unexpected_operator_in_where_err.yaml') + + @classmethod + def dir(cls): + return 'queries/graphql_query/boolexp/basic' -class TestGraphqlQueryPermissions: +class TestGraphqlQueryPermissions(DefaultTestSelectQueries): def test_user_select_unpublished_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/user_select_query_unpublished_articles.yaml') + check_query_f(hge_ctx, self.dir() + '/user_select_query_unpublished_articles.yaml') def test_user_only_other_users_published_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/user_can_query_other_users_published_articles.yaml') + check_query_f(hge_ctx, self.dir() + '/user_can_query_other_users_published_articles.yaml') def test_anonymous_only_published_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/anonymous_can_only_get_published_articles.yaml') + check_query_f(hge_ctx, self.dir() + '/anonymous_can_only_get_published_articles.yaml') def test_user_cannot_access_remarks_col(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/user_cannot_access_remarks_col.yaml') + check_query_f(hge_ctx, self.dir() + '/user_cannot_access_remarks_col.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/graphql_query/permissions' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/graphql_query/permissions' -class TestGraphQLQueryBoolExpSearch: +class TestGraphQLQueryBoolExpSearch(DefaultTestSelectQueries): def test_city_where_like(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_like.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_like.yaml') def test_city_where_not_like(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_nlike.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_nlike.yaml') def test_city_where_ilike(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_ilike.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_ilike.yaml') def test_city_where_not_ilike(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_nilike.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_nilike.yaml') def test_city_where_similar(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_similar.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_similar.yaml') def test_city_where_not_similar(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_not_similar.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_not_similar.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/graphql_query/boolexp/search' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/graphql_query/boolexp/search' -class TestGraphQLQueryBoolExpJsonB: + +class TestGraphQLQueryBoolExpJsonB(DefaultTestSelectQueries): def test_jsonb_contains_article_latest(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_article_author_jsonb_contains_latest.yaml') + check_query_f(hge_ctx, self.dir() + '/select_article_author_jsonb_contains_latest.yaml') def test_jsonb_contains_article_beststeller(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_jsonb_contains_bestseller.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_jsonb_contains_bestseller.yaml') def test_jsonb_contained_in_latest(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_article_author_jsonb_contained_in_latest.yaml') + check_query_f(hge_ctx, self.dir() + '/select_article_author_jsonb_contained_in_latest.yaml') def test_jsonb_contained_in_bestseller_latest(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_article_author_jsonb_contained_in_bestseller_latest.yaml') + check_query_f(hge_ctx, self.dir() + '/select_article_author_jsonb_contained_in_bestseller_latest.yaml') def test_jsonb_has_key_sim_type(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_product_jsonb_has_key_sim_type.yaml') + check_query_f(hge_ctx, self.dir() + '/select_product_jsonb_has_key_sim_type.yaml') def test_jsonb_has_keys_any_os_operating_system(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_product_jsonb_has_keys_any_os_operating_system.yaml') + check_query_f(hge_ctx, self.dir() + '/select_product_jsonb_has_keys_any_os_operating_system.yaml') def test_jsonb_has_keys_all_touchscreen_ram(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_product_jsonb_has_keys_all_ram_touchscreen.yaml') + check_query_f(hge_ctx, self.dir() + '/select_product_jsonb_has_keys_all_ram_touchscreen.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/graphql_query/boolexp/jsonb' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/graphql_query/boolexp/jsonb' -class TestGraphQLQueryOrderBy: + +class TestGraphQLQueryOrderBy(DefaultTestSelectQueries): def test_articles_order_by_without_id(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/articles_order_by_without_id.yaml') + check_query_f(hge_ctx, self.dir() + '/articles_order_by_without_id.yaml') def test_articles_order_by_rel_author_id(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/articles_order_by_rel_author_id.yaml') + check_query_f(hge_ctx, self.dir() + '/articles_order_by_rel_author_id.yaml') def test_articles_order_by_rel_author_rel_contact_phone(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/articles_order_by_rel_author_rel_contact_phone.yaml') + check_query_f(hge_ctx, self.dir() + '/articles_order_by_rel_author_rel_contact_phone.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/graphql_query/order_by' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/graphql_query/order_by' diff --git a/server/tests-py/test_jwt.py b/server/tests-py/test_jwt.py new file mode 100644 index 00000000000..fb17e58107c --- /dev/null +++ b/server/tests-py/test_jwt.py @@ -0,0 +1,97 @@ + +import pytest +import yaml +import jwt +from validate import check_query + +if not pytest.config.getoption("--hge-jwt-key-file"): + pytest.skip("--hge-jwt-key-file is missing, skipping JWT basic tests", allow_module_level=True) + + +class TestJWTBasic: + + def test_jwt_invalid_role_in_request_header(self, hge_ctx): + self.claims['https://hasura.io/jwt/claims'] = { + "x-hasura-user-id": "1", + "x-hasura-allowed-roles": ["contractor", "editor"], + "x-hasura-default-role": "contractor" + } + token = jwt.encode(self.claims, hge_ctx.hge_jwt_key, algorithm='RS512' ).decode('UTF-8') + self.conf['headers']['Authorization'] = 'Bearer ' + token + self.conf['response'] = { + 'errors': [{ + 'path': '$', + 'code': 'access-denied', + 'error': 'Your current role is not in allowed roles' + }] + } + self.conf['status'] = 400 + check_query(hge_ctx, self.conf, add_auth=False) + + def test_jwt_no_allowed_roles_in_claim(self, hge_ctx): + self.claims['https://hasura.io/jwt/claims'] = { + "x-hasura-user-id": "1", + "x-hasura-default-role": "user" + } + token = jwt.encode(self.claims, hge_ctx.hge_jwt_key, algorithm='RS512' ).decode('UTF-8') + self.conf['headers']['Authorization'] = 'Bearer ' + token + self.conf['response'] = { + 'errors': [{ + 'path': '$', + 'code': 'jwt-missing-role-claims', + 'error': 'JWT claim does not contain x-hasura-allowed-roles' + }] + } + self.conf['status'] = 400 + check_query(hge_ctx, self.conf, add_auth=False) + + def test_jwt_invalid_allowed_roles_in_claim(self, hge_ctx): + self.claims['https://hasura.io/jwt/claims'] = { + "x-hasura-user-id": "1", + "x-hasura-allowed-roles": "user", + "x-hasura-default-role": "user" + } + token = jwt.encode(self.claims, hge_ctx.hge_jwt_key, algorithm='RS512' ).decode('UTF-8') + self.conf['headers']['Authorization'] = 'Bearer ' + token + self.conf['response'] = { + 'errors': [{ + 'path': '$', + 'code': 'jwt-invalid-claims', + 'error': 'invalid x-hasura-allowed-roles; should be a list of roles' + }] + } + self.conf['status'] = 400 + check_query(hge_ctx, self.conf, add_auth=False) + + def test_jwt_no_default_role(self, hge_ctx): + self.claims['https://hasura.io/jwt/claims'] = { + "x-hasura-user-id": "1", + "x-hasura-allowed-roles": ["user"], + } + token = jwt.encode(self.claims, hge_ctx.hge_jwt_key, algorithm='RS512' ).decode('UTF-8') + self.conf['headers']['Authorization'] = 'Bearer ' + token + self.conf['response'] = { + 'errors': [{ + 'path': '$', + 'code': 'jwt-missing-role-claims', + 'error': 'JWT claim does not contain x-hasura-default-role' + }] + } + self.conf['status'] = 400 + check_query(hge_ctx, self.conf, add_auth=False) + + @pytest.fixture(autouse=True) + def transact(self, request, hge_ctx): + self.dir = 'queries/graphql_query/permissions' + st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') + assert st_code == 200, resp + with open(self.dir + '/user_select_query_unpublished_articles.yaml') as c: + self.conf = yaml.safe_load(c) + self.claims = { + "sub": "1234567890", + "name": "John Doe", + "iat": 1516239022 + } + yield + st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') + assert st_code == 200, resp diff --git a/server/tests-py/test_subscriptions.py b/server/tests-py/test_subscriptions.py index 74438638c3e..9fd15e97541 100644 --- a/server/tests-py/test_subscriptions.py +++ b/server/tests-py/test_subscriptions.py @@ -9,6 +9,8 @@ import yaml Refer: https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init ''' def test_init_without_payload(hge_ctx): + if hge_ctx.hge_key is not None: + pytest.skip("Payload is needed when access key is set") obj = { 'type': 'connection_init' } @@ -20,9 +22,16 @@ def test_init_without_payload(hge_ctx): Refer: https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init ''' def test_init(hge_ctx): + payload = {} + if hge_ctx.hge_key is not None: + payload = { + 'headers' : { + 'X-Hasura-Access-Key': hge_ctx.hge_key + } + } obj = { 'type': 'connection_init', - 'payload': {}, + 'payload': payload, } hge_ctx.ws.send(json.dumps(obj)) ev = hge_ctx.get_ws_event(3) @@ -146,8 +155,16 @@ class TestSubscriptionLiveQueries(object): ''' Create connection using connection_init ''' + payload = {} + if hge_ctx.hge_key is not None: + payload = { + 'headers' : { + 'X-Hasura-Access-Key': hge_ctx.hge_key + } + } obj = { - 'type': 'connection_init' + 'type': 'connection_init', + 'payload' : payload } hge_ctx.ws.send(json.dumps(obj)) ev = hge_ctx.get_ws_event(3) diff --git a/server/tests-py/test_v1_queries.py b/server/tests-py/test_v1_queries.py index 31f9e8a5199..7ff35c7fa8a 100644 --- a/server/tests-py/test_v1_queries.py +++ b/server/tests-py/test_v1_queries.py @@ -1,452 +1,459 @@ - -import pytest import yaml from validate import check_query_f +from super_classes import DefaultTestSelectQueries, DefaultTestQueries -class TestV1SelectBasic: +class TestV1General(DefaultTestQueries): + + def test_query_string_input_err(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/query_string_input_err.yaml') + + def test_query_unknown_type_err(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/query_unknown_type_err.yaml') + + def test_query_args_as_string_err(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/query_args_as_string_err.yaml') + + @classmethod + def dir(cls): + return "queries/v1/basic" + +class TestV1SelectBasic(DefaultTestSelectQueries): def test_select_query_author(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_article.yaml') + check_query_f(hge_ctx, self.dir() + '/select_article.yaml') def test_nested_select_article_author(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/nested_select_query_article_author.yaml') + check_query_f(hge_ctx, self.dir() + '/nested_select_query_article_author.yaml') + + def test_nested_select_article_author_alias_for_relationship(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/nested_select_query_article_author_rel_alias.yaml') def test_select_author_where(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_author_where.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_author_where.yaml') + + def test_select_table_not_present(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/select_article_table_not_present_err.yaml') def test_select_col_not_present(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_article_col_not_present_err.yaml') - def test_nested_select_query_where(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/nested_select_where_query_author_article.yaml') + check_query_f(hge_ctx, self.dir() + '/select_article_col_not_present_err.yaml') + + def test_select_nested_where_query(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/nested_select_where_query_author_article.yaml') def test_select_query_user(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_user.yaml') + check_query_f(hge_ctx, self.dir() + '/select_user.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/select/basic" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/select/basic" -class TestV1SelectLimits: +class TestV1SelectLimits(DefaultTestSelectQueries): def test_limit_1(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_limit_1.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_limit_1.yaml') def test_limit_2(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_limit_2.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_limit_2.yaml') def test_err_str_limit_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_string_limit_error.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_string_limit_error.yaml') def test_err_neg_limit_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_neg_limit_error.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_neg_limit_error.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/v1/select/limits' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/v1/select/limits' -class TestV1SelectOffset: +class TestV1SelectOffset(DefaultTestSelectQueries): def test_offset_1_limit_2(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_offset_1_limit_2.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_offset_1_limit_2.yaml') def test_offset_2_limit_1(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_offset_2_limit_1.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_offset_2_limit_1.yaml') def test_int_as_string_offset_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_int_as_string_offset_error.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_int_as_string_offset_error.yaml') def test_err_neg_offset_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_article_neg_offset_error.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_article_neg_offset_error.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/v1/select/offset' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/v1/select/offset' -class TestV1SelectBoolExpBasic: +class TestV1SelectBoolExpBasic(DefaultTestSelectQueries): def test_author_article_where_not_equal(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_neq.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_neq.yaml') def test_author_article_where_greater_than(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_gt.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_gt.yaml') def test_author_article_where_greater_than_or_equal(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_gte.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_gte.yaml') def test_author_article_where_less_than(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_lt.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_lt.yaml') def test_author_article_where_less_than_or_equal(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_lte.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_lte.yaml') def test_author_article_where_in(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_in.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_in.yaml') def test_author_article_where_nin(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_nin.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_nin.yaml') def test_order_delivered_at_is_null(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_order_delivered_at_is_null.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_order_delivered_at_is_null.yaml') def test_order_delivered_at_is_not_null(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_query_order_delivered_at_is_not_null.yaml') + check_query_f(hge_ctx, self.dir() + '/select_query_order_delivered_at_is_not_null.yaml') def test_author_article_where_not_less_than(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_author_article_where_not_lt.yaml') + check_query_f(hge_ctx, self.dir() + '/select_author_article_where_not_lt.yaml') def test_article_author_is_published_and_registered(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_article_author_is_published_and_registered.yaml') + check_query_f(hge_ctx, self.dir() + '/select_article_author_is_published_and_registered.yaml') def test_article_author_not_published_nor_registered(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_article_author_not_published_or_not_registered.yaml') + check_query_f(hge_ctx, self.dir() + '/select_article_author_not_published_or_not_registered.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/v1/select/boolexp/basic' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/v1/select/boolexp/basic' -class TestV1SelectBoolExpSearch: +class TestV1SelectBoolExpSearch(DefaultTestSelectQueries): def test_city_where_like(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_like.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_like.yaml') def test_city_where_not_like(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_nlike.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_nlike.yaml') def test_city_where_ilike(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_ilike.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_ilike.yaml') def test_city_where_not_ilike(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_nilike.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_nilike.yaml') def test_city_where_similar(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_similar.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_similar.yaml') def test_city_where_not_similar(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/select_city_where_not_similar.yaml') + check_query_f(hge_ctx, self.dir() + '/select_city_where_not_similar.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/v1/select/boolexp/search' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/v1/select/boolexp/search' -class TestV1SelectPermissions: +class TestV1SelectPermissions(DefaultTestSelectQueries): def test_user_select_unpublished_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/user_select_query_unpublished_articles.yaml') + check_query_f(hge_ctx, self.dir() + '/user_select_query_unpublished_articles.yaml') def test_user_only_other_users_published_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/user_can_query_other_users_published_articles.yaml') + check_query_f(hge_ctx, self.dir() + '/user_can_query_other_users_published_articles.yaml') def test_anonymous_only_published_articles(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/anonymous_can_only_get_published_articles.yaml') + check_query_f(hge_ctx, self.dir() + '/anonymous_can_only_get_published_articles.yaml') def test_user_cannot_access_remarks_col(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/user_cannot_access_remarks_col.yaml') + check_query_f(hge_ctx, self.dir() + '/user_cannot_access_remarks_col.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = 'queries/v1/select/permissions' - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return 'queries/v1/select/permissions' -class TestV1InsertBasic: +class TestV1InsertBasic(DefaultTestQueries): def test_insert_author(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/insert_author.yaml') + check_query_f(hge_ctx, self.dir() + '/insert_author.yaml') def test_insert_author_col_not_present_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/insert_author_col_not_present_err.yaml') + check_query_f(hge_ctx, self.dir() + '/insert_author_col_not_present_err.yaml') def test_insert_null_col_value(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/order_col_shipped_null.yaml") + check_query_f(hge_ctx, self.dir() + "/order_col_shipped_null.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/insert/basic" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/insert/basic" -class TestV1InsertOnConflict: - +class TestV1InsertOnConflict(DefaultTestQueries): + def test_author_on_conflict_update(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/upsert_author.yaml') + check_query_f(hge_ctx, self.dir() + '/upsert_author.yaml') def test_on_conflict_no_action_specified(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_on_conflict_no_action_specified.yaml") + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_no_action_specified.yaml") def test_on_conflict_ignore(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_on_conflict_ignore_constraint.yaml") + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_ignore_constraint.yaml") + hge_ctx.may_skip_test_teardown = True def test_err_missing_article_constraint(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_on_conflict_error_missing_article_constraint.yaml") + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_error_missing_article_constraint.yaml") def test_err_unexpected_action(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_unexpected_on_conflict_action.yaml") + check_query_f(hge_ctx, self.dir() + "/article_unexpected_on_conflict_action.yaml") def test_err_unexpected_constraint(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_unexpected_on_conflict_constraint_error.yaml") + check_query_f(hge_ctx, self.dir() + "/article_unexpected_on_conflict_constraint_error.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/insert/onconflict" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/insert/onconflict" -class TestV1InsertPermissions(object): +class TestV1InsertPermissions(DefaultTestQueries): def test_user_role_on_conflict_update(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/article_on_conflict_user_role.yaml") + check_query_f(hge_ctx, self.dir() + "/article_on_conflict_user_role.yaml") - @pytest.mark.xfail(reason="Refer https://github.com/hasura/graphql-engine/issues/563") def test_user_role_on_conflict_ignore(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_on_conflict_ignore_user_role.yaml") - + check_query_f(hge_ctx, self.dir() + "/author_on_conflict_ignore_user_role.yaml") + hge_ctx.may_skip_test_teardown = True + def test_role_has_no_permissions_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/address_permission_error.yaml") + check_query_f(hge_ctx, self.dir() + "/address_permission_error.yaml") def test_author_user_role_insert_check_perm_success(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_perm_success.yaml") + check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_perm_success.yaml") def test_user_role_insert_check_is_registered_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_is_registered_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_is_registered_fail.yaml") def test_user_role_insert_check_user_id_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_user_id_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_user_id_fail.yaml") def test_student_role_insert_check_bio_success(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_student_role_insert_check_bio_success.yaml") + check_query_f(hge_ctx, self.dir() + "/author_student_role_insert_check_bio_success.yaml") def test_student_role_insert_check_bio_fail(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_student_role_insert_check_bio_fail.yaml") + check_query_f(hge_ctx, self.dir() + "/author_student_role_insert_check_bio_fail.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/insert/permissions" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/insert/permissions" -class TestV1UpdateBasic: +class TestV1UpdateBasic(DefaultTestQueries): def test_set_author_name(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/author_set_name.yaml") + check_query_f(hge_ctx, self.dir() + "/author_set_name.yaml") def test_set_person_details(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_set_details.yaml") + check_query_f(hge_ctx, self.dir() + "/person_set_details.yaml") def test_person_id_inc(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_inc.yaml") + check_query_f(hge_ctx, self.dir() + "/person_inc.yaml") def test_product_mul_price(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/product_mul_price.yaml") + check_query_f(hge_ctx, self.dir() + "/product_mul_price.yaml") def test_product_set_default_price(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/product_set_default_price.yaml") + check_query_f(hge_ctx, self.dir() + "/product_set_default_price.yaml") def test_no_operator_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_error_no_operator.yaml") + check_query_f(hge_ctx, self.dir() + "/person_error_no_operator.yaml") def test_no_where_clause_err(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/person_error_no_where_clause.yaml") + check_query_f(hge_ctx, self.dir() + "/person_error_no_where_clause.yaml") - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/update/basic" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/update/basic" -class TestV1UpdatePermissions: +class TestV1UpdatePermissions(DefaultTestQueries): def test_user_can_update_unpublished_article(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_can_update_unpublished_article.yaml") + check_query_f(hge_ctx, self.dir() + "/user_can_update_unpublished_article.yaml") def test_user_cannot_update_published_version_col(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_cannot_update_published_article_version.yaml") + check_query_f(hge_ctx, self.dir() + "/user_cannot_update_published_article_version.yaml") + hge_ctx.may_skip_test_teardown = True def test_user_cannot_update_another_users_article(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_cannot_update_another_users_article.yaml") + check_query_f(hge_ctx, self.dir() + "/user_cannot_update_another_users_article.yaml") + hge_ctx.may_skip_test_teardown = True def test_user_cannot_update_id_col(self, hge_ctx): - check_query_f(hge_ctx, self.dir + "/user_cannot_update_id_col_article.yaml") + check_query_f(hge_ctx, self.dir() + "/user_cannot_update_id_col_article.yaml") + hge_ctx.may_skip_test_teardown = True - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/update/permissions" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/update/permissions" -class TestV1Delete: - +class TestV1CountBasic(DefaultTestSelectQueries): + + def test_count_authors(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/count_authors.yaml") + + def test_count_published_articles(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/count_published_articles.yaml") + + def test_count_unpublished_articles(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/count_unpublished_articles.yaml") + + def test_count_distinct_authors_with_published_articles(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/count_distinct_authors_with_published_articles.yaml") + + def test_count_articles_registered_authors(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/count_articles_with_registered_authors.yaml") + + def test_count_articles_non_registered_authors(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/count_articles_with_non_registered_authors.yaml") + + def test_count_distinct_col_not_present_err(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/count_distinct_col_not_present_err.yaml") + + def test_count_distinct_authors_with_unpublished_articles(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/count_distinct_authors_with_unpublished_articles.yaml") + + @classmethod + def dir(cls): + return "queries/v1/count/basic" + + +class TestV1CountPermissions(DefaultTestSelectQueries): + + def test_count_user_has_no_select_permission_err(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/count_user_has_no_select_perm_error.yaml") + + def test_count_other_users_unpublished_articles(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + "/count_users_unpublished_articles.yaml") + + @classmethod + def dir(cls): + return "queries/v1/count/permissions" + + +class TestV1Delete(DefaultTestQueries): + def test_delete_author(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/delete_article.yaml') + check_query_f(hge_ctx, self.dir() + '/delete_article.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/delete" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/delete" -class TestMetadata: + +class TestMetadata(DefaultTestQueries): def test_reload_metadata(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/reload_metadata.yaml') + check_query_f(hge_ctx, self.dir() + '/reload_metadata.yaml') def test_export_metadata(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/export_metadata.yaml') + check_query_f(hge_ctx, self.dir() + '/export_metadata.yaml') def test_clear_metadata(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/clear_metadata.yaml') + check_query_f(hge_ctx, self.dir() + '/clear_metadata.yaml') def test_dump_internal_state(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/dump_internal_state.yaml') + check_query_f(hge_ctx, self.dir() + '/dump_internal_state.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/metadata" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/metadata" -class TestRunSQL: +class TestRunSQL(DefaultTestQueries): def test_select_query(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/sql_select_query.yaml') + check_query_f(hge_ctx, self.dir() + '/sql_select_query.yaml') + hge_ctx.may_skip_test_teardown = True def test_set_timezone(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/sql_set_timezone.yaml') + check_query_f(hge_ctx, self.dir() + '/sql_set_timezone.yaml') + hge_ctx.may_skip_test_teardown = True - def test_sql_timezone__error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/sql_set_timezone_error.yaml') + def test_sql_timezone_error(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/sql_set_timezone_error.yaml') def test_sql_query_as_user_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/sql_query_as_user_error.yaml') + check_query_f(hge_ctx, self.dir() + '/sql_query_as_user_error.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/run_sql" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/run_sql" -class TestRelationships: +class TestRelationships(DefaultTestQueries): def test_object_relationship_foreign_key(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/object_relationship_foreign_key.yaml') + check_query_f(hge_ctx, self.dir() + '/object_relationship_foreign_key.yaml') def test_create_object_relationship_as_not_admin_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/create_object_relationship_as_not_admin_error.yaml') + check_query_f(hge_ctx, self.dir() + '/create_object_relationship_as_not_admin_error.yaml') def test_object_relationship_col_not_foreign_key_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/object_relationship_col_not_foreign_key_error.yaml') + check_query_f(hge_ctx, self.dir() + '/object_relationship_col_not_foreign_key_error.yaml') def test_object_relationship_foreign_key_non_public_schema(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/object_relationship_non_public_schema_foreign_key.yaml') + check_query_f(hge_ctx, self.dir() + '/object_relationship_non_public_schema_foreign_key.yaml') def test_object_relationship_manual(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/object_relationship_manual.yaml') + check_query_f(hge_ctx, self.dir() + '/object_relationship_manual.yaml') def test_array_relationship_foreign_key(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/array_relationship_foreign_key.yaml') + check_query_f(hge_ctx, self.dir() + '/array_relationship_foreign_key.yaml') def test_create_array_relationship_as_not_admin_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/create_array_relationship_as_not_admin_error.yaml') + check_query_f(hge_ctx, self.dir() + '/create_array_relationship_as_not_admin_error.yaml') def test_array_relationship_col_not_foreign_key_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/array_relationship_col_not_foreign_key_error.yaml') + check_query_f(hge_ctx, self.dir() + '/array_relationship_col_not_foreign_key_error.yaml') def test_array_relationship_foreign_key_non_public_schema(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/array_relationship_non_public_schema_foreign_key.yaml') + check_query_f(hge_ctx, self.dir() + '/array_relationship_non_public_schema_foreign_key.yaml') def test_array_relationship_manual(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/array_relationship_manual.yaml') + check_query_f(hge_ctx, self.dir() + '/array_relationship_manual.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/relationships" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/relationships" -class TestTrackTables: +class TestTrackTables(DefaultTestQueries): def test_track_untrack_table(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/track_untrack_table.yaml') + check_query_f(hge_ctx, self.dir() + '/track_untrack_table.yaml') + hge_ctx.may_skip_test_teardown = True def test_track_untrack_table_non_public_schema(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/track_untrack_table_non_public_schema.yaml') + check_query_f(hge_ctx, self.dir() + '/track_untrack_table_non_public_schema.yaml') + hge_ctx.may_skip_test_teardown = True def test_track_untrack_table_as_not_admin_error(self, hge_ctx): - check_query_f(hge_ctx, self.dir + '/track_untrack_table_as_not_admin_error.yaml') + check_query_f(hge_ctx, self.dir() + '/track_untrack_table_as_not_admin_error.yaml') - @pytest.fixture(autouse=True) - def transact(self, request, hge_ctx): - self.dir = "queries/v1/track_table" - st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') - assert st_code == 200, resp - yield - st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml') - assert st_code == 200, resp + @classmethod + def dir(cls): + return "queries/v1/track_table" + + +class TestCreatePermission(DefaultTestQueries): + + def test_create_permission_admin_role_error(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/create_article_permission_role_admin_error.yaml') + + def test_create_permission_user_role_error(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/create_article_permission_role_user.yaml') + + @classmethod + def dir(cls): + return "queries/v1/permissions" diff --git a/server/tests-py/test_webhook_insecure.py b/server/tests-py/test_webhook_insecure.py new file mode 100644 index 00000000000..3628182607f --- /dev/null +++ b/server/tests-py/test_webhook_insecure.py @@ -0,0 +1,18 @@ +import pytest +from validate import check_query_f +from super_classes import DefaultTestSelectQueries + +if not pytest.config.getoption("--test-webhook-insecure"): + pytest.skip("--test-webhook-https-insecure flag is missing, skipping tests", allow_module_level=True) + +class TestHTTPSWebhookInsecure(DefaultTestSelectQueries): + + def test_user_select_unpublished_articles_err(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/user_select_query_unpublished_articles_fail.yaml') + + def test_user_only_other_users_published_articles_err(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/user_query_other_users_published_articles_fail.yaml') + + @classmethod + def dir(cls): + return 'webhook/insecure' diff --git a/server/tests-py/validate.py b/server/tests-py/validate.py index 6b8790bc4db..b8361f9fbd5 100644 --- a/server/tests-py/validate.py +++ b/server/tests-py/validate.py @@ -1,6 +1,11 @@ #!/usr/bin/env python3 import yaml +import json +import os +import base64 +import jsondiff +import jwt def check_keys(keys, obj): for k in keys: @@ -40,25 +45,102 @@ def check_event(hge_ctx, trig_name, table, operation, exp_ev_data, headers, webh assert ev['op'] == operation, ev assert ev['data'] == exp_ev_data, ev -def check_query(hge_ctx, conf): + +def test_forbidden_when_access_key_reqd(hge_ctx, conf): headers={} if 'headers' in conf: headers = conf['headers'] + + #Test without access key code, resp = hge_ctx.anyq( conf['url'], conf['query'], headers) + assert code == 401, "\n" + yaml.dump( { + "expected" : "Should be access denied as access key is not provided", + "actual" : { + "code" : code, + "response" : resp + } + } ) + + #Test with random access key + headers['X-Hasura-Access-Key'] = base64.b64encode(os.urandom(30)) + code, resp = hge_ctx.anyq( conf['url'], conf['query'], headers) + assert code == 401, "\n" + yaml.dump( { + "expected" : "Should be access denied as an incorrect access key is provided", + "actual" : { + "code" : code, + "response" : resp + } + } ) + +def test_forbidden_webhook(hge_ctx, conf): + + h = { 'Authorization' : 'Bearer ' + base64.b64encode(base64.b64encode(os.urandom(30))).decode('utf-8') } + code, resp = hge_ctx.anyq( conf['url'], conf['query'], h ) + assert code == 401, "\n" + yaml.dump( { + "expected" : "Should be access denied as it is denied from webhook", + "actual" : { + "code" : code, + "response" : resp + } + } ) + +def check_query(hge_ctx, conf, add_auth=True): + headers={} + if 'headers' in conf: + headers = conf['headers'] + + if add_auth: + if hge_ctx.hge_jwt_key is not None and len(headers) > 0 and 'X-Hasura-Role' in headers: + hClaims=dict() + hClaims['X-Hasura-Allowed-Roles']=[headers['X-Hasura-Role']] + hClaims['X-Hasura-Default-Role']=headers['X-Hasura-Role'] + for key in headers: + if key != 'X-Hasura-Role': + hClaims[key]=headers[key] + claim={ + "sub": "foo", + "name": "bar", + "https://hasura.io/jwt/claims": hClaims + } + headers['Authorization'] = 'Bearer ' + jwt.encode(claim, hge_ctx.hge_jwt_key, algorithm='RS512').decode('UTF-8') + + if hge_ctx.hge_webhook is not None and len(headers) > 0: + if not hge_ctx.webhook_insecure: + test_forbidden_webhook(hge_ctx, conf) + headers['X-Hasura-Auth-Mode'] = 'webhook' + headers_new = dict() + headers_new['Authorization'] = 'Bearer ' + base64.b64encode(json.dumps(headers).encode('utf-8')).decode('utf-8') + headers = headers_new + + elif (hge_ctx.hge_webhook is not None or hge_ctx.hge_jwt_key is not None) and hge_ctx.hge_key is not None and len(headers) == 0: + headers['X-Hasura-Access-Key'] = hge_ctx.hge_key + + elif hge_ctx.hge_key is not None and hge_ctx.hge_webhook is None and hge_ctx.hge_jwt_key is None: + test_forbidden_when_access_key_reqd(hge_ctx, conf) + headers['X-Hasura-Access-Key'] = hge_ctx.hge_key + + code, resp = hge_ctx.anyq( conf['url'], conf['query'], headers) + print (headers) assert code == conf['status'], resp if 'response' in conf: - print ('response\n', yaml.dump(resp)) - print ('expected\n', yaml.dump(conf['response'])) - assert json_ordered(resp) == json_ordered(conf['response']) + assert json_ordered(resp) == json_ordered(conf['response']) , yaml.dump( { + 'response' : resp, + 'expected' : conf['response'], + 'diff': jsondiff.diff(conf['response'], resp) + } ) + return code, resp -def check_query_f(hge_ctx, f): +def check_query_f(hge_ctx, f, add_auth=True): + hge_ctx.may_skip_test_teardown = False with open(f) as c: - conf = yaml.load(c) + conf = yaml.safe_load(c) if isinstance(conf, list): for sconf in conf: check_query( hge_ctx, sconf) else: - check_query( hge_ctx, conf ) + if conf['status'] != 200: + hge_ctx.may_skip_test_teardown = True + check_query( hge_ctx, conf, add_auth ) def json_ordered(obj): if isinstance(obj, dict): diff --git a/server/tests-py/webhook.py b/server/tests-py/webhook.py new file mode 100644 index 00000000000..020b510dd3e --- /dev/null +++ b/server/tests-py/webhook.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +""" +Webhook for GraphQL engine +For successful authentication +1) Add header X-Hasura-Auth-From: webhook to the list of headers +2) Base64 encode the required headers (including X-Hasura-Auth-From) +3) Pass it as the bearer token with the Authorization header +""" +import base64 +import json +import ssl +import http.server +import traceback +import sys + +class S(http.server.BaseHTTPRequestHandler): + + def do_GET(self): + if 'Authorization' in self.headers: + auth = self.headers['Authorization'] + h = dict() + if auth.startswith("Bearer "): + try: + h = json.loads(base64.b64decode(auth[7:]).decode("utf-8")) + if h.get('X-Hasura-Auth-Mode') == 'webhook': + print (h) + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(h).encode('utf-8')) + else: + print ('forbidden') + self.send_response(401) + self.end_headers() + self.wfile.write('{}') + except Exception as e: + print ('forbidden') + self.send_response(401) + self.end_headers() + print("type error: " + str(e)) + print(traceback.format_exc()) + else: + self.send_response(401) + self.end_headers() + print ('forbidden') + +def run(keyfile, certfile, server_class=http.server.HTTPServer, handler_class=S, port=9090): + server_address = ('', port) + httpd = server_class(server_address, handler_class) + httpd.socket = ssl.wrap_socket ( + httpd.socket, + certfile=certfile, + keyfile=keyfile, + server_side=True, + ssl_version=ssl.PROTOCOL_SSLv23) + print('Starting httpd...') + httpd.serve_forever() + +if __name__ == "__main__": + + if len(sys.argv) != 4: + print("Usage: python webhook.py port keyfile certfile") + sys.exit(1) + run(keyfile=sys.argv[2],certfile=sys.argv[3], port=int(sys.argv[1])) diff --git a/server/tests-py/webhook/insecure/setup.yaml b/server/tests-py/webhook/insecure/setup.yaml new file mode 100644 index 00000000000..60266f0dcff --- /dev/null +++ b/server/tests-py/webhook/insecure/setup.yaml @@ -0,0 +1,170 @@ +type: bulk +args: + +#Author table +- type: run_sql + args: + sql: | + create table author( + id serial primary key, + name text unique, + is_registered boolean not null default false, + remarks_internal text + ); +- type: track_table + args: + schema: public + name: author + +#Article table +- type: run_sql + args: + sql: | + CREATE TABLE article ( + id SERIAL PRIMARY KEY, + title TEXT, + content TEXT, + author_id INTEGER NOT NULL REFERENCES author(id), + is_published BOOLEAN NOT NULL default FALSE, + published_on TIMESTAMP + ) +- type: track_table + args: + schema: public + name: article + +#Object relationship +- type: create_object_relationship + args: + name: author + table: article + using: + foreign_key_constraint_on: author_id + +#Array relationship +- type: create_array_relationship + args: + table: author + name: articles + using: + foreign_key_constraint_on: + table: article + column: author_id + +#Article select permission for user +- type: create_select_permission + args: + table: article + role: user + permission: + columns: + - id + - title + - content + - is_published + filter: + $or: + - author_id: X-HASURA-USER-ID + - is_published: true + +#Article select permission for anonymous (only published articles) +- type: create_select_permission + args: + table: article + role: anonymous + permission: + columns: + - id + - title + - content + - is_published + filter: + is_published: true + +#Article insert permission for user +- type: create_insert_permission + args: + table: article + role: user + permission: + check: + author_id: X-Hasura-User-Id + +#Author select permission for user +- type: create_select_permission + args: + table: author + role: user + permission: + columns: + - id + - name + - is_registered + filter: + _or: + - id: X-HASURA-USER-ID + - articles: + is_published: + _eq: true + +#Author select permission for anonymous users +#Only authors with atleast one article will be shown +- type: create_select_permission + args: + table: author + role: anonymous + permission: + columns: + - id + - name + filter: + articles: + is_published: + _eq: true + +#Author insert permission for user +- type: create_insert_permission + args: + table: author + role: user + permission: + check: + id: X-HASURA-USER-ID + allow_upsert: true + +#Insert Author values +- type: insert + args: + table: author + objects: + - name: Author 1 + remarks_internal: remark 1 + - name: Author 2 + remarks_internal: remark 2 + - name: Author 3 + remarks_internal: remark 3 + +#Insert Article values +- type: insert + args: + table: article + objects: + - title: Article 1 + content: Sample article content 1 + author_id: 1 + is_published: false + + - title: Article 2 + content: Sample article content 2 + author_id: 1 + is_published: true + + - title: Article 3 + content: Sample article content 3 + author_id: 2 + is_published: true + + - title: Article 4 + content: Sample article content 4 + author_id: 3 + is_published: false diff --git a/server/tests-py/webhook/insecure/teardown.yaml b/server/tests-py/webhook/insecure/teardown.yaml new file mode 100644 index 00000000000..2b72c359b5a --- /dev/null +++ b/server/tests-py/webhook/insecure/teardown.yaml @@ -0,0 +1,14 @@ +type: bulk +args: + +- type: run_sql + args: + sql: | + drop table article + cascade: true + +- type: run_sql + args: + sql: | + drop table author + cascade: true diff --git a/server/tests-py/webhook/insecure/user_query_other_users_published_articles_fail.yaml b/server/tests-py/webhook/insecure/user_query_other_users_published_articles_fail.yaml new file mode 100644 index 00000000000..3ac1081f5da --- /dev/null +++ b/server/tests-py/webhook/insecure/user_query_other_users_published_articles_fail.yaml @@ -0,0 +1,26 @@ +description: User can only get his unpublished articles +url: /v1alpha1/graphql +status: 500 +headers: + X-Hasura-Role: user + X-Hasura-User-Id: '1' +query: + query: | + query { + article ( + where: { _and: + [ { is_published : {_eq : true}} + { author : { id : {_neq : 1}}} + ] + } + ) { + id + title + content + is_published + author { + id + name + } + } + } diff --git a/server/tests-py/webhook/insecure/user_select_query_unpublished_articles_fail.yaml b/server/tests-py/webhook/insecure/user_select_query_unpublished_articles_fail.yaml new file mode 100644 index 00000000000..ba982e93d59 --- /dev/null +++ b/server/tests-py/webhook/insecure/user_select_query_unpublished_articles_fail.yaml @@ -0,0 +1,22 @@ +description: User can only get his unpublished articles +url: /v1alpha1/graphql +status: 500 +headers: + X-Hasura-Role: user + X-Hasura-User-Id: '1' +query: + query: | + query { + article ( + where: { is_published : {_eq : false}} + ) { + id + title + content + is_published + author { + id + name + } + } + }