This commit is contained in:
Héctor Hurtado
2019-08-21 11:07:23 +02:00
7 changed files with 127 additions and 34 deletions
+27 -9
View File
@@ -16,11 +16,12 @@
# limitations under the License. # limitations under the License.
# #
from urllib.parse import urlparse
from itertools import repeat from itertools import repeat
from urllib.parse import urlparse
from uuid import uuid4 from uuid import uuid4
import asyncio import asyncio
import io import io
import json
import logging import logging
import os import os
import shlex import shlex
@@ -278,15 +279,32 @@ def append_route(app):
async def _append_route(request): async def _append_route(request):
"""Create a new Kapow! route.""" """Create a new Kapow! route."""
app.router._frozen = False app.router._frozen = False
content = await request.json()
try:
content = await request.json()
except Exception as exc:
return web.Response(status=400, reason="Malformed JSON")
name = "ROUTE_" + str(uuid4()).replace('-', '_') name = "ROUTE_" + str(uuid4()).replace('-', '_')
app.router.add_route(content["method"], try:
content["url_pattern"], app.router.add_route(content["method"],
handle_route(content["entrypoint"], content["url_pattern"],
content["command"]), handle_route(content["entrypoint"],
name=name) content["command"]),
print(f'Route created {content["method"]} {content["url_pattern"]}') name=name)
return web.json_response(name) except KeyError as exc:
missing = list()
for field in ("method", "url_pattern", "entrypoint", "command"):
if field not in content:
missing.append(field)
return web.Response(status=422,
reason="Missing Mandatory Field",
body=json.dumps({"missing_mandatory_fields": missing}))
except ValueError as exc:
return web.Response(status=422, reason="Invalid Route Spec")
else:
print(f'Route created {content["method"]} {content["url_pattern"]}')
return web.json_response(name, status=201)
return _append_route return _append_route
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -1,5 +1,7 @@
wip:
pipenv run behave --stop --wip
test: test:
gherkin-lint gherkin-lint
pipenv run behave --no-capture --stop pipenv run behave --no-capture
catalog: catalog:
pipenv run behave --steps-catalog pipenv run behave --steps-catalog
@@ -8,7 +8,7 @@ Feature: Kapow! server reject append requests with malformed JSON bodies.
the server will respond with a bad request error. the server will respond with a bad request error.
Given I have a running Kapow! server Given I have a running Kapow! server
When I try to append with this JSON document: When I try to append with this malformed JSON document:
""" """
{ {
"method" "GET", "method" "GET",
@@ -20,5 +20,5 @@ Feature: Kapow! server reject append requests with malformed JSON bodies.
} }
""" """
Then I get 400 as response code Then I get 400 as response code
And I get "Malformed JSON" as response phrase And I get "Malformed JSON" as response reason phrase
And I get an empty response body And I get an empty response body
@@ -21,5 +21,5 @@ Feature: Kapow! server reject insert requests with malformed JSON bodies.
} }
""" """
Then I get bad request as response code Then I get bad request as response code
And I get "Malformed JSON" as response phrase And I get "Malformed JSON" as response reason phrase
And I get an empty response body And I get an empty response body
+38
View File
@@ -0,0 +1,38 @@
from functools import singledispatch
def assert_same_type(f):
def wrapper(a, b):
if type(a) != type(b):
raise TypeError("Non-matching types")
return f(a, b)
return wrapper
@singledispatch
@assert_same_type
def is_subset(model, obj):
return model == obj
@is_subset.register(dict)
@assert_same_type
def _(model, obj):
for key, value in model.items():
if key not in obj or not is_subset(value, obj[key]):
return False
return True
@is_subset.register(list)
@assert_same_type
def _(model, obj):
if type(model) != type(obj):
raise TypeError("Non-matching types")
return is_subset(set(model), set(obj))
@is_subset.register(set)
@assert_same_type
def _(model, obj):
return model <= obj
+56 -21
View File
@@ -1,12 +1,15 @@
import subprocess from contextlib import suppress
from time import sleep from time import sleep
import json
import shlex import shlex
import socket import socket
from contextlib import suppress import subprocess
import requests import requests
from environconfig import EnvironConfig, StringVar, IntVar from environconfig import EnvironConfig, StringVar, IntVar, BooleanVar
from comparedict import is_subset
import logging
class Env(EnvironConfig): class Env(EnvironConfig):
#: How to run Kapow! server #: How to run Kapow! server
@@ -20,9 +23,30 @@ class Env(EnvironConfig):
KAPOW_BOOT_TIMEOUT = IntVar(default=10) KAPOW_BOOT_TIMEOUT = IntVar(default=10)
@given('I have a just started Kapow! server') KAPOW_DEBUG_TESTS = BooleanVar(default=True)
@given('I have a running Kapow! server')
def step_impl(context):
if Env.KAPOW_DEBUG_TESTS:
# These two lines enable debugging at httplib level
# (requests->urllib3->http.client) You will see the REQUEST,
# including HEADERS and DATA, and RESPONSE with HEADERS but without
# DATA. The only thing missing will be the response.body which is
# not logged.
try:
import http.client as http_client
except ImportError:
# Python 2
import httplib as http_client
http_client.HTTPConnection.debuglevel = 1
# You must initialize logging, otherwise you'll not see debug output.
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
def run_kapow_server(context):
context.server = subprocess.Popen( context.server = subprocess.Popen(
shlex.split(Env.KAPOW_SERVER_CMD), shlex.split(Env.KAPOW_SERVER_CMD),
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
@@ -44,6 +68,11 @@ def step_impl(context):
assert open_ports, "API is unreachable after KAPOW_BOOT_TIMEOUT" assert open_ports, "API is unreachable after KAPOW_BOOT_TIMEOUT"
@given('I have a just started Kapow! server')
@given('I have a running Kapow! server')
def step_impl(context):
run_kapow_server(context)
@when('I request a routes listing') @when('I request a routes listing')
def step_impl(context): def step_impl(context):
@@ -58,13 +87,7 @@ def step_impl(context):
@given('I have a Kapow! server whith the following routes') @given('I have a Kapow! server whith the following routes')
def step_impl(context): def step_impl(context):
context.server = subprocess.Popen( run_kapow_server(context)
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!"
if not hasattr(context, 'table'): if not hasattr(context, 'table'):
raise RuntimeError("A table must be set for this step.") raise RuntimeError("A table must be set for this step.")
@@ -97,30 +120,32 @@ def step_impl(context):
if not hasattr(context, 'table'): if not hasattr(context, 'table'):
raise RuntimeError("A table must be set for this step.") raise RuntimeError("A table must be set for this step.")
for row in context.table: row = context.table[0]
response = requests.post(f"{Env.KAPOW_CONTROLAPI_URL}/routes", context.response = requests.post(f"{Env.KAPOW_CONTROLAPI_URL}/routes",
json={h: row[h] for h in row.headings}) json={h: row[h] for h in row.headings})
response.raise_for_status()
@then('I get {code} as response code') @then('I get {code} as response code')
def step_impl(context, code): def step_impl(context, code):
raise NotImplementedError('STEP: Then I get unprocessable entity as response code') assert context.response.status_code == int(code), f"Got {context.response.status_code} instead"
@then('I get "{reason}" as response reason phrase') @then('I get "{reason}" as response reason phrase')
def step_impl(context, reason): def step_impl(context, reason):
raise NotImplementedError('STEP: Then I get "Missing Mandatory Field" as response phrase') assert context.response.reason == reason, f"Got {context.response.reason} instead"
@then('I get the following entity as response body') @then('I get the following entity as response body')
def step_impl(context): def step_impl(context):
raise NotImplementedError('STEP: Then I get the following entity as response body') for row in context.table:
for name, value in row.items():
assert name in context.response.json(), f"Field {name} not present in {context.response.json()}"
assert set(json.loads(value)) == set(context.response.json()[name])
@then('I get an empty response body') @then('I get an empty response body')
def step_impl(context): def step_impl(context):
raise NotImplementedError('STEP: Then I get an empty response body') assert context.response.content == b'', f"Response body is not empty. Got {context.response.content} instead."
@when('I delete the route with id "{id}"') @when('I delete the route with id "{id}"')
@@ -132,6 +157,16 @@ def step_impl(context, id):
def step_impl(context, id): def step_impl(context, id):
raise NotImplementedError('STEP: Given It has a route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"') raise NotImplementedError('STEP: Given It has a route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"')
@when('I insert the route') @when('I insert the route')
def step_impl(context): def step_impl(context):
raise NotImplementedError('STEP: When I insert the route') raise NotImplementedError('STEP: When I insert the route')
@when('I try to append with this malformed JSON document')
@when('I try to append with this JSON document')
def step_impl(context):
context.response = requests.post(
f"{Env.KAPOW_CONTROLAPI_URL}/routes",
headers={"Content-Type": "application/json"},
data=context.text)