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
- *wait_for_postgres
- run:
name: run the server in background
working_directory: ./server
name: Run Python tests
environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://gql_test:@localhost:5432/gql_test
EVENT_WEBHOOK_HEADER: MyEnvValue
background: true
HASURA_GRAPHQL_DATABASE_URL: 'postgres://gql_test:@localhost:5432/gql_test'
GRAPHQL_ENGINE: '/build/_server_output/graphql-engine'
command: |
/build/_server_output/graphql-engine serve
apt-get update
apt install --yes jq
OUTPUT_FOLDER=/build/_server_test_output/$PG_VERSION .circleci/test-server.sh
- run:
name: create test output dir
command: |
mkdir -p /build/_server_test_output/$PG_VERSION
- *wait_for_hge
- run:
name: pytest the server
working_directory: ./server/tests-py
environment:
DATABASE_URL: postgres://gql_test:@localhost:5432/gql_test
HGE_URL: http://localhost:8080
command: |
pip3 install -r requirements.txt
pytest -vv --hge-url="$HGE_URL" --pg-url="$DATABASE_URL"
- run:
name: stop the server and generate coverage report
name: Generate coverage report
working_directory: ./server
command: |
kill -2 $(ps -e | grep graphql-engine | awk '{print $1}')
stack --system-ghc hpc report graphql-engine.tix --destdir /build/_server_test_output/$PG_VERSION
stack --system-ghc hpc report /build/_server_test_output/$PG_VERSION/graphql-engine.tix --destdir /build/_server_test_output/$PG_VERSION
- store_artifacts:
path: /build/_server_test_output
destination: server_test

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
*.DS_Store
.tern-project
test-server-output

View File

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

View File

@ -9,17 +9,45 @@ def pytest_addoption(parser):
parser.addoption(
"--pg-url", metavar="PG_URL", help="url for connecting to Postgres directly", required=True
)
parser.addoption(
"--hge-key", metavar="HGE_KEY", help="access key for graphql-engine", required=False
)
parser.addoption(
"--hge-webhook", metavar="HGE_WEBHOOK", help="url for graphql-engine's access control webhook", required=False
)
parser.addoption(
"--test-webhook-insecure", action="store_true",
help="Run Test cases for insecure https webhook"
)
parser.addoption(
"--hge-jwt-key-file", metavar="HGE_JWT_KEY_FILE", help="File containting the private key used to encode jwt tokens using RS512 algorithm", required=False
)
@pytest.fixture(scope='session')
def hge_ctx(request):
print ("create hge_ctx")
hge_url = request.config.getoption('--hge-url')
pg_url = request.config.getoption('--pg-url')
hge_key = request.config.getoption('--hge-key')
hge_webhook = request.config.getoption('--hge-webhook')
webhook_insecure = request.config.getoption('--test-webhook-insecure')
hge_jwt_key_file = request.config.getoption('--hge-jwt-key-file')
try:
hge_ctx = HGECtx(hge_url=hge_url, pg_url=pg_url)
hge_ctx = HGECtx(hge_url=hge_url, pg_url=pg_url, hge_key=hge_key, hge_webhook=hge_webhook, hge_jwt_key_file=hge_jwt_key_file, webhook_insecure = webhook_insecure )
except HGECtxError as e:
pytest.exit(str(e))
yield hge_ctx # provide the fixture value
print("teardown hge_ctx")
hge_ctx.teardown()
time.sleep(2)
@pytest.fixture(scope='class')
def setup_ctrl(request, hge_ctx):
"""
This fixure is used to store the state of test setup in some test classes.
Used primarily when teardown is skipped in some test cases in the class where the test is not expected to change the database state.
"""
setup_ctrl = { "setupDone" : False }
yield setup_ctrl
hge_ctx.may_skip_test_teardown = False
request.cls().do_teardown(setup_ctrl, hge_ctx)

View File

@ -53,7 +53,7 @@ class WebhookServer(http.server.HTTPServer):
self.socket.bind(self.server_address)
class HGECtx:
def __init__(self, hge_url, pg_url):
def __init__(self, hge_url, pg_url, hge_key, hge_webhook, hge_jwt_key_file, webhook_insecure):
server_address = ('0.0.0.0', 5592)
self.resp_queue = queue.Queue(maxsize=1)
@ -69,6 +69,15 @@ class HGECtx:
self.http = requests.Session()
self.hge_url = hge_url
self.hge_key = hge_key
self.hge_webhook = hge_webhook
if hge_jwt_key_file is None:
self.hge_jwt_key = None
else:
with open(hge_jwt_key_file) as f:
self.hge_jwt_key = f.read()
self.webhook_insecure = webhook_insecure
self.may_skip_test_teardown = False
self.ws_url = urlparse(hge_url)
self.ws_url = self.ws_url._replace(scheme='ws')
@ -78,7 +87,7 @@ class HGECtx:
self.wst.daemon = True
self.wst.start()
result = subprocess.run(['../../scripts/get-version.sh'], shell=True, stdout=subprocess.PIPE, check=True)
result = subprocess.run(['../../scripts/get-version.sh'], shell=False, stdout=subprocess.PIPE, check=True)
self.version = result.stdout.decode('utf-8').strip()
try:
st_code, resp = self.v1q_f('queries/clear_db.yaml')
@ -108,6 +117,7 @@ class HGECtx:
def reflect_tables(self):
self.meta.reflect(bind=self.engine)
def anyq(self, u, q, h):
resp = self.http.post(
self.hge_url + u,
@ -117,9 +127,13 @@ class HGECtx:
return resp.status_code, resp.json()
def v1q(self, q):
h = dict()
if self.hge_key is not None:
h['X-Hasura-Access-Key'] = self.hge_key
resp = self.http.post(
self.hge_url + "/v1/query",
json=q
json=q,
headers=h
)
return resp.status_code, resp.json()

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
url: /v1alpha1/graphql
status: 200
query:
query: |
mutation insert_orders{
insert_orders(
objects: [
{
placed: "2017-08-19 14:22:11.802755+02",
shipped: null
- description: Insert into order table with a null value
url: /v1alpha1/graphql
status: 200
query:
query: |
mutation insert_orders{
insert_orders(
objects: [
{
placed: "2017-08-19 14:22:11.802755+02",
shipped: null
}
]
) {
returning {
id
}
]
) {
returning {
id
affected_rows
}
affected_rows
}
}
- description: Delete the inserted orders
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from orders;
SELECT setval('orders_id_seq', 1, FALSE);

View File

@ -1,24 +1,33 @@
description: Inserts person data via GraphQL mutation
url: /v1alpha1/graphql
status: 200
query:
variables:
value:
name:
first: john
last: murphy
query: |
mutation insert_person($value: jsonb) {
insert_person(
objects: [
{
details: $value
- description: Inserts person data via GraphQL mutation
url: /v1alpha1/graphql
status: 200
query:
variables:
value:
name:
first: john
last: murphy
query: |
mutation insert_person($value: jsonb) {
insert_person(
objects: [
{
details: $value
}
]
) {
returning {
id
details
}
]
) {
returning {
id
details
}
}
}
- description: Delete the inserted persons
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from person;
SELECT setval('person_id_seq', 1, FALSE);

View File

@ -1,27 +1,36 @@
description: Inserts persons data via GraphQL mutation
url: /v1alpha1/graphql
status: 200
query:
variables:
value:
- name:
first: thelonious
last: jaha
- name:
first: clarke
last: griffin
query: |
mutation insert_person($value: jsonb) {
insert_person(
objects: [
{
details: $value
- description: Inserts persons data via GraphQL mutation
url: /v1alpha1/graphql
status: 200
query:
variables:
value:
- name:
first: thelonious
last: jaha
- name:
first: clarke
last: griffin
query: |
mutation insert_person($value: jsonb) {
insert_person(
objects: [
{
details: $value
}
]
) {
returning {
id
details
}
]
) {
returning {
id
details
}
}
}
- description: Delete the inserted persons
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from person;
SELECT setval('person_id_seq', 1, FALSE);

View File

@ -77,3 +77,80 @@ args:
table: article
column: author_id
- type: run_sql
args:
sql: |
CREATE TYPE complex AS (
r double precision,
i double precision
);
- type: run_sql
args:
sql: |
CREATE TYPE inventory_item AS (
name text,
supplier_id integer,
price numeric
);
#Test table with different types
- type: run_sql
args:
sql: |
create table test_types (
c1_smallint smallint,
c2_integer integer,
c3_bigint bigint,
c4_decimal decimal (5, 2),
c5_numeric numeric (4, 3),
c6_real real,
c7_double_precision double precision,
c8_smallserial smallserial primary key,
c9_serial serial,
c10_bigserial bigserial,
c11_varchar_3 varchar(3),
c12_char_4 char(4),
c13_text text,
c14_timestamp timestamp,
c15_timestamptz timestamptz,
c16_date date,
c17_time time,
c18_time_with_zone time with time zone,
c19_interval interval,
c20_boolean boolean,
c21_point point,
c22_line line,
c23_lseg lseg,
c24_box box,
c25_closed_path path,
c26_open_path path,
c27_polygon polygon,
c28_circle circle,
c29_cidr cidr,
c30_inet inet,
c31_macaddr macaddr,
c32_json json,
c33_jsonb jsonb,
c34_text_array text[],
c35_integer_2d_array integer[][],
c36_uuid uuid,
c37_composite_type_complex complex,
c38_composite_type_inventory inventory_item,
c39_range_integer int4range,
c40_range_bigint int8range,
c41_range_numeric numrange,
c42_range_timestamp tsrange,
c43_range_timestamptz tstzrange,
c44_xml xml
);
- type: track_table
args:
schema: public
name: test_types
#Set timezone
- type: run_sql
args:
sql: |
SET TIME ZONE 'UTC';

View File

@ -12,15 +12,33 @@ args:
args:
sql: |
drop table person
- type: run_sql
args:
sql: |
drop table orders
- type: run_sql
args:
sql: |
drop table article
- type: run_sql
args:
sql: |
drop table author
- type: run_sql
args:
sql: |
drop table test_types
- type: run_sql
args:
sql: |
drop type complex
- type: run_sql
args:
sql: |
drop type inventory_item

View File

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

View File

@ -1,44 +1,52 @@
description: Insert the path for a curved road
url: /v1alpha1/graphql
status: 200
response:
data:
insert_compounds:
returning:
- description: Insert the path for a road
url: /v1alpha1/graphql
status: 200
response:
data:
insert_compounds:
returning:
- user_id: 1
name: foo
areas: &areas
type: MultiPolygon
coordinates:
- - - [43.75049, 11.03207]
- [43.80009, 11.03208]
- [43.90009, 11.03308]
- [43.75049, 11.03207]
- - [43.75049, 11.03207]
- [41.60009, 21.03208]
- [41.70009, 21.03308]
- [43.75049, 11.03207]
- - - [23.75049, 31.03207]
- [23.80009, 31.03208]
- [23.80009, 31.03308]
- [23.75049, 31.03207]
crs: &crs
type: name
properties:
name: 'urn:ogc:def:crs:EPSG::4326'
query:
variables:
compounds:
- user_id: 1
name: foo
areas: &areas
type: MultiPolygon
coordinates:
- - - [43.75049, 11.03207]
- [43.80009, 11.03208]
- [43.90009, 11.03308]
- [43.75049, 11.03207]
- - [43.75049, 11.03207]
- [41.60009, 21.03208]
- [41.70009, 21.03308]
- [43.75049, 11.03207]
- - - [23.75049, 31.03207]
- [23.80009, 31.03208]
- [23.80009, 31.03308]
- [23.75049, 31.03207]
crs: &crs
type: name
properties:
name: 'urn:ogc:def:crs:EPSG::4326'
query:
variables:
compounds:
- user_id: 1
name: foo
areas: *areas
query: |
mutation insertCompounds($compounds: [compounds_insert_input!]!) {
insert_compounds(objects: $compounds) {
returning{
user_id
name
areas
areas: *areas
query: |
mutation insertCompounds($compounds: [compounds_insert_input!]!) {
insert_compounds(objects: $compounds) {
returning{
user_id
name
areas
}
}
}
}
- description: Delete the inserted compounds
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
delete from compounds

View File

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

View File

@ -1,71 +1,79 @@
description: Insert a geometry collection
url: /v1alpha1/graphql
status: 200
response:
data:
insert_geometry_collection:
returning:
- id: 1
geometries: &geometry
type: GeometryCollection
geometries:
- type: Point
coordinates: [43.75049, 11.03207]
- type: MultiPoint
coordinates:
- [43.75049, 11.03207]
- [43.80009, 11.03208]
- [43.80009, 11.03308]
- type: LineString
coordinates:
- [43.75049, 11.03207]
- [43.80009, 11.03208]
- type: MultiLineString
coordinates:
- - [43.75049, 11.03207]
- [43.80009, 11.03208]
- - [43.80009, 11.03208]
- [42.60009, 10.13248]
- type: Polygon
coordinates: &coords
- - [43.75049, 11.03207]
- [43.80009, 11.03208]
- [42.60009, 10.13248]
- description: Insert a geometry collection
url: /v1alpha1/graphql
status: 200
response:
data:
insert_geometry_collection:
returning:
- id: 1
geometries: &geometry
type: GeometryCollection
geometries:
- type: Point
coordinates: [43.75049, 11.03207]
- type: MultiPoint
coordinates:
- [43.75049, 11.03207]
- type: MultiPolygon
coordinates:
- - - [43.75049, 11.03207]
- [43.80009, 11.03208]
- [43.90009, 11.03308]
- [43.75049, 11.03207]
- [43.80009, 11.03208]
- [43.80009, 11.03308]
- type: LineString
coordinates:
- [43.75049, 11.03207]
- [43.80009, 11.03208]
- type: MultiLineString
coordinates:
- - [43.75049, 11.03207]
- [41.60009, 21.03208]
- [41.70009, 21.03308]
- [43.80009, 11.03208]
- - [43.80009, 11.03208]
- [42.60009, 10.13248]
- type: Polygon
coordinates: &coords
- - [43.75049, 11.03207]
- [43.80009, 11.03208]
- [42.60009, 10.13248]
- [43.75049, 11.03207]
- - - [23.75049, 31.03207]
- [23.80009, 31.03208]
- [23.80009, 31.03308]
- [23.75049, 31.03207]
crs: &crs
type: name
properties:
name: 'urn:ogc:def:crs:EPSG::4326'
query:
variables:
geometries:
- geometries: *geometry
query: |
mutation insertGeometries($geometries: [geometry_collection_insert_input!]!) {
insert_geometry_collection(objects: $geometries) {
returning{
id
geometries
- type: MultiPolygon
coordinates:
- - - [43.75049, 11.03207]
- [43.80009, 11.03208]
- [43.90009, 11.03308]
- [43.75049, 11.03207]
- - [43.75049, 11.03207]
- [41.60009, 21.03208]
- [41.70009, 21.03308]
- [43.75049, 11.03207]
- - - [23.75049, 31.03207]
- [23.80009, 31.03208]
- [23.80009, 31.03308]
- [23.75049, 31.03207]
crs: &crs
type: name
properties:
name: 'urn:ogc:def:crs:EPSG::4326'
query:
variables:
geometries:
- geometries: *geometry
query: |
mutation insertGeometries($geometries: [geometry_collection_insert_input!]!) {
insert_geometry_collection(objects: $geometries) {
returning{
id
geometries
}
}
}
}
- description: Delete the inserted geometry collection
url: /v1/query
status: 200
query:
type: run_sql
args:
sql: |
DELETE from geometry_collection

View File

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

View File

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

View File

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

View File

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

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
args:
- type: run_sql
args:
sql: |
CREATE TYPE complex AS (
r double precision,
i double precision
);
- type: run_sql
args:
sql: |
CREATE TYPE inventory_item AS (
name text,
supplier_id integer,
price numeric
);
#Test table with different types
- type: run_sql
args:
sql: |
create table test_types (
c1_smallint smallint,
c2_integer integer,
c3_bigint bigint,
c4_decimal decimal (5, 2),
c5_numeric numeric (4, 3),
c6_real real,
c7_double_precision double precision,
c8_smallserial smallserial,
c9_serial serial,
c10_bigserial bigserial,
c11_varchar_3 varchar(3),
c12_char_4 char(4),
c13_text text,
c14_timestamp timestamp,
c15_timestamptz timestamptz,
c16_date date,
c17_time time,
c18_time_with_zone time with time zone,
c19_interval interval,
c20_boolean boolean,
c21_point point,
c22_line line,
c23_lseg lseg,
c24_box box,
c25_closed_path path,
c26_open_path path,
c27_polygon polygon,
c28_circle circle,
c29_cidr cidr,
c30_inet inet,
c31_macaddr macaddr,
c32_json json,
c33_jsonb jsonb,
c34_text_array text[],
c35_integer_2d_array integer[][],
c36_uuid uuid,
c37_composite_type_complex complex,
c38_composite_type_inventory inventory_item,
c39_range_integer int4range,
c40_range_bigint int8range,
c41_range_numeric numrange,
c42_range_timestamp tsrange,
c43_range_timestamptz tstzrange,
c44_xml xml
);
- type: track_table
args:
schema: public
name: test_types
#Author table
- type: run_sql
args:
sql: |
create table author(
id serial primary key,
name text unique
id serial primary key,
name text unique,
"createdAt" timestamptz
);
- type: track_table
args:
@ -51,15 +125,105 @@ args:
table: article
column: author_id
#Insert values
- type: run_sql
args:
sql: |
insert into test_types
( c1_smallint
, c2_integer
, c3_bigint
, c4_decimal
, c5_numeric
, c6_real
, c7_double_precision
, c11_varchar_3
, c12_char_4
, c13_text
, c14_timestamp
, c15_timestamptz
, c16_date
, c17_time
, c18_time_with_zone
, c19_interval
, c20_boolean
, c21_point
, c22_line
, c23_lseg
, c24_box
, c25_closed_path
, c26_open_path
, c27_polygon
, c28_circle
, c29_cidr
, c30_inet
, c31_macaddr
, c32_json
, c33_jsonb
, c34_text_array
, c35_integer_2d_array
, c36_uuid
, c37_composite_type_complex
, c38_composite_type_inventory
, c39_range_integer
, c40_range_bigint
, c41_range_numeric
, c42_range_timestamp
, c43_range_timestamptz
, c44_xml
)
values
( 32767 -- c1_smallint
, 2147483647 -- c2_integer
, 9223372036854775807 -- c3_bigint
, 123.45 -- c4_decimal
, 1.234 -- c5_numeric
, 0.00390625 -- c6_real
, 16.0001220703125 -- c7_double_precision
, 'abc' -- c11_varchar_3
, 'baaz' -- c12_char_4
, 'foo bar baz' -- c13_text
, '2004-10-19T10:23:54' -- c14_timestamp
, '2015-10-17T14:42:43+00:00' -- c15_timestamptz
, '2014-09-14' -- c16_date
, '11:09:23' -- c17_time
, '15:22:23+00' -- c18_time_with_zone
, '01:03:02' -- c19_interval
, true -- c20_boolean
, '(1,2)' -- c21_point
, '{2,3,-1}' -- c22_line
, '[(4,2),(3,1)]' -- c23_line
, '((31,12),(14,11))' -- c24_box
, '((0,0),(0,3),(1,0))' -- c25_closed_path
, '[(0,0),(0,-1),(-3,0)]' -- c26_open_path
, '((0,0),(0,6),(2,0))' -- c27_polygon
, '<(-2,-3),3>' -- c28_circle
, '192.168.100.128/25' -- c29_cidr
, '198.24.10.0' -- c30_inet
, '08:00:2b:01:02:03' -- c31_macaddr
, '{ "a" : "b" }' -- c32_json
, '{ "c" : "d" }' -- c33_jsonb
, '{"a","b","c"}' -- c34_text_array
, '{{4,5,6},{7,8,9}}' -- c35_integer_2d_array
, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11' -- c36_uuid
, '(1.23,-3.456)' -- c37_composite_type_complexa
, '("fuzzy dice",42,1.99)' -- c38_composite_type_inventory
, '[123,456)' -- c39_range_integer
, '[1147483647, 2147483647)' -- c40_range_bigint
, '[1.23, 4.56]' -- c41_range_numeric
, '[2010-01-01 14:30:00, 2010-01-01 15:30:02)' -- c42_range_timestamp
, '(2011-02-05T12:03:00+00:00, 2012-03-04T16:40:04+00:00]' -- c43_range_timestamptz
, '<foo>bar</foo>' -- c44_xml
)
#Insert values
- type: run_sql
args:
sql: |
insert into author (name)
insert into author (name, "createdAt")
values
('Author 1'),
('Author 2')
('Author 1', '2017-09-21T09:39:44Z'),
('Author 2', '2017-09-21T09:50:44Z')
- type: run_sql
args:
@ -106,3 +270,9 @@ args:
number: '123456789'
- name: User 2
number: '123456780'
#Set timezone
- type: run_sql
args:
sql: |
SET TIME ZONE 'UTC';

View File

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

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

View File

@ -1,372 +1,340 @@
import pytest
import yaml
from validate import check_query_f
from super_classes import DefaultTestQueries
class TestGraphQLInsert(object):
class TestGraphQLInsert(DefaultTestQueries):
def test_inserts_author_article(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_article.yaml")
check_query_f(hge_ctx, self.dir() + "/author_article.yaml")
hge_ctx.may_skip_test_teardown = True
def test_inserts_various_postgres_types(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/insert_various_postgres_types.yaml")
hge_ctx.may_skip_test_teardown = True
@pytest.mark.xfail(reason="Refer https://github.com/hasura/graphql-engine/issues/348")
def test_insert_into_array_col_with_array_input(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/insert_into_array_col_with_array_input.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_using_variable(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_jsonb_variable.yaml")
check_query_f(hge_ctx, self.dir() + "/person_jsonb_variable.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_using_array_variable(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_jsonb_variable_array.yaml")
check_query_f(hge_ctx, self.dir() + "/person_jsonb_variable_array.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_null_col_value(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/order_col_shipped_null.yaml")
check_query_f(hge_ctx, self.dir() + "/order_col_shipped_null.yaml")
hge_ctx.may_skip_test_teardown = True
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/insert/basic"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/insert/basic"
class TestGraphqlInsertOnConflict(object):
class TestGraphqlInsertOnConflict(DefaultTestQueries):
def test_on_conflict_update(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_on_conflict_update.yaml")
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_update.yaml")
def test_on_conflict_no_action_specified(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_on_conflict_no_action_specified.yaml")
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_no_action_specified.yaml")
def test_on_conflict_ignore(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_on_conflict_ignore_constraint.yaml")
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_ignore_constraint.yaml")
hge_ctx.may_skip_test_teardown = True
def test_on_conflict_update_empty_cols(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_on_conflict_empty_update_columns.yaml")
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_empty_update_columns.yaml")
hge_ctx.may_skip_test_teardown = True
def test_err_missing_article_constraint(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_on_conflict_error_missing_article_constraint.yaml")
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_error_missing_article_constraint.yaml")
def test_err_unexpected_action(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_unexpected_on_conflict_action.yaml")
check_query_f(hge_ctx, self.dir() + "/article_unexpected_on_conflict_action.yaml")
def test_err_unexpected_constraint(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_unexpected_on_conflict_constraint_error.yaml")
check_query_f(hge_ctx, self.dir() + "/article_unexpected_on_conflict_constraint_error.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/insert/onconflict"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/insert/onconflict"
class TestGraphqlInsertPermission(object):
class TestGraphqlInsertPermission(DefaultTestQueries):
def test_user_role_on_conflict_update(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_on_conflict_user_role.yaml")
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_user_role.yaml")
def test_user_role_on_conflict_constraint_on_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_constraint_on_user_role_error.yaml")
@pytest.mark.xfail(reason="Refer https://github.com/hasura/graphql-engine/issues/563")
def test_user_role_on_conflict_ignore(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_on_conflict_ignore_user_role.yaml")
check_query_f(hge_ctx, self.dir() + "/author_on_conflict_ignore_user_role.yaml")
hge_ctx.may_skip_test_teardown = True
def test_user_on_conflict_err_no_action_specified(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_article_on_conflict_err_no_action_specified.yaml")
check_query_f(hge_ctx, self.dir() + "/user_article_on_conflict_err_no_action_specified.yaml")
def test_user_err_missing_article_constraint(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_article_on_conflict_error_missing_article_constraint.yaml")
check_query_f(hge_ctx, self.dir() + "/user_article_on_conflict_error_missing_article_constraint.yaml")
def test_user_err_unexpected_action(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_article_error_unexpected_on_conflict_action.yaml")
check_query_f(hge_ctx, self.dir() + "/user_article_error_unexpected_on_conflict_action.yaml")
def test_user_err_unexpected_constraint(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_article_unexpected_on_conflict_constraint_error.yaml")
check_query_f(hge_ctx, self.dir() + "/user_article_unexpected_on_conflict_constraint_error.yaml")
def test_role_has_no_permissions_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/address_permission_error.yaml")
check_query_f(hge_ctx, self.dir() + "/address_permission_error.yaml")
def test_author_user_role_insert_check_perm_success(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_perm_success.yaml")
check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_perm_success.yaml")
def test_user_role_insert_check_is_registered_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_is_registered_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_is_registered_fail.yaml")
def test_user_role_insert_check_user_id_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_user_id_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_user_id_fail.yaml")
def test_student_role_insert_check_bio_success(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_student_role_insert_check_bio_success.yaml")
check_query_f(hge_ctx, self.dir() + "/author_student_role_insert_check_bio_success.yaml")
def test_student_role_insert_check_bio_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_student_role_insert_check_bio_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/author_student_role_insert_check_bio_fail.yaml")
def test_company_user_role_insert(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/company_user_role.yaml")
check_query_f(hge_ctx, self.dir() + "/company_user_role.yaml")
def test_company_user_role_insert_on_conflict(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/company_user_role_on_conflict.yaml")
check_query_f(hge_ctx, self.dir() + "/company_user_role_on_conflict.yaml")
def test_resident_user_role_insert(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/resident_user.yaml")
check_query_f(hge_ctx, self.dir() + "/resident_user.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/insert/permissions"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/insert/permissions"
class TestGraphqlInsertConstraints(object):
class TestGraphqlInsertConstraints(DefaultTestQueries):
def test_address_not_null_constraint_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/address_not_null_constraint_error.yaml")
check_query_f(hge_ctx, self.dir() + "/address_not_null_constraint_error.yaml")
def test_insert_unique_constraint_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_unique_constraint_error.yaml")
check_query_f(hge_ctx, self.dir() + "/author_unique_constraint_error.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/insert/constraints"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/insert/constraints"
class TestGraphqlInsertGeoJson():
class TestGraphqlInsertGeoJson(DefaultTestQueries):
def test_insert_point_landmark(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_landmark.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_landmark.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_3d_point_drone_loc(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_drone_3d_location.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_drone_3d_location.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_landmark_single_position_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_landmark_single_position_err.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_landmark_single_position_err.yaml")
def test_insert_line_string_road(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_road.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_road.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_road_single_point_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_road_single_point_err.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_road_single_point_err.yaml")
def test_insert_multi_point_service_locations(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_service_locations.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_service_locations.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_multi_line_string_route(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_route.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_route.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_polygon(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_area.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_area.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_linear_ring_less_than_4_points_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_area_less_than_4_points_err.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_area_less_than_4_points_err.yaml")
def test_insert_linear_ring_last_point_not_equal_to_first_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_linear_ring_last_point_not_equal_to_first_err.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_linear_ring_last_point_not_equal_to_first_err.yaml")
def test_insert_multi_polygon_compounds(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_compounds.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_compounds.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_geometry_collection(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_geometry_collection.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_geometry_collection.yaml")
hge_ctx.may_skip_test_teardown = True
def test_insert_unexpected_geometry_type_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_geometry_unexpected_type_err.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_geometry_unexpected_type_err.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/insert/geojson"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/insert/geojson"
class TestGraphqlNestedInserts(object):
class TestGraphqlNestedInserts(DefaultTestQueries):
def test_author_with_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_with_articles.yaml")
check_query_f(hge_ctx, self.dir() + "/author_with_articles.yaml")
def test_author_with_articles_author_id_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_with_articles_author_id_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/author_with_articles_author_id_fail.yaml")
def test_articles_with_author(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/articles_with_author.yaml")
check_query_f(hge_ctx, self.dir() + "/articles_with_author.yaml")
def test_articles_with_author_author_id_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/articles_with_author_author_id_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/articles_with_author_author_id_fail.yaml")
def test_author_upsert_articles_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_upsert_articles_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/author_upsert_articles_fail.yaml")
def test_articles_author_upsert_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/articles_author_upsert_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/articles_author_upsert_fail.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/insert/nested"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/insert/nested"
class TestGraphqlInsertViews(object):
class TestGraphqlInsertViews(DefaultTestQueries):
def test_insert_view_author_simple(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_view_author_simple.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_view_author_simple.yaml")
def test_insert_view_author_complex_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/insert_view_author_complex_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/insert_view_author_complex_fail.yaml")
def test_nested_insert_article_author_simple_view(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/nested_insert_article_author_simple_view.yaml")
check_query_f(hge_ctx, self.dir() + "/nested_insert_article_author_simple_view.yaml")
def test_nested_insert_article_author_complex_view_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/nested_insert_article_author_complex_view_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/nested_insert_article_author_complex_view_fail.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/insert/views"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/insert/views"
class TestGraphqlUpdateBasic:
class TestGraphqlUpdateBasic(DefaultTestQueries):
def test_set_author_name(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_set_name.yaml")
check_query_f(hge_ctx, self.dir() + "/author_set_name.yaml")
def test_set_person_details(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_set_details.yaml")
check_query_f(hge_ctx, self.dir() + "/person_set_details.yaml")
def test_person_id_inc(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_inc.yaml")
check_query_f(hge_ctx, self.dir() + "/person_inc.yaml")
def test_no_operator_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_error_no_operator.yaml")
check_query_f(hge_ctx, self.dir() + "/person_error_no_operator.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/update/basic"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/update/basic"
class TestGraphqlUpdateJsonB:
class TestGraphqlUpdateJsonB(DefaultTestQueries):
def test_jsonb_append_object(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_append_object.yaml")
check_query_f(hge_ctx, self.dir() + "/person_append_object.yaml")
def test_jsonb_append_array(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_append_array.yaml")
check_query_f(hge_ctx, self.dir() + "/person_append_array.yaml")
def test_jsonb_prepend_array(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_prepend_array.yaml")
check_query_f(hge_ctx, self.dir() + "/person_prepend_array.yaml")
def test_jsonb_delete_at_path(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_delete_at_path.yaml")
check_query_f(hge_ctx, self.dir() + "/person_delete_at_path.yaml")
def test_jsonb_delete_array_element(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_delete_array_element.yaml")
check_query_f(hge_ctx, self.dir() + "/person_delete_array_element.yaml")
def test_jsonb_delete_key(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_delete_key.yaml")
check_query_f(hge_ctx, self.dir() + "/person_delete_key.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/update/jsonb"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/update/jsonb"
class TestGraphqlUpdatePermissions:
class TestGraphqlUpdatePermissions(DefaultTestQueries):
def test_user_can_update_unpublished_article(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_can_update_unpublished_article.yaml")
check_query_f(hge_ctx, self.dir() + "/user_can_update_unpublished_article.yaml")
def test_user_cannot_update_published_version_col(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_cannot_update_published_article_version.yaml")
check_query_f(hge_ctx, self.dir() + "/user_cannot_update_published_article_version.yaml")
hge_ctx.may_skip_test_teardown = True
def test_user_cannot_update_another_users_article(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_cannot_update_another_users_article.yaml")
check_query_f(hge_ctx, self.dir() + "/user_cannot_update_another_users_article.yaml")
hge_ctx.may_skip_test_teardown = True
def test_user_cannot_update_id_col(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_cannot_update_id_col_article.yaml")
check_query_f(hge_ctx, self.dir() + "/user_cannot_update_id_col_article.yaml")
hge_ctx.may_skip_test_teardown = True
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/update/permissions"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/update/permissions"
class TestGraphqlDeleteBasic:
class TestGraphqlDeleteBasic(DefaultTestQueries):
def test_article_delete(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article.yaml")
check_query_f(hge_ctx, self.dir() + "/article.yaml")
def test_article_delete_returning(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_returning.yaml")
check_query_f(hge_ctx, self.dir() + "/article_returning.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/delete/basic"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/delete/basic"
class TestGraphqlDeleteConstraints:
class TestGraphqlDeleteConstraints(DefaultTestQueries):
def test_author_delete_foreign_key_violation(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_foreign_key_violation.yaml")
check_query_f(hge_ctx, self.dir() + "/author_foreign_key_violation.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/delete/constraints"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/delete/constraints"
class TestGraphqlDeletePermissions:
class TestGraphqlDeletePermissions(DefaultTestQueries):
def test_author_can_delete_his_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_can_delete_his_articles.yaml")
check_query_f(hge_ctx, self.dir() + "/author_can_delete_his_articles.yaml")
def test_author_cannot_delete_other_users_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_cannot_delete_other_users_articles.yaml")
check_query_f(hge_ctx, self.dir() + "/author_cannot_delete_other_users_articles.yaml")
hge_ctx.may_skip_test_teardown = True
def test_resident_delete_without_select_perm_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/resident_delete_without_select_perm_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/resident_delete_without_select_perm_fail.yaml")
hge_ctx.may_skip_test_teardown = True
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/graphql_mutation/delete/permissions"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/graphql_mutation/delete/permissions"

View File

@ -1,277 +1,255 @@
import pytest
import yaml
from validate import check_query_f
from super_classes import DefaultTestSelectQueries
class TestGraphQLQueryBasic:
class TestGraphQLQueryBasic(DefaultTestSelectQueries):
def test_select_query_author(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_author.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_author.yaml')
def test_select_various_postgres_types(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/select_query_test_types.yaml')
def test_select_query_author_quoted_col(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/select_query_author_col_quoted.yaml')
def test_select_query_author_pk(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_author_by_pkey.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_author_by_pkey.yaml')
def test_select_query_where(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_author_where.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_author_where.yaml')
def test_nested_select_query_article_author(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/nested_select_query_article_author.yaml')
check_query_f(hge_ctx, self.dir() + '/nested_select_query_article_author.yaml')
def test_nested_select_query_deep(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/nested_select_query_deep.yaml')
check_query_f(hge_ctx, self.dir() + '/nested_select_query_deep.yaml')
def test_nested_select_query_where(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/nested_select_where_query_author_article.yaml')
check_query_f(hge_ctx, self.dir() + '/nested_select_where_query_author_article.yaml')
def test_nested_select_query_where_on_relationship(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/nested_select_query_article_author_where_on_relationship.yaml')
def test_select_query_user(self, hge_ctx):
check_query_f(hge_ctx, "queries/graphql_query/basic/select_query_user.yaml")
check_query_f(hge_ctx, self.dir() + "/select_query_user.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/graphql_query/basic'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
def test_select_query_non_tracked_table(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/select_query_non_tracked_table_err.yaml")
class TestGraphQLQueryAgg:
def test_select_query_col_not_present_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/select_query_author_col_not_present_err.yaml")
@classmethod
def dir(cls):
return 'queries/graphql_query/basic'
class TestGraphQLQueryAgg(DefaultTestSelectQueries):
def test_article_agg_count_sum_avg_max_min_with_aliases(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/article_agg_count_sum_avg_max_min_with_aliases.yaml')
check_query_f(hge_ctx, self.dir() + '/article_agg_count_sum_avg_max_min_with_aliases.yaml')
def test_article_agg_where(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/article_agg_where.yaml')
check_query_f(hge_ctx, self.dir() + '/article_agg_where.yaml')
def test_author_agg_with_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/author_agg_with_articles.yaml')
check_query_f(hge_ctx, self.dir() + '/author_agg_with_articles.yaml')
def test_author_agg_with_articles_where(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/author_agg_with_articles_where.yaml')
check_query_f(hge_ctx, self.dir() + '/author_agg_with_articles_where.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/graphql_query/aggregations'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/graphql_query/aggregations'
class TestGraphQLQueryAggPerm:
class TestGraphQLQueryAggPerm(DefaultTestSelectQueries):
def test_author_agg_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/author_agg_articles.yaml')
check_query_f(hge_ctx, self.dir() + '/author_agg_articles.yaml')
def test_article_agg_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/article_agg_fail.yaml')
check_query_f(hge_ctx, self.dir() + '/article_agg_fail.yaml')
def test_author_articles_agg_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/author_articles_agg_fail.yaml')
check_query_f(hge_ctx, self.dir() + '/author_articles_agg_fail.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/graphql_query/agg_perm'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/graphql_query/agg_perm'
class TestGraphQLQueryLimits:
class TestGraphQLQueryLimits(DefaultTestSelectQueries):
def test_limit_1(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_limit_1.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_limit_1.yaml')
def test_limit_2(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_limit_2.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_limit_2.yaml')
def test_err_str_limit_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_string_limit_error.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_string_limit_error.yaml')
def test_err_neg_limit_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_neg_limit_error.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_neg_limit_error.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/graphql_query/limits'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/graphql_query/limits'
class TestGraphQLQueryOffsets:
class TestGraphQLQueryOffsets(DefaultTestSelectQueries):
def test_offset_1_limit_2(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_offset_1_limit_2.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_offset_1_limit_2.yaml')
def test_offset_2_limit_1(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_offset_2_limit_1.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_offset_2_limit_1.yaml')
def test_int_as_string_offset(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_string_offset.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_string_offset.yaml')
def test_err_neg_offset_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_neg_offset_error.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_neg_offset_error.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/graphql_query/offset'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/graphql_query/offset'
class TestGraphQLQueryBoolExpBasic:
class TestGraphQLQueryBoolExpBasic(DefaultTestSelectQueries):
def test_author_article_where_not_equal(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_neq.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_neq.yaml')
def test_author_article_operator_ne_not_found_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/select_author_article_operator_ne_not_found_err.yaml')
def test_author_article_where_greater_than(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_gt.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_gt.yaml')
def test_author_article_where_greater_than_or_equal(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_gte.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_gte.yaml')
def test_author_article_where_less_than(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_lt.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_lt.yaml')
def test_author_article_where_less_than_or_equal(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_lte.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_lte.yaml')
def test_author_article_where_in(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_in.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_in.yaml')
def test_author_article_where_nin(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_nin.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_nin.yaml')
def test_order_delivered_at_is_null(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_order_delivered_at_is_null.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_order_delivered_at_is_null.yaml')
def test_order_delivered_at_is_not_null(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_order_delivered_at_is_not_null.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_order_delivered_at_is_not_null.yaml')
def test_author_article_where_not_less_than(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_not_lt.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_not_lt.yaml')
def test_article_author_is_published_and_registered(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_article_author_is_published_and_registered.yaml')
check_query_f(hge_ctx, self.dir() + '/select_article_author_is_published_and_registered.yaml')
def test_article_author_not_published_nor_registered(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_article_author_not_published_or_not_registered.yaml')
check_query_f(hge_ctx, self.dir() + '/select_article_author_not_published_or_not_registered.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/graphql_query/boolexp/basic'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
def test_article_author_unexpected_operator_in_where_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/select_author_article_unexpected_operator_in_where_err.yaml')
@classmethod
def dir(cls):
return 'queries/graphql_query/boolexp/basic'
class TestGraphqlQueryPermissions:
class TestGraphqlQueryPermissions(DefaultTestSelectQueries):
def test_user_select_unpublished_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/user_select_query_unpublished_articles.yaml')
check_query_f(hge_ctx, self.dir() + '/user_select_query_unpublished_articles.yaml')
def test_user_only_other_users_published_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/user_can_query_other_users_published_articles.yaml')
check_query_f(hge_ctx, self.dir() + '/user_can_query_other_users_published_articles.yaml')
def test_anonymous_only_published_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/anonymous_can_only_get_published_articles.yaml')
check_query_f(hge_ctx, self.dir() + '/anonymous_can_only_get_published_articles.yaml')
def test_user_cannot_access_remarks_col(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/user_cannot_access_remarks_col.yaml')
check_query_f(hge_ctx, self.dir() + '/user_cannot_access_remarks_col.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/graphql_query/permissions'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/graphql_query/permissions'
class TestGraphQLQueryBoolExpSearch:
class TestGraphQLQueryBoolExpSearch(DefaultTestSelectQueries):
def test_city_where_like(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_like.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_like.yaml')
def test_city_where_not_like(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_nlike.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_nlike.yaml')
def test_city_where_ilike(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_ilike.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_ilike.yaml')
def test_city_where_not_ilike(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_nilike.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_nilike.yaml')
def test_city_where_similar(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_similar.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_similar.yaml')
def test_city_where_not_similar(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_not_similar.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_not_similar.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/graphql_query/boolexp/search'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/graphql_query/boolexp/search'
class TestGraphQLQueryBoolExpJsonB:
class TestGraphQLQueryBoolExpJsonB(DefaultTestSelectQueries):
def test_jsonb_contains_article_latest(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_article_author_jsonb_contains_latest.yaml')
check_query_f(hge_ctx, self.dir() + '/select_article_author_jsonb_contains_latest.yaml')
def test_jsonb_contains_article_beststeller(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_jsonb_contains_bestseller.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_jsonb_contains_bestseller.yaml')
def test_jsonb_contained_in_latest(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_article_author_jsonb_contained_in_latest.yaml')
check_query_f(hge_ctx, self.dir() + '/select_article_author_jsonb_contained_in_latest.yaml')
def test_jsonb_contained_in_bestseller_latest(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_article_author_jsonb_contained_in_bestseller_latest.yaml')
check_query_f(hge_ctx, self.dir() + '/select_article_author_jsonb_contained_in_bestseller_latest.yaml')
def test_jsonb_has_key_sim_type(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_product_jsonb_has_key_sim_type.yaml')
check_query_f(hge_ctx, self.dir() + '/select_product_jsonb_has_key_sim_type.yaml')
def test_jsonb_has_keys_any_os_operating_system(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_product_jsonb_has_keys_any_os_operating_system.yaml')
check_query_f(hge_ctx, self.dir() + '/select_product_jsonb_has_keys_any_os_operating_system.yaml')
def test_jsonb_has_keys_all_touchscreen_ram(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_product_jsonb_has_keys_all_ram_touchscreen.yaml')
check_query_f(hge_ctx, self.dir() + '/select_product_jsonb_has_keys_all_ram_touchscreen.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/graphql_query/boolexp/jsonb'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/graphql_query/boolexp/jsonb'
class TestGraphQLQueryOrderBy:
class TestGraphQLQueryOrderBy(DefaultTestSelectQueries):
def test_articles_order_by_without_id(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/articles_order_by_without_id.yaml')
check_query_f(hge_ctx, self.dir() + '/articles_order_by_without_id.yaml')
def test_articles_order_by_rel_author_id(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/articles_order_by_rel_author_id.yaml')
check_query_f(hge_ctx, self.dir() + '/articles_order_by_rel_author_id.yaml')
def test_articles_order_by_rel_author_rel_contact_phone(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/articles_order_by_rel_author_rel_contact_phone.yaml')
check_query_f(hge_ctx, self.dir() + '/articles_order_by_rel_author_rel_contact_phone.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/graphql_query/order_by'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/graphql_query/order_by'

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
'''
def test_init_without_payload(hge_ctx):
if hge_ctx.hge_key is not None:
pytest.skip("Payload is needed when access key is set")
obj = {
'type': 'connection_init'
}
@ -20,9 +22,16 @@ def test_init_without_payload(hge_ctx):
Refer: https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init
'''
def test_init(hge_ctx):
payload = {}
if hge_ctx.hge_key is not None:
payload = {
'headers' : {
'X-Hasura-Access-Key': hge_ctx.hge_key
}
}
obj = {
'type': 'connection_init',
'payload': {},
'payload': payload,
}
hge_ctx.ws.send(json.dumps(obj))
ev = hge_ctx.get_ws_event(3)
@ -146,8 +155,16 @@ class TestSubscriptionLiveQueries(object):
'''
Create connection using connection_init
'''
payload = {}
if hge_ctx.hge_key is not None:
payload = {
'headers' : {
'X-Hasura-Access-Key': hge_ctx.hge_key
}
}
obj = {
'type': 'connection_init'
'type': 'connection_init',
'payload' : payload
}
hge_ctx.ws.send(json.dumps(obj))
ev = hge_ctx.get_ws_event(3)

View File

@ -1,452 +1,459 @@
import pytest
import yaml
from validate import check_query_f
from super_classes import DefaultTestSelectQueries, DefaultTestQueries
class TestV1SelectBasic:
class TestV1General(DefaultTestQueries):
def test_query_string_input_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/query_string_input_err.yaml')
def test_query_unknown_type_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/query_unknown_type_err.yaml')
def test_query_args_as_string_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/query_args_as_string_err.yaml')
@classmethod
def dir(cls):
return "queries/v1/basic"
class TestV1SelectBasic(DefaultTestSelectQueries):
def test_select_query_author(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_article.yaml')
check_query_f(hge_ctx, self.dir() + '/select_article.yaml')
def test_nested_select_article_author(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/nested_select_query_article_author.yaml')
check_query_f(hge_ctx, self.dir() + '/nested_select_query_article_author.yaml')
def test_nested_select_article_author_alias_for_relationship(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/nested_select_query_article_author_rel_alias.yaml')
def test_select_author_where(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_author_where.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_author_where.yaml')
def test_select_table_not_present(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/select_article_table_not_present_err.yaml')
def test_select_col_not_present(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_article_col_not_present_err.yaml')
def test_nested_select_query_where(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/nested_select_where_query_author_article.yaml')
check_query_f(hge_ctx, self.dir() + '/select_article_col_not_present_err.yaml')
def test_select_nested_where_query(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/nested_select_where_query_author_article.yaml')
def test_select_query_user(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_user.yaml')
check_query_f(hge_ctx, self.dir() + '/select_user.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/select/basic"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/select/basic"
class TestV1SelectLimits:
class TestV1SelectLimits(DefaultTestSelectQueries):
def test_limit_1(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_limit_1.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_limit_1.yaml')
def test_limit_2(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_limit_2.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_limit_2.yaml')
def test_err_str_limit_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_string_limit_error.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_string_limit_error.yaml')
def test_err_neg_limit_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_neg_limit_error.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_neg_limit_error.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/v1/select/limits'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/v1/select/limits'
class TestV1SelectOffset:
class TestV1SelectOffset(DefaultTestSelectQueries):
def test_offset_1_limit_2(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_offset_1_limit_2.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_offset_1_limit_2.yaml')
def test_offset_2_limit_1(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_offset_2_limit_1.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_offset_2_limit_1.yaml')
def test_int_as_string_offset_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_int_as_string_offset_error.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_int_as_string_offset_error.yaml')
def test_err_neg_offset_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_article_neg_offset_error.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_article_neg_offset_error.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/v1/select/offset'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/v1/select/offset'
class TestV1SelectBoolExpBasic:
class TestV1SelectBoolExpBasic(DefaultTestSelectQueries):
def test_author_article_where_not_equal(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_neq.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_neq.yaml')
def test_author_article_where_greater_than(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_gt.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_gt.yaml')
def test_author_article_where_greater_than_or_equal(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_gte.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_gte.yaml')
def test_author_article_where_less_than(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_lt.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_lt.yaml')
def test_author_article_where_less_than_or_equal(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_lte.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_lte.yaml')
def test_author_article_where_in(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_in.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_in.yaml')
def test_author_article_where_nin(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_nin.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_nin.yaml')
def test_order_delivered_at_is_null(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_order_delivered_at_is_null.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_order_delivered_at_is_null.yaml')
def test_order_delivered_at_is_not_null(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_query_order_delivered_at_is_not_null.yaml')
check_query_f(hge_ctx, self.dir() + '/select_query_order_delivered_at_is_not_null.yaml')
def test_author_article_where_not_less_than(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_author_article_where_not_lt.yaml')
check_query_f(hge_ctx, self.dir() + '/select_author_article_where_not_lt.yaml')
def test_article_author_is_published_and_registered(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_article_author_is_published_and_registered.yaml')
check_query_f(hge_ctx, self.dir() + '/select_article_author_is_published_and_registered.yaml')
def test_article_author_not_published_nor_registered(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_article_author_not_published_or_not_registered.yaml')
check_query_f(hge_ctx, self.dir() + '/select_article_author_not_published_or_not_registered.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/v1/select/boolexp/basic'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/v1/select/boolexp/basic'
class TestV1SelectBoolExpSearch:
class TestV1SelectBoolExpSearch(DefaultTestSelectQueries):
def test_city_where_like(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_like.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_like.yaml')
def test_city_where_not_like(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_nlike.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_nlike.yaml')
def test_city_where_ilike(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_ilike.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_ilike.yaml')
def test_city_where_not_ilike(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_nilike.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_nilike.yaml')
def test_city_where_similar(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_similar.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_similar.yaml')
def test_city_where_not_similar(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/select_city_where_not_similar.yaml')
check_query_f(hge_ctx, self.dir() + '/select_city_where_not_similar.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/v1/select/boolexp/search'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/v1/select/boolexp/search'
class TestV1SelectPermissions:
class TestV1SelectPermissions(DefaultTestSelectQueries):
def test_user_select_unpublished_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/user_select_query_unpublished_articles.yaml')
check_query_f(hge_ctx, self.dir() + '/user_select_query_unpublished_articles.yaml')
def test_user_only_other_users_published_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/user_can_query_other_users_published_articles.yaml')
check_query_f(hge_ctx, self.dir() + '/user_can_query_other_users_published_articles.yaml')
def test_anonymous_only_published_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/anonymous_can_only_get_published_articles.yaml')
check_query_f(hge_ctx, self.dir() + '/anonymous_can_only_get_published_articles.yaml')
def test_user_cannot_access_remarks_col(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/user_cannot_access_remarks_col.yaml')
check_query_f(hge_ctx, self.dir() + '/user_cannot_access_remarks_col.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = 'queries/v1/select/permissions'
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return 'queries/v1/select/permissions'
class TestV1InsertBasic:
class TestV1InsertBasic(DefaultTestQueries):
def test_insert_author(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/insert_author.yaml')
check_query_f(hge_ctx, self.dir() + '/insert_author.yaml')
def test_insert_author_col_not_present_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/insert_author_col_not_present_err.yaml')
check_query_f(hge_ctx, self.dir() + '/insert_author_col_not_present_err.yaml')
def test_insert_null_col_value(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/order_col_shipped_null.yaml")
check_query_f(hge_ctx, self.dir() + "/order_col_shipped_null.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/insert/basic"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/insert/basic"
class TestV1InsertOnConflict:
class TestV1InsertOnConflict(DefaultTestQueries):
def test_author_on_conflict_update(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/upsert_author.yaml')
check_query_f(hge_ctx, self.dir() + '/upsert_author.yaml')
def test_on_conflict_no_action_specified(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_on_conflict_no_action_specified.yaml")
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_no_action_specified.yaml")
def test_on_conflict_ignore(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_on_conflict_ignore_constraint.yaml")
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_ignore_constraint.yaml")
hge_ctx.may_skip_test_teardown = True
def test_err_missing_article_constraint(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_on_conflict_error_missing_article_constraint.yaml")
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_error_missing_article_constraint.yaml")
def test_err_unexpected_action(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_unexpected_on_conflict_action.yaml")
check_query_f(hge_ctx, self.dir() + "/article_unexpected_on_conflict_action.yaml")
def test_err_unexpected_constraint(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_unexpected_on_conflict_constraint_error.yaml")
check_query_f(hge_ctx, self.dir() + "/article_unexpected_on_conflict_constraint_error.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/insert/onconflict"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/insert/onconflict"
class TestV1InsertPermissions(object):
class TestV1InsertPermissions(DefaultTestQueries):
def test_user_role_on_conflict_update(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/article_on_conflict_user_role.yaml")
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_user_role.yaml")
@pytest.mark.xfail(reason="Refer https://github.com/hasura/graphql-engine/issues/563")
def test_user_role_on_conflict_ignore(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_on_conflict_ignore_user_role.yaml")
check_query_f(hge_ctx, self.dir() + "/author_on_conflict_ignore_user_role.yaml")
hge_ctx.may_skip_test_teardown = True
def test_role_has_no_permissions_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/address_permission_error.yaml")
check_query_f(hge_ctx, self.dir() + "/address_permission_error.yaml")
def test_author_user_role_insert_check_perm_success(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_perm_success.yaml")
check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_perm_success.yaml")
def test_user_role_insert_check_is_registered_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_is_registered_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_is_registered_fail.yaml")
def test_user_role_insert_check_user_id_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_user_role_insert_check_user_id_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_user_id_fail.yaml")
def test_student_role_insert_check_bio_success(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_student_role_insert_check_bio_success.yaml")
check_query_f(hge_ctx, self.dir() + "/author_student_role_insert_check_bio_success.yaml")
def test_student_role_insert_check_bio_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_student_role_insert_check_bio_fail.yaml")
check_query_f(hge_ctx, self.dir() + "/author_student_role_insert_check_bio_fail.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/insert/permissions"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/insert/permissions"
class TestV1UpdateBasic:
class TestV1UpdateBasic(DefaultTestQueries):
def test_set_author_name(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/author_set_name.yaml")
check_query_f(hge_ctx, self.dir() + "/author_set_name.yaml")
def test_set_person_details(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_set_details.yaml")
check_query_f(hge_ctx, self.dir() + "/person_set_details.yaml")
def test_person_id_inc(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_inc.yaml")
check_query_f(hge_ctx, self.dir() + "/person_inc.yaml")
def test_product_mul_price(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/product_mul_price.yaml")
check_query_f(hge_ctx, self.dir() + "/product_mul_price.yaml")
def test_product_set_default_price(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/product_set_default_price.yaml")
check_query_f(hge_ctx, self.dir() + "/product_set_default_price.yaml")
def test_no_operator_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_error_no_operator.yaml")
check_query_f(hge_ctx, self.dir() + "/person_error_no_operator.yaml")
def test_no_where_clause_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/person_error_no_where_clause.yaml")
check_query_f(hge_ctx, self.dir() + "/person_error_no_where_clause.yaml")
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/update/basic"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/update/basic"
class TestV1UpdatePermissions:
class TestV1UpdatePermissions(DefaultTestQueries):
def test_user_can_update_unpublished_article(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_can_update_unpublished_article.yaml")
check_query_f(hge_ctx, self.dir() + "/user_can_update_unpublished_article.yaml")
def test_user_cannot_update_published_version_col(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_cannot_update_published_article_version.yaml")
check_query_f(hge_ctx, self.dir() + "/user_cannot_update_published_article_version.yaml")
hge_ctx.may_skip_test_teardown = True
def test_user_cannot_update_another_users_article(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_cannot_update_another_users_article.yaml")
check_query_f(hge_ctx, self.dir() + "/user_cannot_update_another_users_article.yaml")
hge_ctx.may_skip_test_teardown = True
def test_user_cannot_update_id_col(self, hge_ctx):
check_query_f(hge_ctx, self.dir + "/user_cannot_update_id_col_article.yaml")
check_query_f(hge_ctx, self.dir() + "/user_cannot_update_id_col_article.yaml")
hge_ctx.may_skip_test_teardown = True
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/update/permissions"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/update/permissions"
class TestV1Delete:
class TestV1CountBasic(DefaultTestSelectQueries):
def test_count_authors(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/count_authors.yaml")
def test_count_published_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/count_published_articles.yaml")
def test_count_unpublished_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/count_unpublished_articles.yaml")
def test_count_distinct_authors_with_published_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/count_distinct_authors_with_published_articles.yaml")
def test_count_articles_registered_authors(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/count_articles_with_registered_authors.yaml")
def test_count_articles_non_registered_authors(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/count_articles_with_non_registered_authors.yaml")
def test_count_distinct_col_not_present_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/count_distinct_col_not_present_err.yaml")
def test_count_distinct_authors_with_unpublished_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/count_distinct_authors_with_unpublished_articles.yaml")
@classmethod
def dir(cls):
return "queries/v1/count/basic"
class TestV1CountPermissions(DefaultTestSelectQueries):
def test_count_user_has_no_select_permission_err(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/count_user_has_no_select_perm_error.yaml")
def test_count_other_users_unpublished_articles(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/count_users_unpublished_articles.yaml")
@classmethod
def dir(cls):
return "queries/v1/count/permissions"
class TestV1Delete(DefaultTestQueries):
def test_delete_author(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/delete_article.yaml')
check_query_f(hge_ctx, self.dir() + '/delete_article.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/delete"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/delete"
class TestMetadata:
class TestMetadata(DefaultTestQueries):
def test_reload_metadata(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/reload_metadata.yaml')
check_query_f(hge_ctx, self.dir() + '/reload_metadata.yaml')
def test_export_metadata(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/export_metadata.yaml')
check_query_f(hge_ctx, self.dir() + '/export_metadata.yaml')
def test_clear_metadata(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/clear_metadata.yaml')
check_query_f(hge_ctx, self.dir() + '/clear_metadata.yaml')
def test_dump_internal_state(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/dump_internal_state.yaml')
check_query_f(hge_ctx, self.dir() + '/dump_internal_state.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/metadata"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/metadata"
class TestRunSQL:
class TestRunSQL(DefaultTestQueries):
def test_select_query(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/sql_select_query.yaml')
check_query_f(hge_ctx, self.dir() + '/sql_select_query.yaml')
hge_ctx.may_skip_test_teardown = True
def test_set_timezone(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/sql_set_timezone.yaml')
check_query_f(hge_ctx, self.dir() + '/sql_set_timezone.yaml')
hge_ctx.may_skip_test_teardown = True
def test_sql_timezone__error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/sql_set_timezone_error.yaml')
def test_sql_timezone_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/sql_set_timezone_error.yaml')
def test_sql_query_as_user_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/sql_query_as_user_error.yaml')
check_query_f(hge_ctx, self.dir() + '/sql_query_as_user_error.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/run_sql"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/run_sql"
class TestRelationships:
class TestRelationships(DefaultTestQueries):
def test_object_relationship_foreign_key(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/object_relationship_foreign_key.yaml')
check_query_f(hge_ctx, self.dir() + '/object_relationship_foreign_key.yaml')
def test_create_object_relationship_as_not_admin_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/create_object_relationship_as_not_admin_error.yaml')
check_query_f(hge_ctx, self.dir() + '/create_object_relationship_as_not_admin_error.yaml')
def test_object_relationship_col_not_foreign_key_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/object_relationship_col_not_foreign_key_error.yaml')
check_query_f(hge_ctx, self.dir() + '/object_relationship_col_not_foreign_key_error.yaml')
def test_object_relationship_foreign_key_non_public_schema(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/object_relationship_non_public_schema_foreign_key.yaml')
check_query_f(hge_ctx, self.dir() + '/object_relationship_non_public_schema_foreign_key.yaml')
def test_object_relationship_manual(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/object_relationship_manual.yaml')
check_query_f(hge_ctx, self.dir() + '/object_relationship_manual.yaml')
def test_array_relationship_foreign_key(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/array_relationship_foreign_key.yaml')
check_query_f(hge_ctx, self.dir() + '/array_relationship_foreign_key.yaml')
def test_create_array_relationship_as_not_admin_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/create_array_relationship_as_not_admin_error.yaml')
check_query_f(hge_ctx, self.dir() + '/create_array_relationship_as_not_admin_error.yaml')
def test_array_relationship_col_not_foreign_key_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/array_relationship_col_not_foreign_key_error.yaml')
check_query_f(hge_ctx, self.dir() + '/array_relationship_col_not_foreign_key_error.yaml')
def test_array_relationship_foreign_key_non_public_schema(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/array_relationship_non_public_schema_foreign_key.yaml')
check_query_f(hge_ctx, self.dir() + '/array_relationship_non_public_schema_foreign_key.yaml')
def test_array_relationship_manual(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/array_relationship_manual.yaml')
check_query_f(hge_ctx, self.dir() + '/array_relationship_manual.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/relationships"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/relationships"
class TestTrackTables:
class TestTrackTables(DefaultTestQueries):
def test_track_untrack_table(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/track_untrack_table.yaml')
check_query_f(hge_ctx, self.dir() + '/track_untrack_table.yaml')
hge_ctx.may_skip_test_teardown = True
def test_track_untrack_table_non_public_schema(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/track_untrack_table_non_public_schema.yaml')
check_query_f(hge_ctx, self.dir() + '/track_untrack_table_non_public_schema.yaml')
hge_ctx.may_skip_test_teardown = True
def test_track_untrack_table_as_not_admin_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/track_untrack_table_as_not_admin_error.yaml')
check_query_f(hge_ctx, self.dir() + '/track_untrack_table_as_not_admin_error.yaml')
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
self.dir = "queries/v1/track_table"
st_code, resp = hge_ctx.v1q_f(self.dir + '/setup.yaml')
assert st_code == 200, resp
yield
st_code, resp = hge_ctx.v1q_f(self.dir + '/teardown.yaml')
assert st_code == 200, resp
@classmethod
def dir(cls):
return "queries/v1/track_table"
class TestCreatePermission(DefaultTestQueries):
def test_create_permission_admin_role_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/create_article_permission_role_admin_error.yaml')
def test_create_permission_user_role_error(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/create_article_permission_role_user.yaml')
@classmethod
def dir(cls):
return "queries/v1/permissions"

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
import yaml
import json
import os
import base64
import jsondiff
import jwt
def check_keys(keys, obj):
for k in keys:
@ -40,25 +45,102 @@ def check_event(hge_ctx, trig_name, table, operation, exp_ev_data, headers, webh
assert ev['op'] == operation, ev
assert ev['data'] == exp_ev_data, ev
def check_query(hge_ctx, conf):
def test_forbidden_when_access_key_reqd(hge_ctx, conf):
headers={}
if 'headers' in conf:
headers = conf['headers']
#Test without access key
code, resp = hge_ctx.anyq( conf['url'], conf['query'], headers)
assert code == 401, "\n" + yaml.dump( {
"expected" : "Should be access denied as access key is not provided",
"actual" : {
"code" : code,
"response" : resp
}
} )
#Test with random access key
headers['X-Hasura-Access-Key'] = base64.b64encode(os.urandom(30))
code, resp = hge_ctx.anyq( conf['url'], conf['query'], headers)
assert code == 401, "\n" + yaml.dump( {
"expected" : "Should be access denied as an incorrect access key is provided",
"actual" : {
"code" : code,
"response" : resp
}
} )
def test_forbidden_webhook(hge_ctx, conf):
h = { 'Authorization' : 'Bearer ' + base64.b64encode(base64.b64encode(os.urandom(30))).decode('utf-8') }
code, resp = hge_ctx.anyq( conf['url'], conf['query'], h )
assert code == 401, "\n" + yaml.dump( {
"expected" : "Should be access denied as it is denied from webhook",
"actual" : {
"code" : code,
"response" : resp
}
} )
def check_query(hge_ctx, conf, add_auth=True):
headers={}
if 'headers' in conf:
headers = conf['headers']
if add_auth:
if hge_ctx.hge_jwt_key is not None and len(headers) > 0 and 'X-Hasura-Role' in headers:
hClaims=dict()
hClaims['X-Hasura-Allowed-Roles']=[headers['X-Hasura-Role']]
hClaims['X-Hasura-Default-Role']=headers['X-Hasura-Role']
for key in headers:
if key != 'X-Hasura-Role':
hClaims[key]=headers[key]
claim={
"sub": "foo",
"name": "bar",
"https://hasura.io/jwt/claims": hClaims
}
headers['Authorization'] = 'Bearer ' + jwt.encode(claim, hge_ctx.hge_jwt_key, algorithm='RS512').decode('UTF-8')
if hge_ctx.hge_webhook is not None and len(headers) > 0:
if not hge_ctx.webhook_insecure:
test_forbidden_webhook(hge_ctx, conf)
headers['X-Hasura-Auth-Mode'] = 'webhook'
headers_new = dict()
headers_new['Authorization'] = 'Bearer ' + base64.b64encode(json.dumps(headers).encode('utf-8')).decode('utf-8')
headers = headers_new
elif (hge_ctx.hge_webhook is not None or hge_ctx.hge_jwt_key is not None) and hge_ctx.hge_key is not None and len(headers) == 0:
headers['X-Hasura-Access-Key'] = hge_ctx.hge_key
elif hge_ctx.hge_key is not None and hge_ctx.hge_webhook is None and hge_ctx.hge_jwt_key is None:
test_forbidden_when_access_key_reqd(hge_ctx, conf)
headers['X-Hasura-Access-Key'] = hge_ctx.hge_key
code, resp = hge_ctx.anyq( conf['url'], conf['query'], headers)
print (headers)
assert code == conf['status'], resp
if 'response' in conf:
print ('response\n', yaml.dump(resp))
print ('expected\n', yaml.dump(conf['response']))
assert json_ordered(resp) == json_ordered(conf['response'])
assert json_ordered(resp) == json_ordered(conf['response']) , yaml.dump( {
'response' : resp,
'expected' : conf['response'],
'diff': jsondiff.diff(conf['response'], resp)
} )
return code, resp
def check_query_f(hge_ctx, f):
def check_query_f(hge_ctx, f, add_auth=True):
hge_ctx.may_skip_test_teardown = False
with open(f) as c:
conf = yaml.load(c)
conf = yaml.safe_load(c)
if isinstance(conf, list):
for sconf in conf:
check_query( hge_ctx, sconf)
else:
check_query( hge_ctx, conf )
if conf['status'] != 200:
hge_ctx.may_skip_test_teardown = True
check_query( hge_ctx, conf, add_auth )
def json_ordered(obj):
if isinstance(obj, dict):

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