From c07638601b042531f906de120e9c2e046e98c990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Tue, 20 Aug 2019 10:59:26 +0200 Subject: [PATCH] Implementing poc spec behavior in append route. --- poc/bin/kapow | 29 +++++++--- poc/dist/kapow-0.0.1-py3.7.egg | Bin 0 -> 6929 bytes .../append/error_unprocessable.feature | 4 +- spec/test/features/steps/steps.py | 50 ++++++++++++++---- 4 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 poc/dist/kapow-0.0.1-py3.7.egg diff --git a/poc/bin/kapow b/poc/bin/kapow index 0dd3b0c..bd3c16f 100755 --- a/poc/bin/kapow +++ b/poc/bin/kapow @@ -16,11 +16,12 @@ # limitations under the License. # -from urllib.parse import urlparse from itertools import repeat +from urllib.parse import urlparse from uuid import uuid4 import asyncio import io +import json import logging import os import shlex @@ -280,13 +281,25 @@ def append_route(app): app.router._frozen = False content = await request.json() name = "ROUTE_" + str(uuid4()).replace('-', '_') - app.router.add_route(content["method"], - content["url_pattern"], - handle_route(content["entrypoint"], - content["command"]), - name=name) - print(f'Route created {content["method"]} {content["url_pattern"]}') - return web.json_response(name) + try: + app.router.add_route(content["method"], + content["url_pattern"], + handle_route(content["entrypoint"], + content["command"]), + name=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) return _append_route diff --git a/poc/dist/kapow-0.0.1-py3.7.egg b/poc/dist/kapow-0.0.1-py3.7.egg new file mode 100644 index 0000000000000000000000000000000000000000..4bc98c7d5180a29fc69960fcc70dc7bb23778d7b GIT binary patch literal 6929 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 TXup1lb@%zz<7oYAauEIp$R-B0 literal 0 HcmV?d00001 diff --git a/spec/test/features/control/append/error_unprocessable.feature b/spec/test/features/control/append/error_unprocessable.feature index 933cf07..2025918 100644 --- a/spec/test/features/control/append/error_unprocessable.feature +++ b/spec/test/features/control/append/error_unprocessable.feature @@ -15,8 +15,8 @@ Feature: Kapow! server reject responses with semantic errors. 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" | + | missing_mandatory_fields | + | ["url_pattern", "method"] | Scenario: Error because of wrong route specification. If a request contains an invalid expression in the diff --git a/spec/test/features/steps/steps.py b/spec/test/features/steps/steps.py index c95a2d1..25fce6e 100644 --- a/spec/test/features/steps/steps.py +++ b/spec/test/features/steps/steps.py @@ -1,12 +1,14 @@ -import subprocess +from contextlib import suppress from time import sleep +import json import shlex import socket -from contextlib import suppress +import subprocess import requests -from environconfig import EnvironConfig, StringVar, IntVar +from environconfig import EnvironConfig, StringVar, IntVar, BooleanVar +import logging class Env(EnvironConfig): #: How to run Kapow! server @@ -20,6 +22,30 @@ class Env(EnvironConfig): KAPOW_BOOT_TIMEOUT = IntVar(default=10) + KAPOW_DEBUG_TESTS = BooleanVar(default=True) + + +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 + + @given('I have a just started Kapow! server') @given('I have a running Kapow! server') def step_impl(context): @@ -97,30 +123,32 @@ 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() + row = context.table[0] + context.response = requests.post(f"{Env.KAPOW_CONTROLAPI_URL}/routes", + json={h: row[h] for h in row.headings}) @then('I get {code} as response 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') 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') 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') 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}"')