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.
This commit is contained in:
nizar-m 2018-10-28 23:57:49 +05:30 committed by Vamshi Surabhi
parent 58582be644
commit 0ffb0478b9
75 changed files with 3305 additions and 1048 deletions

View File

@ -90,34 +90,19 @@ refs:
at: /build at: /build
- *wait_for_postgres - *wait_for_postgres
- run: - run:
name: run the server in background name: Run Python tests
working_directory: ./server
environment: environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://gql_test:@localhost:5432/gql_test HASURA_GRAPHQL_DATABASE_URL: 'postgres://gql_test:@localhost:5432/gql_test'
EVENT_WEBHOOK_HEADER: MyEnvValue GRAPHQL_ENGINE: '/build/_server_output/graphql-engine'
background: true
command: | 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: - run:
name: create test output dir name: Generate coverage report
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
working_directory: ./server working_directory: ./server
command: | command: |
kill -2 $(ps -e | grep graphql-engine | awk '{print $1}') stack --system-ghc hpc report /build/_server_test_output/$PG_VERSION/graphql-engine.tix --destdir /build/_server_test_output/$PG_VERSION
stack --system-ghc hpc report graphql-engine.tix --destdir /build/_server_test_output/$PG_VERSION
- store_artifacts: - store_artifacts:
path: /build/_server_test_output path: /build/_server_test_output
destination: server_test destination: server_test

206
.circleci/test-server.sh Executable file
View File

@ -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"

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ npm-debug.log
*.temp *.temp
*.DS_Store *.DS_Store
.tern-project .tern-project
test-server-output

View File

@ -128,5 +128,9 @@ dmypy.json
pyvenv.cfg pyvenv.cfg
pip-selfcheck.json pip-selfcheck.json
# End of https://www.gitignore.io/api/python # End of https://www.gitignore.io/api/python
*.pem
ca.srl
webhook-req.cnf
webhook.csr

View File

@ -9,17 +9,45 @@ def pytest_addoption(parser):
parser.addoption( parser.addoption(
"--pg-url", metavar="PG_URL", help="url for connecting to Postgres directly", required=True "--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') @pytest.fixture(scope='session')
def hge_ctx(request): def hge_ctx(request):
print ("create hge_ctx") print ("create hge_ctx")
hge_url = request.config.getoption('--hge-url') hge_url = request.config.getoption('--hge-url')
pg_url = request.config.getoption('--pg-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: 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: except HGECtxError as e:
pytest.exit(str(e)) pytest.exit(str(e))
yield hge_ctx # provide the fixture value yield hge_ctx # provide the fixture value
print("teardown hge_ctx") print("teardown hge_ctx")
hge_ctx.teardown() hge_ctx.teardown()
time.sleep(2) 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)

View File

@ -53,7 +53,7 @@ class WebhookServer(http.server.HTTPServer):
self.socket.bind(self.server_address) self.socket.bind(self.server_address)
class HGECtx: 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) server_address = ('0.0.0.0', 5592)
self.resp_queue = queue.Queue(maxsize=1) self.resp_queue = queue.Queue(maxsize=1)
@ -69,6 +69,15 @@ class HGECtx:
self.http = requests.Session() self.http = requests.Session()
self.hge_url = hge_url 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 = urlparse(hge_url)
self.ws_url = self.ws_url._replace(scheme='ws') self.ws_url = self.ws_url._replace(scheme='ws')
@ -78,7 +87,7 @@ class HGECtx:
self.wst.daemon = True self.wst.daemon = True
self.wst.start() 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() self.version = result.stdout.decode('utf-8').strip()
try: try:
st_code, resp = self.v1q_f('queries/clear_db.yaml') st_code, resp = self.v1q_f('queries/clear_db.yaml')
@ -108,6 +117,7 @@ class HGECtx:
def reflect_tables(self): def reflect_tables(self):
self.meta.reflect(bind=self.engine) self.meta.reflect(bind=self.engine)
def anyq(self, u, q, h): def anyq(self, u, q, h):
resp = self.http.post( resp = self.http.post(
self.hge_url + u, self.hge_url + u,
@ -117,9 +127,13 @@ class HGECtx:
return resp.status_code, resp.json() return resp.status_code, resp.json()
def v1q(self, q): 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( resp = self.http.post(
self.hge_url + "/v1/query", self.hge_url + "/v1/query",
json=q json=q,
headers=h
) )
return resp.status_code, resp.json() return resp.status_code, resp.json()

View File

@ -0,0 +1,2 @@
[pytest]
norecursedirs = queries webhook

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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: '<foo>bar</foo>'
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: "<foo>bar</foo>"
}
]
) {
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);

View File

@ -1,20 +1,29 @@
description: Insert into order table with a null value - description: Insert into order table with a null value
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
query: query:
query: | query: |
mutation insert_orders{ mutation insert_orders{
insert_orders( insert_orders(
objects: [ objects: [
{ {
placed: "2017-08-19 14:22:11.802755+02", placed: "2017-08-19 14:22:11.802755+02",
shipped: null shipped: null
}
]
) {
returning {
id
} }
] affected_rows
) {
returning {
id
} }
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);

View File

@ -1,24 +1,33 @@
description: Inserts person data via GraphQL mutation - description: Inserts person data via GraphQL mutation
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
query: query:
variables: variables:
value: value:
name: name:
first: john first: john
last: murphy last: murphy
query: | query: |
mutation insert_person($value: jsonb) { mutation insert_person($value: jsonb) {
insert_person( insert_person(
objects: [ objects: [
{ {
details: $value 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);

View File

@ -1,27 +1,36 @@
description: Inserts persons data via GraphQL mutation - description: Inserts persons data via GraphQL mutation
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
query: query:
variables: variables:
value: value:
- name: - name:
first: thelonious first: thelonious
last: jaha last: jaha
- name: - name:
first: clarke first: clarke
last: griffin last: griffin
query: | query: |
mutation insert_person($value: jsonb) { mutation insert_person($value: jsonb) {
insert_person( insert_person(
objects: [ objects: [
{ {
details: $value 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);

View File

@ -77,3 +77,80 @@ args:
table: article table: article
column: author_id 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';

View File

@ -12,15 +12,33 @@ args:
args: args:
sql: | sql: |
drop table person drop table person
- type: run_sql - type: run_sql
args: args:
sql: | sql: |
drop table orders drop table orders
- type: run_sql - type: run_sql
args: args:
sql: | sql: |
drop table article drop table article
- type: run_sql - type: run_sql
args: args:
sql: | sql: |
drop table author 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

View File

@ -1,36 +1,44 @@
description: Insert area as a polygon - description: Insert area as a polygon
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
response: response:
data: data:
insert_area: insert_area:
returning: returning:
- id: 1 - id: 1
name: Foo name: Foo
area: &area area: &area
coordinates: &coords coordinates: &coords
- -
- [43.75049, 11.03207] - [43.75049, 11.03207]
- [43.80009, 11.03208] - [43.80009, 11.03208]
- [42.60009, 10.13248] - [42.60009, 10.13248]
- [43.75049, 11.03207] - [43.75049, 11.03207]
type: Polygon type: Polygon
crs: &crs crs: &crs
type: name type: name
properties: properties:
name: 'urn:ogc:def:crs:EPSG::4326' name: 'urn:ogc:def:crs:EPSG::4326'
query: query:
variables: variables:
areas: areas:
- name: Foo - name: Foo
area: *area area: *area
query: | query: |
mutation insertArea($areas: [area_insert_input!]!) { mutation insertArea($areas: [area_insert_input!]!) {
insert_area(objects: $areas) { insert_area(objects: $areas) {
returning{ returning{
id id
name name
area area
}
} }
} }
} - description: Delete the inserted area
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from area

View File

@ -1,44 +1,52 @@
description: Insert the path for a curved road - description: Insert the path for a road
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
response: response:
data: data:
insert_compounds: insert_compounds:
returning: 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 - user_id: 1
name: foo name: foo
areas: &areas areas: *areas
type: MultiPolygon query: |
coordinates: mutation insertCompounds($compounds: [compounds_insert_input!]!) {
- - - [43.75049, 11.03207] insert_compounds(objects: $compounds) {
- [43.80009, 11.03208] returning{
- [43.90009, 11.03308] user_id
- [43.75049, 11.03207] name
- - [43.75049, 11.03207] areas
- [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
} }
} }
} - description: Delete the inserted compounds
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from compounds

View File

@ -1,29 +1,37 @@
description: Insert location of drone as geojson point - description: Insert location of drone as geojson point
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
response: response:
data: data:
insert_drone_3d_location: insert_drone_3d_location:
returning: 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 - drone_id: 1
location: &loc location: *loc
coordinates: [43.75049, 11.03207, 1.234] query: |
type: Point mutation insertDroneLoc($location: [drone_3d_location_insert_input!]!) {
crs: insert_drone_3d_location(objects: $location) {
type: name returning{
properties: drone_id
name: 'urn:ogc:def:crs:EPSG::4326' location
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
} }
} }
} - description: Delete the inserted drone location
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from drone_3d_location

View File

@ -1,71 +1,79 @@
description: Insert a geometry collection - description: Insert a geometry collection
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
response: response:
data: data:
insert_geometry_collection: insert_geometry_collection:
returning: returning:
- id: 1 - id: 1
geometries: &geometry geometries: &geometry
type: GeometryCollection type: GeometryCollection
geometries: geometries:
- type: Point - type: Point
coordinates: [43.75049, 11.03207] coordinates: [43.75049, 11.03207]
- type: MultiPoint - type: MultiPoint
coordinates: 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]
- [43.75049, 11.03207] - [43.75049, 11.03207]
- [43.80009, 11.03208]
- type: MultiPolygon - [43.80009, 11.03308]
coordinates:
- - - [43.75049, 11.03207] - type: LineString
- [43.80009, 11.03208] coordinates:
- [43.90009, 11.03308] - [43.75049, 11.03207]
- [43.75049, 11.03207] - [43.80009, 11.03208]
- type: MultiLineString
coordinates:
- - [43.75049, 11.03207] - - [43.75049, 11.03207]
- [41.60009, 21.03208] - [43.80009, 11.03208]
- [41.70009, 21.03308] - - [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] - [43.75049, 11.03207]
- - - [23.75049, 31.03207]
- [23.80009, 31.03208] - type: MultiPolygon
- [23.80009, 31.03308] coordinates:
- [23.75049, 31.03207] - - - [43.75049, 11.03207]
- [43.80009, 11.03208]
crs: &crs - [43.90009, 11.03308]
type: name - [43.75049, 11.03207]
properties: - - [43.75049, 11.03207]
name: 'urn:ogc:def:crs:EPSG::4326' - [41.60009, 21.03208]
query: - [41.70009, 21.03308]
variables: - [43.75049, 11.03207]
geometries: - - - [23.75049, 31.03207]
- geometries: *geometry - [23.80009, 31.03208]
query: | - [23.80009, 31.03308]
mutation insertGeometries($geometries: [geometry_collection_insert_input!]!) { - [23.75049, 31.03207]
insert_geometry_collection(objects: $geometries) {
returning{ crs: &crs
id type: name
geometries 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

View File

@ -1,44 +1,52 @@
description: Insert landmarks - description: Insert landmarks
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
response: response:
data: data:
insert_landmark: insert_landmark:
returning: returning:
- id: 1 - id: 1
name: Baz 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 type: river
location: &loc1 location: *loc1
coordinates: [43.75049, 11.03207] - name: Foo Bar
type: Point
crs: &crs
type: name
properties:
name: 'urn:ogc:def:crs:EPSG::4326'
- id: 2
name: Foo Bar
type: park type: park
location: &loc2 location: *loc2
coordinates: [43.76417, 11.25869] query: |
type: Point mutation insertLandmark($landmarks: [landmark_insert_input!]!) {
crs: *crs insert_landmark(objects: $landmarks) {
query: returning{
variables: id
landmarks: name
- name: Baz location
type: river type
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
} }
} }
} - description: Delete the inserted landmarks
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from landmark

View File

@ -1,33 +1,41 @@
description: Insert a straight road as a geojson LineString - description: Insert a straight road as a geojson LineString
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
response: response:
data: data:
insert_road: insert_road:
returning: returning:
- id: 1 - id: 1
name: Foo name: Foo
path: &path path: &path
coordinates: coordinates:
- [43.75049, 11.03207] - [43.75049, 11.03207]
- [43.80009, 11.03208] - [43.80009, 11.03208]
type: LineString type: LineString
crs: crs:
type: name type: name
properties: properties:
name: 'urn:ogc:def:crs:EPSG::4326' name: 'urn:ogc:def:crs:EPSG::4326'
query: query:
variables: variables:
roads: roads:
- name: Foo - name: Foo
path: *path path: *path
query: | query: |
mutation insertRoad($roads: [road_insert_input!]!) { mutation insertRoad($roads: [road_insert_input!]!) {
insert_road(objects: $roads) { insert_road(objects: $roads) {
returning{ returning{
id id
name name
path path
}
} }
} }
} - description: Delete the inserted road
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from road

View File

@ -1,37 +1,45 @@
description: Insert a route as a geojson MultiLineString - description: Insert a route as a geojson MultiLineString
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
response: response:
data: data:
insert_route: insert_route:
returning: returning:
- id: 1 - id: 1
name: Foo name: Foo
route: &route route: &route
coordinates: coordinates:
- -
- [43.75049, 11.03207] - [43.75049, 11.03207]
- [43.80009, 11.03208] - [43.80009, 11.03208]
- -
- [43.80009, 11.03208] - [43.80009, 11.03208]
- [42.60009, 10.13248] - [42.60009, 10.13248]
type: MultiLineString type: MultiLineString
crs: crs:
type: name type: name
properties: properties:
name: 'urn:ogc:def:crs:EPSG::4326' name: 'urn:ogc:def:crs:EPSG::4326'
query: query:
variables: variables:
routes: routes:
- name: Foo - name: Foo
route: *route route: *route
query: | query: |
mutation insertRoute($routes: [route_insert_input!]!) { mutation insertRoute($routes: [route_insert_input!]!) {
insert_route(objects: $routes) { insert_route(objects: $routes) {
returning{ returning{
id id
name name
route route
}
} }
} }
} - description: Delete the inserted route
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from route

View File

@ -1,31 +1,39 @@
description: Insert the path for a curved road - description: Insert the path for a curved road
url: /v1alpha1/graphql url: /v1alpha1/graphql
status: 200 status: 200
response: response:
data: data:
insert_service_locations: insert_service_locations:
returning: returning:
- id: 1 - id: 1
locations: &locs locations: &locs
coordinates: coordinates:
- [43.75049, 11.03207] - [43.75049, 11.03207]
- [43.80009, 11.03208] - [43.80009, 11.03208]
- [43.80009, 11.03308] - [43.80009, 11.03308]
type: MultiPoint type: MultiPoint
crs: crs:
type: name type: name
properties: properties:
name: 'urn:ogc:def:crs:EPSG::4326' name: 'urn:ogc:def:crs:EPSG::4326'
query: query:
variables: variables:
locations: locations:
- locations: *locs - locations: *locs
query: | query: |
mutation insertServLoc($locations: [service_locations_insert_input!]!) { mutation insertServLoc($locations: [service_locations_insert_input!]!) {
insert_service_locations(objects: $locations) { insert_service_locations(objects: $locations) {
returning{ returning{
id id
locations locations
}
} }
} }
} - description: Delete the inserted service locations
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from service_locations

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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: '<foo>bar</foo>'
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
}
}

View File

@ -1,13 +1,87 @@
type: bulk type: bulk
args: 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 #Author table
- type: run_sql - type: run_sql
args: args:
sql: | sql: |
create table author( create table author(
id serial primary key, id serial primary key,
name text unique name text unique,
"createdAt" timestamptz
); );
- type: track_table - type: track_table
args: args:
@ -51,15 +125,105 @@ args:
table: article table: article
column: author_id 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
, '<foo>bar</foo>' -- c44_xml
)
#Insert values #Insert values
- type: run_sql - type: run_sql
args: args:
sql: | sql: |
insert into author (name) insert into author (name, "createdAt")
values values
('Author 1'), ('Author 1', '2017-09-21T09:39:44Z'),
('Author 2') ('Author 2', '2017-09-21T09:50:44Z')
- type: run_sql - type: run_sql
args: args:
@ -106,3 +270,9 @@ args:
number: '123456789' number: '123456789'
- name: User 2 - name: User 2
number: '123456780' number: '123456780'
#Set timezone
- type: run_sql
args:
sql: |
SET TIME ZONE 'UTC';

View File

@ -1,5 +1,11 @@
type: bulk type: bulk
args: args:
- type: run_sql
args:
sql: |
drop table test_types
#Drop relationship first #Drop relationship first
- type: drop_relationship - type: drop_relationship
args: args:
@ -22,3 +28,13 @@ args:
args: args:
sql: | sql: |
drop table "user" drop table "user"
- type: run_sql
args:
sql: |
drop type complex
- type: run_sql
args:
sql: |
drop type inventory_item

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
description: Count number of authors
url: /v1/query
status: 400
query:
type: random
args:
foo: bar

View File

@ -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
)

View File

@ -0,0 +1,10 @@
type: bulk
args:
- type: run_sql
args:
sql: |
drop table article
- type: run_sql
args:
sql: |
drop table author

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,9 @@
description: Count number of authors
url: /v1/query
status: 200
response:
count: 4
query:
type: count
args:
table: author

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
)

View File

@ -0,0 +1,10 @@
type: bulk
args:
- type: run_sql
args:
sql: |
drop table article
- type: run_sql
args:
sql: |
drop table author

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
)

View File

@ -0,0 +1,13 @@
type: bulk
args:
- type: run_sql
args:
sql: |
drop table article
- type: run_sql
args:
sql: |
drop table author

View File

@ -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

View File

@ -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

View File

@ -4,3 +4,6 @@ pytest
requests requests
pyyaml pyyaml
websocket-client websocket-client
pyjwt >= 1.5.3
jsondiff
cryptography

View File

@ -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

View File

@ -1,40 +1,34 @@
import pytest
import yaml 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): def test_introspection(self, hge_ctx):
with open(self.dir + "/introspection.yaml") as c: with open(self.dir() + "/introspection.yaml") as c:
conf = yaml.load(c) conf = yaml.load(c)
code, resp = hge_ctx.anyq( conf['url'], conf['query'], {}) code, resp = check_query(hge_ctx, conf)
assert code == 200 assert code == 200, resp
hasArticle = False hasArticle = False
hasArticleAuthorFKRel = False hasArticleAuthorFKRel = False
hasArticleAuthorManualRel = False hasArticleAuthorManualRel = False
for t in resp['data']['__schema']['types']: for t in resp['data']['__schema']['types']:
if t['name'] == 'article': if t['name'] == 'article':
hasArticle = True hasArticle = True
for fld in t['fields']: for fld in t['fields']:
if fld['name'] == 'author_obj_rel_manual': if fld['name'] == 'author_obj_rel_manual':
hasArticleAuthorManualRel = True hasArticleAuthorManualRel = True
assert fld['type']['kind'] == 'OBJECT' assert fld['type']['kind'] == 'OBJECT'
elif fld['name'] == 'author_obj_rel_fk': elif fld['name'] == 'author_obj_rel_fk':
hasArticleAuthorFKRel = True hasArticleAuthorFKRel = True
assert fld['type']['kind'] == 'NON_NULL' assert fld['type']['kind'] == 'NON_NULL'
assert hasArticle assert hasArticle
assert hasArticleAuthorFKRel assert hasArticleAuthorFKRel
assert hasArticleAuthorManualRel assert hasArticleAuthorManualRel
def test_introspection_user(self, hge_ctx): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_introspection" return "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

View File

@ -1,372 +1,340 @@
import pytest import pytest
import yaml import yaml
from validate import check_query_f 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/insert/basic" return "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
class TestGraphqlInsertOnConflict(object): class TestGraphqlInsertOnConflict(DefaultTestQueries):
def test_on_conflict_update(self, hge_ctx): 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): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/insert/onconflict" return "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
class TestGraphqlInsertPermission(DefaultTestQueries):
class TestGraphqlInsertPermission(object):
def test_user_role_on_conflict_update(self, hge_ctx): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/insert/permissions" return "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
class TestGraphqlInsertConstraints(object): class TestGraphqlInsertConstraints(DefaultTestQueries):
def test_address_not_null_constraint_err(self, hge_ctx): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/insert/constraints" return "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
class TestGraphqlInsertGeoJson(): class TestGraphqlInsertGeoJson(DefaultTestQueries):
def test_insert_point_landmark(self, hge_ctx): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/insert/geojson" return "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
class TestGraphqlNestedInserts(object): class TestGraphqlNestedInserts(DefaultTestQueries):
def test_author_with_articles(self, hge_ctx): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/insert/nested" return "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
class TestGraphqlInsertViews(object):
class TestGraphqlInsertViews(DefaultTestQueries):
def test_insert_view_author_simple(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/insert/views" return "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
class TestGraphqlUpdateBasic:
class TestGraphqlUpdateBasic(DefaultTestQueries):
def test_set_author_name(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/update/basic" return "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
class TestGraphqlUpdateJsonB: class TestGraphqlUpdateJsonB(DefaultTestQueries):
def test_jsonb_append_object(self, hge_ctx): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/update/jsonb" return "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
class TestGraphqlUpdatePermissions: class TestGraphqlUpdatePermissions(DefaultTestQueries):
def test_user_can_update_unpublished_article(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/update/permissions" return "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
class TestGraphqlDeleteBasic: class TestGraphqlDeleteBasic(DefaultTestQueries):
def test_article_delete(self, hge_ctx): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/delete/basic" return "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
class TestGraphqlDeleteConstraints: class TestGraphqlDeleteConstraints(DefaultTestQueries):
def test_author_delete_foreign_key_violation(self, hge_ctx): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/delete/constraints" return "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
class TestGraphqlDeletePermissions: class TestGraphqlDeletePermissions(DefaultTestQueries):
def test_author_can_delete_his_articles(self, hge_ctx): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/graphql_mutation/delete/permissions" return "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

View File

@ -1,277 +1,255 @@
import pytest
import yaml import yaml
from validate import check_query_f from validate import check_query_f
from super_classes import DefaultTestSelectQueries
class TestGraphQLQueryBasic:
class TestGraphQLQueryBasic(DefaultTestSelectQueries):
def test_select_query_author(self, hge_ctx): 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): 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): 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): 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): 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): 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): 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 test_select_query_non_tracked_table(self, hge_ctx):
def transact(self, request, hge_ctx): check_query_f(hge_ctx, self.dir() + "/select_query_non_tracked_table_err.yaml")
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
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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/graphql_query/aggregations' return '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
class TestGraphQLQueryAggPerm:
class TestGraphQLQueryAggPerm(DefaultTestSelectQueries):
def test_author_agg_articles(self, hge_ctx): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/graphql_query/agg_perm' return '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
class TestGraphQLQueryLimits:
class TestGraphQLQueryLimits(DefaultTestSelectQueries):
def test_limit_1(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/graphql_query/limits' return '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
class TestGraphQLQueryOffsets:
class TestGraphQLQueryOffsets(DefaultTestSelectQueries):
def test_offset_1_limit_2(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/graphql_query/offset' return '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
class TestGraphQLQueryBoolExpBasic: class TestGraphQLQueryBoolExpBasic(DefaultTestSelectQueries):
def test_author_article_where_not_equal(self, hge_ctx): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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 test_article_author_unexpected_operator_in_where_err(self, hge_ctx):
def transact(self, request, hge_ctx): check_query_f(hge_ctx, self.dir() + '/select_author_article_unexpected_operator_in_where_err.yaml')
self.dir = 'queries/graphql_query/boolexp/basic'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml') @classmethod
assert st_code == 200, resp def dir(cls):
yield return 'queries/graphql_query/boolexp/basic'
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
class TestGraphqlQueryPermissions: class TestGraphqlQueryPermissions(DefaultTestSelectQueries):
def test_user_select_unpublished_articles(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/graphql_query/permissions' return '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
class TestGraphQLQueryBoolExpSearch: class TestGraphQLQueryBoolExpSearch(DefaultTestSelectQueries):
def test_city_where_like(self, hge_ctx): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/graphql_query/boolexp/search' return '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
class TestGraphQLQueryBoolExpJsonB:
class TestGraphQLQueryBoolExpJsonB(DefaultTestSelectQueries):
def test_jsonb_contains_article_latest(self, hge_ctx): 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): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/graphql_query/boolexp/jsonb' return '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
class TestGraphQLQueryOrderBy:
class TestGraphQLQueryOrderBy(DefaultTestSelectQueries):
def test_articles_order_by_without_id(self, hge_ctx): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/graphql_query/order_by' return '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

View File

@ -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

View File

@ -9,6 +9,8 @@ import yaml
Refer: https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init Refer: https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init
''' '''
def test_init_without_payload(hge_ctx): 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 = { obj = {
'type': 'connection_init' '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 Refer: https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init
''' '''
def test_init(hge_ctx): 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 = { obj = {
'type': 'connection_init', 'type': 'connection_init',
'payload': {}, 'payload': payload,
} }
hge_ctx.ws.send(json.dumps(obj)) hge_ctx.ws.send(json.dumps(obj))
ev = hge_ctx.get_ws_event(3) ev = hge_ctx.get_ws_event(3)
@ -146,8 +155,16 @@ class TestSubscriptionLiveQueries(object):
''' '''
Create connection using connection_init 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 = { obj = {
'type': 'connection_init' 'type': 'connection_init',
'payload' : payload
} }
hge_ctx.ws.send(json.dumps(obj)) hge_ctx.ws.send(json.dumps(obj))
ev = hge_ctx.get_ws_event(3) ev = hge_ctx.get_ws_event(3)

View File

@ -1,452 +1,459 @@
import pytest
import yaml import yaml
from validate import check_query_f 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): 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): 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): 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): def test_select_col_not_present(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_article_col_not_present_err.yaml') 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') 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/select/basic" return "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
class TestV1SelectLimits: class TestV1SelectLimits(DefaultTestSelectQueries):
def test_limit_1(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/v1/select/limits' return '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
class TestV1SelectOffset: class TestV1SelectOffset(DefaultTestSelectQueries):
def test_offset_1_limit_2(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/v1/select/offset' return '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
class TestV1SelectBoolExpBasic: class TestV1SelectBoolExpBasic(DefaultTestSelectQueries):
def test_author_article_where_not_equal(self, hge_ctx): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/v1/select/boolexp/basic' return '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
class TestV1SelectBoolExpSearch: class TestV1SelectBoolExpSearch(DefaultTestSelectQueries):
def test_city_where_like(self, hge_ctx): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/v1/select/boolexp/search' return '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
class TestV1SelectPermissions: class TestV1SelectPermissions(DefaultTestSelectQueries):
def test_user_select_unpublished_articles(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = 'queries/v1/select/permissions' return '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
class TestV1InsertBasic: class TestV1InsertBasic(DefaultTestQueries):
def test_insert_author(self, hge_ctx): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/insert/basic" return "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
class TestV1InsertOnConflict: class TestV1InsertOnConflict(DefaultTestQueries):
def test_author_on_conflict_update(self, hge_ctx): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/insert/onconflict" return "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
class TestV1InsertPermissions(object): class TestV1InsertPermissions(DefaultTestQueries):
def test_user_role_on_conflict_update(self, hge_ctx): 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): 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): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/insert/permissions" return "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
class TestV1UpdateBasic: class TestV1UpdateBasic(DefaultTestQueries):
def test_set_author_name(self, hge_ctx): 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): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/update/basic" return "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
class TestV1UpdatePermissions: class TestV1UpdatePermissions(DefaultTestQueries):
def test_user_can_update_unpublished_article(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/update/permissions" return "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
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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/delete" return "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
class TestMetadata:
class TestMetadata(DefaultTestQueries):
def test_reload_metadata(self, hge_ctx): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/metadata" return "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
class TestRunSQL: class TestRunSQL(DefaultTestQueries):
def test_select_query(self, hge_ctx): 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): 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): def test_sql_timezone_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/sql_set_timezone_error.yaml') check_query_f(hge_ctx, self.dir() + '/sql_set_timezone_error.yaml')
def test_sql_query_as_user_error(self, hge_ctx): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/run_sql" return "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
class TestRelationships: class TestRelationships(DefaultTestQueries):
def test_object_relationship_foreign_key(self, hge_ctx): 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): 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): 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): 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): 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): 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): 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): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/relationships" return "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
class TestTrackTables: class TestTrackTables(DefaultTestQueries):
def test_track_untrack_table(self, hge_ctx): 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): 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): 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) @classmethod
def transact(self, request, hge_ctx): def dir(cls):
self.dir = "queries/v1/track_table" return "queries/v1/track_table"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield class TestCreatePermission(DefaultTestQueries):
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp 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"

View File

@ -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'

View File

@ -1,6 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import yaml import yaml
import json
import os
import base64
import jsondiff
import jwt
def check_keys(keys, obj): def check_keys(keys, obj):
for k in keys: 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['op'] == operation, ev
assert ev['data'] == exp_ev_data, 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={} headers={}
if 'headers' in conf: if 'headers' in conf:
headers = conf['headers'] headers = conf['headers']
#Test without access key
code, resp = hge_ctx.anyq( conf['url'], conf['query'], headers) 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 assert code == conf['status'], resp
if 'response' in conf: if 'response' in conf:
print ('response\n', yaml.dump(resp)) assert json_ordered(resp) == json_ordered(conf['response']) , yaml.dump( {
print ('expected\n', yaml.dump(conf['response'])) 'response' : resp,
assert json_ordered(resp) == json_ordered(conf['response']) '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: with open(f) as c:
conf = yaml.load(c) conf = yaml.safe_load(c)
if isinstance(conf, list): if isinstance(conf, list):
for sconf in conf: for sconf in conf:
check_query( hge_ctx, sconf) check_query( hge_ctx, sconf)
else: 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): def json_ordered(obj):
if isinstance(obj, dict): if isinstance(obj, dict):

View File

@ -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]))

View File

@ -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

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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
}
}
}