From 97d845638290d2e5197fb9c0d55ac29ed3fdf7e1 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Wed, 30 Dec 2020 12:41:33 +0100 Subject: [PATCH] cov: Add io tests for basic cli commands and invalid config options Full code coverage for Config.hs except expected overlays. --- test/io-tests/configs/expected/types.config | 25 ++++ test/io-tests/configs/invalid.yaml | 4 + test/io-tests/configs/types.config | 16 +++ test/io-tests/fixtures.yaml | 142 ++++++++++++++++++++ test/io-tests/test_io.py | 76 +++++++++-- 5 files changed, 251 insertions(+), 12 deletions(-) create mode 100644 test/io-tests/configs/expected/types.config create mode 100644 test/io-tests/configs/invalid.yaml create mode 100644 test/io-tests/configs/types.config diff --git a/test/io-tests/configs/expected/types.config b/test/io-tests/configs/expected/types.config new file mode 100644 index 000000000..6d6ff1b4c --- /dev/null +++ b/test/io-tests/configs/expected/types.config @@ -0,0 +1,25 @@ +db-anon-role = "required" +db-channel = "pgrst" +db-channel-enabled = false +db-extra-search-path = "public" +db-max-rows = "" +db-pool = 10 +db-pool-timeout = 10 +db-pre-request = "" +db-prepared-statements = true +db-root-spec = "" +db-schemas = "required" +db-tx-end = "commit" +db-uri = "required" +jwt-aud = "" +jwt-role-claim-key = ".\"role\"" +jwt-secret = "" +jwt-secret-is-base64 = false +log-level = "error" +openapi-server-proxy-uri = "" +raw-media-types = "" +server-host = "!4" +server-port = 3000 +server-unix-socket = "" +server-unix-socket-mode = "660" +app.settings.test = "Bool False" diff --git a/test/io-tests/configs/invalid.yaml b/test/io-tests/configs/invalid.yaml new file mode 100644 index 000000000..0f916b29a --- /dev/null +++ b/test/io-tests/configs/invalid.yaml @@ -0,0 +1,4 @@ +yaml: + is: + not: + supported: diff --git a/test/io-tests/configs/types.config b/test/io-tests/configs/types.config new file mode 100644 index 000000000..e4e75124e --- /dev/null +++ b/test/io-tests/configs/types.config @@ -0,0 +1,16 @@ +# tests how config options fall back with invalid types +db-anon-role = "required" +db-schemas = "required" +db-uri = "required" + +# expects string +app.settings.test = false + +# expects boolean or string +db-channel-enabled = 13 + +# expects integer or string +db-max-rows = true + +# expects string +raw-media-types = true diff --git a/test/io-tests/fixtures.yaml b/test/io-tests/fixtures.yaml index 6e3a0f495..42e77e966 100644 --- a/test/io-tests/fixtures.yaml +++ b/test/io-tests/fixtures.yaml @@ -1,3 +1,144 @@ +cli: +# success: valid commands + - name: help long + args: ['--help'] + - name: help short + args: ['-h'] + - name: example long + args: ['--example'] + - name: example short + args: ['-e'] + - name: dump config + args: ['--dump-config'] + use_defaultenv: true + - name: dump schema + args: ['--dump-schema'] + use_defaultenv: true +# failures: config files + - name: no config + expect: error + - name: non-existant config file + expect: error + args: ['does_not_exist.conf'] + - name: invalid config file + expect: error + args: ['test/io-tests/configs/invalid.yaml'] +# failures: required config options + - name: missing db-anon-role + expect: error + env: + PGRST_DB_URI: required + PGRST_DB_SCHEMAS: required + - name: missing db-schemas + expect: error + env: + PGRST_DB_ANON_ROLE: required + PGRST_DB_URI: required + - name: missing db-uri + expect: error + env: + PGRST_DB_ANON_ROLE: required + PGRST_DB_SCHEMAS: required +# failures: wrong config values + - name: invalid server-unix-socket-mode not octal + expect: error + use_defaultenv: true + env: + PGRST_SERVER_UNIX_SOCKET_MODE: '800' + - name: invalid server-unix-socket-mode < 600 + expect: error + use_defaultenv: true + env: + PGRST_SERVER_UNIX_SOCKET_MODE: '599' + - name: invalid server-unix-socket-mode > 777 + expect: error + use_defaultenv: true + env: + PGRST_SERVER_UNIX_SOCKET_MODE: '778' +# TODO: Bug needs to be fixed +# - name: invalid jwt-aud +# expect: error +# use_defaultenv: true +# env: +# PGRST_JWT_AUD: 'htp:/@@localhorst.invalid' + - name: invalid log-level + expect: error + use_defaultenv: true + env: + PGRST_LOG_LEVEL: never + - name: invalid db-tx-end + expect: error + use_defaultenv: true + env: + PGRST_DB_TX_END: random + - name: invalid openapi-server-proxy-uri + expect: error + use_defaultenv: true + env: + PGRST_OPENAPI_SERVER_PROXY_URI: 'htp:/@@localhorst.invalid' + - name: invalid jwt-secret not base64 + expect: error + use_defaultenv: true + env: + PGRST_JWT_SECRET_IS_BASE64: 'true' + PGRST_JWT_SECRET: 'no base-64!' +# success: parsing config values + - name: log-level= + expect: 'log-level = "error"' + use_defaultenv: true + env: + PGRST_LOG_LEVEL: "" + - name: log-level=crit + expect: 'log-level = "crit"' + use_defaultenv: true + env: + PGRST_LOG_LEVEL: crit + - name: log-level=error + expect: 'log-level = "error"' + use_defaultenv: true + env: + PGRST_LOG_LEVEL: error + - name: log-level=warn + expect: 'log-level = "warn"' + use_defaultenv: true + env: + PGRST_LOG_LEVEL: warn + - name: log-level=info + expect: 'log-level = "info"' + use_defaultenv: true + env: + PGRST_LOG_LEVEL: info + - name: db-tx-end= + expect: 'db-tx-end = "commit"' + use_defaultenv: true + env: + PGRST_DB_TX_END: "" + - name: db-tx-end=commit + expect: 'db-tx-end = "commit"' + use_defaultenv: true + env: + PGRST_DB_TX_END: commit + - name: db-tx-end=commit-allow-override + expect: 'db-tx-end = "commit-allow-override"' + use_defaultenv: true + env: + PGRST_DB_TX_END: commit-allow-override + - name: db-tx-end=rollback-allow-override + expect: 'db-tx-end = "rollback-allow-override"' + use_defaultenv: true + env: + PGRST_DB_TX_END: rollback-allow-override + - name: db-tx-end=rollback + expect: 'db-tx-end = "rollback"' + use_defaultenv: true + env: + PGRST_DB_TX_END: rollback + - name: raw-media-types list + expect: 'raw-media-types = "image/png,image/jpeg"' + use_defaultenv: true + env: + PGRST_RAW_MEDIA_TYPES: ' image/png , image/jpeg ' + roleclaims: - key: '.postgrest.a_role' data: @@ -26,6 +167,7 @@ roleclaims: data: role: postgrest_test_author expected_status: 401 + invalidroleclaimkeys: - 'role.other' - '.role##' diff --git a/test/io-tests/test_io.py b/test/io-tests/test_io.py index 7b5d75e64..fbfebf34a 100644 --- a/test/io-tests/test_io.py +++ b/test/io-tests/test_io.py @@ -3,6 +3,7 @@ import contextlib import dataclasses from datetime import datetime +from itertools import repeat from operator import attrgetter import os import pathlib @@ -28,6 +29,22 @@ POSTGREST_BIN = shutil.which("postgrest") SECRET = "reallyreallyreallyreallyverysafe" +def itemgetter(*items): + "operator.itemgetter with None as fallback when key does not exist" + if len(items) == 1: + item = items[0] + + def g(obj): + return obj.get(item) + + else: + + def g(obj): + return tuple(obj.get(item) for item in items) + + return g + + class PostgrestTimedOut(Exception): "Connecting to PostgREST endpoint timed out." @@ -83,27 +100,36 @@ def hpctixfile(): return tixfile.with_suffix(f".{test}.tix") -def dumpconfig(configpath=None, env=None, stdin=None): - "Dump the config as parsed by PostgREST." +def cli(args, env=None, stdin=None): + "Run PostgREST and return stdout." env = env or {} - command = [POSTGREST_BIN, "--dump-config"] + command = [POSTGREST_BIN] + args env["HPCTIXFILE"] = hpctixfile() - if configpath: - command.append(configpath) - process = subprocess.Popen( command, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE ) process.stdin.write(stdin or b"") - result = process.communicate(timeout=5)[0] - process.kill() - process.wait() - if process.returncode != 0: - raise PostgrestError() - return result.decode("utf-8") + try: + result = process.communicate(timeout=5)[0] + if process.returncode != 0: + raise PostgrestError() + return result.decode("utf-8") + finally: + process.kill() + process.wait() + + +def dumpconfig(configpath=None, env=None, stdin=None): + "Dump the config as parsed by PostgREST." + args = ["--dump-config"] + + if configpath: + args.append(configpath) + + return cli(args, env=env, stdin=stdin) @contextlib.contextmanager @@ -181,6 +207,32 @@ def jwtauthheader(claim, secret): return authheader(jwt.encode(claim, secret).decode("utf-8")) +@pytest.mark.parametrize( + "args,env,use_defaultenv,expect", + map(itemgetter("args", "env", "use_defaultenv", "expect"), FIXTURES["cli"]), + ids=map(itemgetter("name"), FIXTURES["cli"]), +) +def test_cli(args, env, use_defaultenv, expect, defaultenv): + """ + When PostgREST is run with arguments and / environment variabales + it should return. Exit code should be according to . + """ + # use --dump-config by default to make sure that the postgrest process will terminate for sure + args = args or ["--dump-config"] + + env = env or {} + if use_defaultenv: + env = {**defaultenv, **env} + + if expect == "error": + with pytest.raises(PostgrestError): + print(cli(args, env=env)) + else: + dump = cli(args, env=env).split("\n") + if expect: + assert expect in dump + + @pytest.mark.parametrize( "expectedconfig", (CONFIGSDIR / "expected").iterdir(), ids=attrgetter("name") )