diff --git a/poc/bin/kapow b/poc/bin/kapow index 9836dfd..0dd3b0c 100755 --- a/poc/bin/kapow +++ b/poc/bin/kapow @@ -306,21 +306,27 @@ def delete_route(app): ######################################################################## -async def run_init_script(app, scripts): +async def run_init_script(app, scripts, interactive): """ Run the init script if given, then wait for the shell to finish. """ if not scripts: # No script given - cmd = "/bin/bash" + if not interactive: + return + else: + cmd = "/bin/bash" else: def build_filenames(): for filename in scripts: yield shlex.quote(filename) yield "<(echo)" filenames = " ".join(build_filenames()) - cmd = f"/bin/bash --init-file <(cat {filenames})" + if interactive: + cmd = f"/bin/bash --init-file <(cat {filenames})" + else: + cmd = f"/bin/bash <(cat {filenames})" shell_task = await asyncio.create_subprocess_shell( cmd, @@ -330,16 +336,17 @@ async def run_init_script(app, scripts): }) await shell_task.wait() - await app.cleanup() - os._exit(shell_task.returncode) + if interactive: + await app.cleanup() + os._exit(shell_task.returncode) async def start_background_tasks(app): global loop - app["debug_tasks"] = loop.create_task(run_init_script(app, app["scripts"])) + app["debug_tasks"] = loop.create_task(run_init_script(app, app["scripts"], app["interactive"])) -async def start_kapow_server(bind, scripts, certfile=None, keyfile=None): +async def start_kapow_server(bind, scripts, certfile=None, interactive=False, keyfile=None): user_app = web.Application(client_max_size=1024**3) user_runner = web.AppRunner(user_app) await user_runner.setup() @@ -366,6 +373,7 @@ async def start_kapow_server(bind, scripts, certfile=None, keyfile=None): web.put('/handlers/{id}/{field:.*}', set_field), ]) control_app["scripts"] = scripts + control_app["interactive"] = interactive control_app.on_startup.append(start_background_tasks) control_runner = web.AppRunner(control_app) @@ -391,12 +399,13 @@ def kapow(ctx): @click.option("--certfile", default=None) @click.option("--keyfile", default=None) @click.option("--bind", default="0.0.0.0:8080") +@click.option("-i", "--interactive", is_flag=True) @click.argument("scripts", nargs=-1) -def server(certfile, keyfile, bind, scripts): +def server(certfile, keyfile, bind, interactive, scripts): if bool(certfile) ^ bool(keyfile): print("For SSL both 'certfile' and 'keyfile' should be provided.") sys.exit(1) - loop.run_until_complete(start_kapow_server(bind, scripts, certfile, keyfile)) + loop.run_until_complete(start_kapow_server(bind, scripts, certfile, interactive, keyfile)) loop.run_forever() @kapow.group() diff --git a/spec/test/.gherkin-lintrc b/spec/test/.gherkin-lintrc index 108e70e..9c5c061 100644 --- a/spec/test/.gherkin-lintrc +++ b/spec/test/.gherkin-lintrc @@ -12,8 +12,8 @@ "Scenario": 2, "Background": 2, "given": 4, - "when": 6, - "then": 6, + "when": 4, + "then": 4, "and": 6, "but": 6, "example": 2, @@ -24,7 +24,7 @@ "new-line-at-eof": ["on", "yes"], "no-multiple-empty-lines": "on", "no-scenario-outlines-without-examples": "on", - "name-length": ["on", {"Feature": 50}], + "name-length": ["on", {"Feature": 80}], "no-restricted-tags": ["on", {"tags": ["@watch", "@wip"]}], "use-and": "on", "no-duplicate-tags": "on", diff --git a/spec/test/Makefile b/spec/test/Makefile index a73f203..49b8678 100644 --- a/spec/test/Makefile +++ b/spec/test/Makefile @@ -1,3 +1,5 @@ test: gherkin-lint - pipenv run behave --no-capture + pipenv run behave --no-capture --stop +catalog: + pipenv run behave --steps-catalog diff --git a/spec/test/features/control/append/error_malformed.feature b/spec/test/features/control/append/error_malformed.feature index dbe2d31..2ed88de 100644 --- a/spec/test/features/control/append/error_malformed.feature +++ b/spec/test/features/control/append/error_malformed.feature @@ -8,17 +8,17 @@ Feature: Kapow! server reject responses with malformed JSON bodies. the server will respond with a bad request error. Given I have a running Kapow! server - When I try to append with this JSON document: - """ - { - "method" "GET", - "url_pattern": /hello, - "entrypoint": null - "command": "echo Hello - World | response /body", - "id": "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" - } - """ - Then I get 400 as response code - And I get "Malformed JSON" as response phrase - And I get an empty response body + When I try to append with this JSON document: + """ + { + "method" "GET", + "url_pattern": /hello, + "entrypoint": null + "command": "echo Hello + World | response /body", + "id": "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" + } + """ + Then I get 400 as response code + And I get "Malformed JSON" as response phrase + And I get an empty response body diff --git a/spec/test/features/control/append/error_unprocessable.feature b/spec/test/features/control/append/error_unprocessable.feature index 5fbb274..933cf07 100644 --- a/spec/test/features/control/append/error_unprocessable.feature +++ b/spec/test/features/control/append/error_unprocessable.feature @@ -9,11 +9,11 @@ Feature: Kapow! server reject responses with semantic errors. missing fields. Given I have a running Kapow! server - When I append the route: - | entrypoint | command | - | /bin/sh -c | ls -la / \| response /body | - Then I get unprocessable entity as response code - And I get "Missing Mandatory Field" as response phrase + When I append the route: + | entrypoint | command | + | /bin/sh -c | ls -la / \| response /body | + Then I get 422 as response code + And I get "Missing Mandatory Field" as response reason phrase And I get the following entity as response body: | missing_mandatory_fields | | "url_pattern", "method" | @@ -23,11 +23,11 @@ Feature: Kapow! server reject responses with semantic errors. field url_pattern the server responds with an error. Given I have a running Kapow! server - When I append the route: - | method | url_pattern | entrypoint | command | - | GET | +123-- | /bin/sh -c | ls -la / \| response /body | - Then I get unprocessable entity as response code - And I get "Invalid Route Spec" as response phrase + When I append the route: + | method | url_pattern | entrypoint | command | + | GET | +123-- | /bin/sh -c | ls -la / \| response /body | + Then I get 422 as response code + And I get "Invalid Route Spec" as response reason phrase And I get an empty response body Scenario: Error because of wrong method value. @@ -35,9 +35,9 @@ Feature: Kapow! server reject responses with semantic errors. field method the server responds with an error. Given I have a running Kapow! server - When I append the route: - | method | url_pattern | entrypoint | command | - | AVECES | +123-- | /bin/sh -c | ls -la / \| response /body | - Then I get unprocessable entity as response code - And I get "Invalid Data Type" as response phrase + When I append the route: + | method | url_pattern | entrypoint | command | + | AVECES | +123-- | /bin/sh -c | ls -la / \| response /body | + Then I get 422 as response code + And I get "Invalid Data Type" as response reason phrase And I get an empty response body diff --git a/spec/test/features/control/append/success.feature b/spec/test/features/control/append/success.feature index ab44a07..a048b17 100644 --- a/spec/test/features/control/append/success.feature +++ b/spec/test/features/control/append/success.feature @@ -8,11 +8,11 @@ Feature: Append new routes in Kapow! server. will be at index 0. Given I have a just started Kapow! server - When I append the route: - | method | url_pattern | entrypoint | command | - | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | - Then I get created as response code - And I get "Created" as response phrase + When I append the route: + | method | url_pattern | entrypoint | command | + | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | + Then I get 201 as response code + And I get "Created" as response reason phrase And I get the following entity as response body: | method | url_pattern | entrypoint | command | index | id | | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | * | @@ -25,11 +25,11 @@ Feature: Append new routes in Kapow! server. | method | url_pattern | entrypoint | command | | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | | GET | /listDir/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| response /body | - When I append the route: - | method | url_pattern | entrypoint | command | - | GET | /listEtcDir | /bin/sh -c | ls -la /etc \| response /body | - Then I get created as response code - And I get "Created" as response phrase + When I append the route: + | method | url_pattern | entrypoint | command | + | GET | /listEtcDir | /bin/sh -c | ls -la /etc \| response /body | + Then I get 201 as response code + And I get "Created" as response reason phrase And I get the following entity as response body: | method | url_pattern | entrypoint | command | index | id | | GET | /listEtcDir | /bin/sh -c | ls -la /etc \| response /body | 2 | * | diff --git a/spec/test/features/control/delete/error_notfound.feature b/spec/test/features/control/delete/error_notfound.feature index 8a0caa7..81ddedd 100644 --- a/spec/test/features/control/delete/error_notfound.feature +++ b/spec/test/features/control/delete/error_notfound.feature @@ -7,7 +7,7 @@ Feature: Fail to delete a route in Kapow! server. will trigger a not found error. Given I have a just started Kapow! server - When I delete the route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" - Then I get not found as response code - And I get "Not Found" as response phrase + When I delete the route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" + Then I get 404 as response code + And I get "Not Found" as response reason phrase And I get an empty response body diff --git a/spec/test/features/control/delete/list_order.feature b/spec/test/features/control/delete/list_order.feature index eec9ecc..b599aba 100644 --- a/spec/test/features/control/delete/list_order.feature +++ b/spec/test/features/control/delete/list_order.feature @@ -16,35 +16,35 @@ Feature: Routes auto-ordering in a Kapow! server. will maintain their relative order and their indexes will be decreased by one. - When I delete the first route inserted - Then I get 200 as response code - And I get "OK" as response phrase - And I get an empty response body - When I request a routes listing - Then I get 200 as response code - And I get "OK" as response phrase - And I get a list with the following elements: - | method | url_pattern | entrypoint | command | Index | id | - | GET | /listVarDir | /bin/sh -c | ls -la /var \| response /body | 0 | * | - | GET | /listEtcDir | /bin/sh -c | ls -la /etc \| response /body | 1 | * | - | GET | /listDir/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| response /body | 2 | * | + When I delete the first route inserted + Then I get 200 as response code + And I get "OK" as response phrase + And I get an empty response body + When I request a routes listing + Then I get 200 as response code + And I get "OK" as response phrase + And I get a list with the following elements: + | method | url_pattern | entrypoint | command | Index | id | + | GET | /listVarDir | /bin/sh -c | ls -la /var \| response /body | 0 | * | + | GET | /listEtcDir | /bin/sh -c | ls -la /etc \| response /body | 1 | * | + | GET | /listDir/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| response /body | 2 | * | Scenario: Removing the last routes. When removing the last route the remaining ones will maintain their relative order and indexes. - When I delete the last route inserted - Then I get 200 as response code - And I get "OK" as response phrase - And I get an empty response body - When I request a routes listing - Then I get 200 as response code - And I get "OK" as response phrase - And I get a list with the following elements: - | method | url_pattern | entrypoint | command | Index | id | - | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | * | - | GET | /listVarDir | /bin/sh -c | ls -la /var \| response /body | 1 | * | - | GET | /listEtcDir | /bin/sh -c | ls -la /etc \| response /body | 2 | * | + When I delete the last route inserted + Then I get 200 as response code + And I get "OK" as response phrase + And I get an empty response body + When I request a routes listing + Then I get 200 as response code + And I get "OK" as response phrase + And I get a list with the following elements: + | method | url_pattern | entrypoint | command | Index | id | + | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | * | + | GET | /listVarDir | /bin/sh -c | ls -la /var \| response /body | 1 | * | + | GET | /listEtcDir | /bin/sh -c | ls -la /etc \| response /body | 2 | * | Scenario: Removing a midst route. When removing a midst route the remaining ones will diff --git a/spec/test/features/control/delete/success.feature b/spec/test/features/control/delete/success.feature index fc31272..09522c3 100644 --- a/spec/test/features/control/delete/success.feature +++ b/spec/test/features/control/delete/success.feature @@ -6,8 +6,8 @@ Feature: Delete routes in Kapow! server. Routes are removed from the sever by specifying their id. Given I have a running Kapow! server - And It has a route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" - When I delete the route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" - Then I get ok as response code - And I get "OK" as response phrase + And It has a route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" + When I delete the route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" + Then I get 200 as response code + And I get "OK" as response reason phrase And I get an empty response body diff --git a/spec/test/features/control/insert/error_malformed.feature b/spec/test/features/control/insert/error_malformed.feature index 10f82ea..fda85fa 100644 --- a/spec/test/features/control/insert/error_malformed.feature +++ b/spec/test/features/control/insert/error_malformed.feature @@ -8,18 +8,18 @@ Feature: Kapow! server reject responses with malformed JSON bodies. the server will respond with a bad request error. Given I have a running Kapow! server - When I try to insert with this JSON document: - """ - { - "method" "GET", - "url_pattern": /hello, - "entrypoint": null - "command": "echo Hello - World | response /body", - "index": 0, - "id": "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" - } - """ - Then I get bad request as response code - And I get "Malformed JSON" as response phrase - And I get an empty response body + When I try to insert with this JSON document: + """ + { + "method" "GET", + "url_pattern": /hello, + "entrypoint": null + "command": "echo Hello + World | response /body", + "index": 0, + "id": "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" + } + """ + Then I get bad request as response code + And I get "Malformed JSON" as response phrase + And I get an empty response body diff --git a/spec/test/features/control/insert/error_unprocessable.feature b/spec/test/features/control/insert/error_unprocessable.feature index df1cba9..357a3bc 100644 --- a/spec/test/features/control/insert/error_unprocessable.feature +++ b/spec/test/features/control/insert/error_unprocessable.feature @@ -8,35 +8,35 @@ Feature: Kapow! server reject insert responses with semantic errors. missing fields. Given I have a running Kapow! server - When I insert the route: - | entrypoint | command | - | /bin/sh -c | ls -la / \| response /body | - Then I get unprocessable entity as response code - And I get "Missing Mandatory Field" as response phrase - And I get the following entity as response body: - | missing_mandatory_fields | - | "url_pattern", "method" | + When I insert the route: + | entrypoint | command | + | /bin/sh -c | ls -la / \| response /body | + Then I get 422 as response code + And I get "Missing Mandatory Field" as response reason phrase + And I get the following entity as response body: + | missing_mandatory_fields | + | "url_pattern", "method" | Scenario: Error because of wrong route specification. If a request contains an invalid expression in the url_pattern field the server responds with an error. Given I have a running Kapow! server - When I insert the route: - | method | url_pattern | entrypoint | command | index | - | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | - Then I get unprocessable entity as response code - And I get "Invalid Route Spec" as response phrase - And I get an empty response body + When I insert the route: + | method | url_pattern | entrypoint | command | index | + | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | + Then I get 422 as response code + And I get "Invalid Route Spec" as response reason phrase + And I get an empty response body Scenario: Error because of wrong method value. If a request contains an invalid value in the method field the server responds with an error. Given I have a running Kapow! server - When I insert the route: - | method | url_pattern | entrypoint | command | index | - | AVECES | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | - Then I get unprocessable entity as response code - And I get "Invalid Data Type" as response phrase - And I get an empty response body + When I insert the route: + | method | url_pattern | entrypoint | command | index | + | AVECES | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | + Then I get 422 as response code + And I get "Invalid Data Type" as response reason phrase + And I get an empty response body diff --git a/spec/test/features/control/insert/success.feature b/spec/test/features/control/insert/success.feature index 4a2f2ff..35775ab 100644 --- a/spec/test/features/control/insert/success.feature +++ b/spec/test/features/control/insert/success.feature @@ -13,11 +13,11 @@ Feature: Insert new routes in Kapow! server. A route can be inserted at the begining of the list by specifying an index 0 in the request. - When I insert the route: - | method | url_pattern | entrypoint | command | index | - | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | - Then I get ok as response code - And I get "OK" as response phrase + When I insert the route: + | method | url_pattern | entrypoint | command | index | + | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | + Then I get 200 as response code + And I get "OK" as response reason phrase And I get the following entity as response body: | method | url_pattern | entrypoint | command | index | id | | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | * | @@ -27,11 +27,11 @@ Feature: Insert new routes in Kapow! server. by specifying an index less or equal to the last index in the request. - When I insert the route: - | method | url_pattern | entrypoint | command | index | - | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 1 | - Then I get ok as response code - And I get "OK" as response phrase + When I insert the route: + | method | url_pattern | entrypoint | command | index | + | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 1 | + Then I get 200 as response code + And I get "OK" as response reason phrase And I get the following entity as response body: | method | url_pattern | entrypoint | command | index | id | | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 1 | * | diff --git a/spec/test/features/control/list/success.feature b/spec/test/features/control/list/success.feature index 8b2c423..c946ec4 100644 --- a/spec/test/features/control/list/success.feature +++ b/spec/test/features/control/list/success.feature @@ -9,10 +9,10 @@ Feature: Listing routes in a Kapow! server will show an empty list of routes. Given I have a just started Kapow! server - When I request a routes listing - Then I get 200 as response code - And I get "OK" as response phrase - And I get an empty list + When I request a routes listing + Then I get 200 as response code + And I get "OK" as response phrase + And I get an empty list Scenario: Listing routes on a server with routes loaded. After some route creation/insertion operations the server @@ -22,10 +22,10 @@ Feature: Listing routes in a Kapow! server | method | url_pattern | entrypoint | command | | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | | GET | /listDir/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| response /body | - When I request a routes listing - Then I get 200 as response code - And I get "OK" as response phrase - And I get a list with the following elements: - | method | url_pattern | entrypoint | command | index | id | - | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | * | - | GET | /listDir/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| response /body | 1 | * | + When I request a routes listing + Then I get 200 as response code + And I get "OK" as response phrase + And I get a list with the following elements: + | method | url_pattern | entrypoint | command | index | id | + | GET | /listRootDir | /bin/sh -c | ls -la / \| response /body | 0 | * | + | GET | /listDir/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| response /body | 1 | * | diff --git a/spec/test/features/steps/steps.py b/spec/test/features/steps/steps.py index 6a9156f..c95a2d1 100644 --- a/spec/test/features/steps/steps.py +++ b/spec/test/features/steps/steps.py @@ -1,8 +1,11 @@ import subprocess from time import sleep +import shlex +import socket +from contextlib import suppress import requests -from environconfig import EnvironConfig, StringVar +from environconfig import EnvironConfig, StringVar, IntVar class Env(EnvironConfig): @@ -15,16 +18,31 @@ class Env(EnvironConfig): #: Where the Data API is KAPOW_DATAAPI_URL = StringVar(default="http://localhost:8080") + KAPOW_BOOT_TIMEOUT = IntVar(default=10) @given('I have a just started Kapow! server') +@given('I have a running Kapow! server') def step_impl(context): context.server = subprocess.Popen( - Env.KAPOW_SERVER_CMD, + shlex.split(Env.KAPOW_SERVER_CMD), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - shell=True) - is_running = context.server.poll() is None - assert is_running, "Server is not running!" + shell=False) + + # Check process is running with reachable APIs + open_ports = False + for _ in range(Env.KAPOW_BOOT_TIMEOUT): + is_running = context.server.poll() is None + assert is_running, "Server is not running!" + with suppress(requests.exceptions.ConnectionError): + open_ports = ( + requests.head(Env.KAPOW_CONTROLAPI_URL, timeout=1).status_code + and requests.head(Env.KAPOW_DATAAPI_URL, timeout=1).status_code) + if open_ports: + break + sleep(1) + + assert open_ports, "API is unreachable after KAPOW_BOOT_TIMEOUT" @when('I request a routes listing') @@ -69,3 +87,51 @@ def step_impl(context): assert header in entry, f"Response does not contain the key {header}" if row[header] != '*': assert entry[header] == row[header], f"Values mismatch" + +# +# +# + +@when('I append the route') +def step_impl(context): + if not hasattr(context, 'table'): + raise RuntimeError("A table must be set for this step.") + + for row in context.table: + response = requests.post(f"{Env.KAPOW_CONTROLAPI_URL}/routes", + json={h: row[h] for h in row.headings}) + response.raise_for_status() + + +@then('I get {code} as response code') +def step_impl(context, code): + raise NotImplementedError('STEP: Then I get unprocessable entity as response code') + + +@then('I get "{reason}" as response reason phrase') +def step_impl(context, reason): + raise NotImplementedError('STEP: Then I get "Missing Mandatory Field" as response phrase') + + +@then('I get the following entity as response body') +def step_impl(context): + raise NotImplementedError('STEP: Then I get the following entity as response body') + + +@then('I get an empty response body') +def step_impl(context): + raise NotImplementedError('STEP: Then I get an empty response body') + + +@when('I delete the route with id "{id}"') +def step_impl(context, id): + raise NotImplementedError('STEP: When I delete the route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"') + + +@given('It has a route with id "{id}"') +def step_impl(context, id): + raise NotImplementedError('STEP: Given It has a route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"') + +@when('I insert the route') +def step_impl(context): + raise NotImplementedError('STEP: When I insert the route')