Merged
This commit is contained in:
+27
-9
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+3
-1
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user