From 19f7cb0171c3927d0371a3122b13677b6a234953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Mon, 11 Nov 2019 16:39:21 +0100 Subject: [PATCH 01/27] Revert "Removed references to reason phrase from spec". Closes #66 This reverts commit d9e072f89a425093ca25ae65494fb7b78f7554d3. --- spec/README.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/spec/README.md b/spec/README.md index 2fb6fe0..a9523c4 100644 --- a/spec/README.md +++ b/spec/README.md @@ -131,10 +131,13 @@ whole lifetime of the server. conservative in what you do, be liberal in what you accept from others. * We reuse conventions of well-established software projects, such as Docker. * All requests and responses will leverage JSON as the data encoding method. -* The API calls responses have two parts: +* The API calls responses have several parts: * The HTTP status code (e.g., `400`, which is a bad request). The target audience of this information is the client code. The client can thus use this information to control the program flow. + * The HTTP reason phrase. The target audience in this case is the human + operating the client. The human can use this information to make a + decision on how to proceed. * The body is optional * All successful API calls will return a representation of the *final* state attained by the objects which have been addressed (either requested, set or @@ -242,8 +245,8 @@ A new id is created for the appended route so it can be referenced later. } ``` * **Error Responses**: - * **Code**: `400 Bad Request` - * **Code**: `422 Unprocessable Entity` + * **Code**: `400 Malformed JSON` + * **Code**: `422 Invalid Route` * **Sample Call**:
```sh $ curl -X POST --data-binary @- $KAPOW_URL/routes < ```sh $ curl -X PUT --data-binary @- $KAPOW_URL/routes < **Content**: The value of the resource. Note that it may be empty. * **Error Responses**: - * **Code**: `400 Bad Request`
- **Notes**: An invalid resource path has been requested. Check the list of - valid resource paths at the top of this section. - * **Code**: `404 Not Found`
+ * **Code**: `400 Invalid Resource Path`
+ **Notes**: Check the list of valid resource paths at the top of this section. + * **Code**: `404 Handler ID Not Found`
**Notes**: Refers to the handler resource itself. - * **Code**: `404 Not Found`
+ * **Code**: `404 Resource Item Not Found`
**Notes**: Refers to the named item in the corresponding data API resource. * **Sample Call**:
```sh @@ -534,10 +537,9 @@ path doesn't exist or is invalid. * **Success Responses**: * **Code**: `200 OK` * **Error Responses**: - * **Code**: `400 Bad Request`
- **Notes**: An invalid resource path has been requested. Check the list of - valid resource paths at the top of this section. - * **Code**: `404 Not Found`
+ * **Code**: `400 Invalid Resource Path`
+ **Notes**: Check the list of valid resource paths at the top of this section. + * **Code**: `404 Handler ID Not Found`
**Notes**: Refers to the handler resource itself. * **Sample Call**:
```sh From 9eb186bd0f4d093bea8b618d4f404f49c27e1637 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Mon, 11 Nov 2019 17:17:25 +0100 Subject: [PATCH 02/27] Revert "Closes: #66" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c3be29255ab965bd224c48740300750ffc3da84b. Co-authored-by: Roberto Abdelkader Martínez Pérez --- spec/test/features/control/append/error_malformed.feature | 2 +- .../features/control/append/error_unprocessable.feature | 4 ++-- spec/test/features/control/append/success.feature | 4 ++-- spec/test/features/control/delete/error_notfound.feature | 2 +- spec/test/features/control/delete/success.feature | 2 +- spec/test/features/control/get/error_notfound.feature | 2 +- spec/test/features/control/get/success.feature | 2 +- spec/test/features/control/insert/error_malformed.feature | 2 +- .../features/control/insert/error_unprocessable.feature | 6 +++--- spec/test/features/control/insert/success.feature | 4 ++-- spec/test/features/control/list/success.feature | 4 ++-- .../features/data/handler/error_handleridnotfound.feature | 8 ++++---- .../features/data/handler/error_invalidresource.feature | 2 +- .../test/features/data/handler/error_itemnotfound.feature | 2 +- spec/test/features/data/handler/success.feature | 4 ++-- spec/test/features/data/request/success.feature | 2 +- spec/test/features/data/response/success.feature | 2 +- 17 files changed, 27 insertions(+), 27 deletions(-) diff --git a/spec/test/features/control/append/error_malformed.feature b/spec/test/features/control/append/error_malformed.feature index 567a4e3..e64414d 100644 --- a/spec/test/features/control/append/error_malformed.feature +++ b/spec/test/features/control/append/error_malformed.feature @@ -28,4 +28,4 @@ Feature: Kapow! server reject append requests with malformed JSON bodies. Hi! I am an invalid JSON document. """ Then I get 400 as response code -# And I get "Malformed JSON" as response reason phrase + And I get "Malformed JSON" as response reason phrase diff --git a/spec/test/features/control/append/error_unprocessable.feature b/spec/test/features/control/append/error_unprocessable.feature index 92121d9..1a21b46 100644 --- a/spec/test/features/control/append/error_unprocessable.feature +++ b/spec/test/features/control/append/error_unprocessable.feature @@ -31,7 +31,7 @@ Feature: Kapow! server rejects requests with semantic errors. } """ Then I get 422 as response code -# And I get "Invalid Route" as response reason phrase + And I get "Invalid Route" as response reason phrase Scenario: Error because bad route format. If a request contains an invalid expression in the @@ -48,4 +48,4 @@ Feature: Kapow! server rejects requests with semantic errors. } """ Then I get 422 as response code -# And I get "Invalid Route" as response reason phrase + And I get "Invalid Route" as response reason phrase diff --git a/spec/test/features/control/append/success.feature b/spec/test/features/control/append/success.feature index f66ef29..bf67e0c 100644 --- a/spec/test/features/control/append/success.feature +++ b/spec/test/features/control/append/success.feature @@ -33,7 +33,7 @@ Feature: Append new routes in Kapow! server. } """ Then I get 201 as response code -# And I get "Created" as response reason phrase + And I get "Created" as response reason phrase And I get the following response body: """ { @@ -64,7 +64,7 @@ Feature: Append new routes in Kapow! server. } """ Then I get 201 as response code -# And I get "Created" as response reason phrase + And I get "Created" as response reason phrase And I get the following response body: """ { diff --git a/spec/test/features/control/delete/error_notfound.feature b/spec/test/features/control/delete/error_notfound.feature index 587363f..d70292a 100644 --- a/spec/test/features/control/delete/error_notfound.feature +++ b/spec/test/features/control/delete/error_notfound.feature @@ -24,4 +24,4 @@ Feature: Fail to delete a route in Kapow! server. Given I have a just started Kapow! server 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 "Not Found" as response reason phrase diff --git a/spec/test/features/control/delete/success.feature b/spec/test/features/control/delete/success.feature index 0b7a12a..614f186 100644 --- a/spec/test/features/control/delete/success.feature +++ b/spec/test/features/control/delete/success.feature @@ -26,4 +26,4 @@ Feature: Delete routes in Kapow! server. | GET | /qux/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| kapow set /response/body | When I delete the first route Then I get 204 as response code -# And I get "No Content" as response reason phrase + And I get "No Content" as response reason phrase diff --git a/spec/test/features/control/get/error_notfound.feature b/spec/test/features/control/get/error_notfound.feature index 903711c..69e1e31 100644 --- a/spec/test/features/control/get/error_notfound.feature +++ b/spec/test/features/control/get/error_notfound.feature @@ -24,4 +24,4 @@ Feature: Fail to retrieve route details in Kapow! server. Given I have a just started Kapow! server When I get 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 "Not Found" as response reason phrase diff --git a/spec/test/features/control/get/success.feature b/spec/test/features/control/get/success.feature index d8a73cf..04d1287 100644 --- a/spec/test/features/control/get/success.feature +++ b/spec/test/features/control/get/success.feature @@ -26,7 +26,7 @@ Feature: Retrieve route details in Kapow! server. | GET | /qux/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| kapow set /response/body | When I get the first route Then I get 200 as response code -# And I get "OK" as response reason phrase + And I get "OK" as response reason phrase And I get the following response body: """ { diff --git a/spec/test/features/control/insert/error_malformed.feature b/spec/test/features/control/insert/error_malformed.feature index 22f9806..d2453db 100644 --- a/spec/test/features/control/insert/error_malformed.feature +++ b/spec/test/features/control/insert/error_malformed.feature @@ -37,4 +37,4 @@ Feature: Kapow! server rejects insertion requests with malformed JSON bodies. } """ Then I get 400 as response code -# And I get "Malformed JSON" as response reason phrase + And I get "Malformed JSON" as response reason phrase diff --git a/spec/test/features/control/insert/error_unprocessable.feature b/spec/test/features/control/insert/error_unprocessable.feature index 07f2a19..04bc4a0 100644 --- a/spec/test/features/control/insert/error_unprocessable.feature +++ b/spec/test/features/control/insert/error_unprocessable.feature @@ -31,7 +31,7 @@ Feature: Kapow! server rejects insertion requests with semantic errors. } """ Then I get 422 as response code -# And I get "Invalid Route" as response reason phrase + And I get "Invalid Route" as response reason phrase Scenario: Error because wrong route specification. If a request contains an invalid expression in the @@ -49,7 +49,7 @@ Feature: Kapow! server rejects insertion requests with semantic errors. } """ Then I get 422 as response code -# And I get "Invalid Route" as response reason phrase + And I get "Invalid Route" as response reason phrase Scenario: Error because negative index specified. If a request contains a negative number in the @@ -67,4 +67,4 @@ Feature: Kapow! server rejects insertion requests with semantic errors. } """ Then I get 422 as response code -# And I get "Invalid Route" as response reason phrase + And I get "Invalid Route" as response reason phrase diff --git a/spec/test/features/control/insert/success.feature b/spec/test/features/control/insert/success.feature index 9264106..d93d5e1 100644 --- a/spec/test/features/control/insert/success.feature +++ b/spec/test/features/control/insert/success.feature @@ -40,7 +40,7 @@ Feature: Insert new routes in Kapow! server. } """ Then I get 201 as response code -# And I get "Created" as response reason phrase + And I get "Created" as response reason phrase And I get the following response body: """ { @@ -69,7 +69,7 @@ Feature: Insert new routes in Kapow! server. } """ Then I get 201 as response code -# And I get "Created" as response reason phrase + And I get "Created" as response reason phrase And I get the following response body: """ { diff --git a/spec/test/features/control/list/success.feature b/spec/test/features/control/list/success.feature index 4817467..0361043 100644 --- a/spec/test/features/control/list/success.feature +++ b/spec/test/features/control/list/success.feature @@ -25,7 +25,7 @@ Feature: Listing routes in a Kapow! server. 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 reason phrase + And I get "OK" as response reason phrase And I get the following response body: """ [] @@ -41,7 +41,7 @@ Feature: Listing routes in a Kapow! server. | GET | /qux/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| kapow set /response/body | When I request a routes listing Then I get 200 as response code -# And I get "OK" as response reason phrase + And I get "OK" as response reason phrase And I get the following response body: """ [ diff --git a/spec/test/features/data/handler/error_handleridnotfound.feature b/spec/test/features/data/handler/error_handleridnotfound.feature index 782a2dc..3aea141 100644 --- a/spec/test/features/data/handler/error_handleridnotfound.feature +++ b/spec/test/features/data/handler/error_handleridnotfound.feature @@ -25,14 +25,14 @@ Feature: Fail to retrieve resources from nonexistent handler in Kapow! server. Given I have a running Kapow! server When I get the resource "/request/path" for the handler with id "XXXXXXXXXX" Then I get 404 as response code -# And I get "Handler ID Not Found" as response reason phrase + And I get "Handler ID Not Found" as response reason phrase Scenario: Try to get an invalid resource from a nonexistent handler. A request to retrieve an invalid resource from a nonexistent - handler will trigger an invalid resource path error + handler will trigger a handler ID not found error even if the resource is invalid. Given I have a running Kapow! server When I get the resource "/invalid/path" for the handler with id "XXXXXXXXXX" - Then I get 400 as response code -# And I get "Invalid Resource Path" as response reason phrase + Then I get 404 as response code + And I get "Handler ID Not Found" as response reason phrase diff --git a/spec/test/features/data/handler/error_invalidresource.feature b/spec/test/features/data/handler/error_invalidresource.feature index 7afa680..73dcc94 100644 --- a/spec/test/features/data/handler/error_invalidresource.feature +++ b/spec/test/features/data/handler/error_invalidresource.feature @@ -27,4 +27,4 @@ Feature: Fail to retrieve an invalid resource for a handler in Kapow! server. When I send a request to the testing route "/foo" And I get the resource "/invented/path" Then I get 400 as response code -# And I get "Invalid Resource Path" as response reason phrase + And I get "Invalid Resource Path" as response reason phrase diff --git a/spec/test/features/data/handler/error_itemnotfound.feature b/spec/test/features/data/handler/error_itemnotfound.feature index 9b505ab..887446a 100644 --- a/spec/test/features/data/handler/error_itemnotfound.feature +++ b/spec/test/features/data/handler/error_itemnotfound.feature @@ -28,4 +28,4 @@ Feature: Fail to retrieve nonexistent resource items in Kapow! server. When I send a request to the testing route "/foo" And I get the resource "/request/params/meloinvento" Then I get 404 as response code -# And I get "Resource Item Not Found" as response reason phrase + And I get "Resource Item Not Found" as response reason phrase diff --git a/spec/test/features/data/handler/success.feature b/spec/test/features/data/handler/success.feature index 5c4bbc5..91c5a36 100644 --- a/spec/test/features/data/handler/success.feature +++ b/spec/test/features/data/handler/success.feature @@ -28,7 +28,7 @@ Feature: Retrieve a resource from a handler in Kapow! server. When I send a request to the testing route "/foo" And I get the resource "/request/path" Then I get 200 as response code -# And I get "OK" as response reason phrase + And I get "OK" as response reason phrase And I get the following response raw body: """ /foo @@ -44,7 +44,7 @@ Feature: Retrieve a resource from a handler in Kapow! server. When I send a request to the testing route "/foo?name=bar" And I get the resource "/request/params/name" Then I get 200 as response code -# And I get "OK" as response reason phrase + And I get "OK" as response reason phrase And I get the following response raw body: """ bar diff --git a/spec/test/features/data/request/success.feature b/spec/test/features/data/request/success.feature index 3c2fa2a..03ca2a9 100644 --- a/spec/test/features/data/request/success.feature +++ b/spec/test/features/data/request/success.feature @@ -34,7 +34,7 @@ Feature: Retrieve request resources from a handler in Kapow! server. | body | | bodyVal1 | And I get the resource "" Then I get 200 as response code -# And I get "OK" as response reason phrase + And I get "OK" as response reason phrase And I get the following response raw body: """ diff --git a/spec/test/features/data/response/success.feature b/spec/test/features/data/response/success.feature index 3fc46e7..05cff7e 100644 --- a/spec/test/features/data/response/success.feature +++ b/spec/test/features/data/response/success.feature @@ -41,7 +41,7 @@ Feature: Setting values for handler response resources in Kapow! server. And I set the resource "" with value "" And I release the testing request Then I get 200 as response code -# And I get "OK" as response reason phrase + And I get "OK" as response reason phrase And I get the value "" for the response "" named "" in the testing request Examples: From e4606ab84a1b99bbd21f1a1c8d72907139cb0de8 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Mon, 11 Nov 2019 17:32:52 +0100 Subject: [PATCH 03/27] Drop compound error scenario MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to allow different Kapow! implementations (Go, Python, ...) compound errors will be left as implementation-dependent. The spec will only test for simple error conditions. Co-authored-by: Roberto Abdelkader Martínez Pérez --- .../data/handler/error_handleridnotfound.feature | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/spec/test/features/data/handler/error_handleridnotfound.feature b/spec/test/features/data/handler/error_handleridnotfound.feature index 3aea141..34b13f2 100644 --- a/spec/test/features/data/handler/error_handleridnotfound.feature +++ b/spec/test/features/data/handler/error_handleridnotfound.feature @@ -26,13 +26,3 @@ Feature: Fail to retrieve resources from nonexistent handler in Kapow! server. When I get the resource "/request/path" for the handler with id "XXXXXXXXXX" Then I get 404 as response code And I get "Handler ID Not Found" as response reason phrase - - Scenario: Try to get an invalid resource from a nonexistent handler. - A request to retrieve an invalid resource from a nonexistent - handler will trigger a handler ID not found error - even if the resource is invalid. - - Given I have a running Kapow! server - When I get the resource "/invalid/path" for the handler with id "XXXXXXXXXX" - Then I get 404 as response code - And I get "Handler ID Not Found" as response reason phrase From 6194c6961a0f9a6f064e5835fe4ffae8b8df9a14 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Mon, 11 Nov 2019 17:46:16 +0100 Subject: [PATCH 04/27] Tweak PoC to pass current test suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roberto Abdelkader Martínez Pérez --- poc/Makefile | 2 +- spec/test/features/steps/steps.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poc/Makefile b/poc/Makefile index b7a9a04..5853f17 100644 --- a/poc/Makefile +++ b/poc/Makefile @@ -6,4 +6,4 @@ sync: pipenv sync --dev test: sync - pipenv run make -C ../spec/test + KAPOW_DATAAPI_URL=http://localhost:8081 pipenv run make -C ../spec/test diff --git a/spec/test/features/steps/steps.py b/spec/test/features/steps/steps.py index f280e0f..6e04527 100644 --- a/spec/test/features/steps/steps.py +++ b/spec/test/features/steps/steps.py @@ -52,7 +52,7 @@ class Env(EnvironConfig): #: Where the User Interface is KAPOW_USER_URL = StringVar(default="http://localhost:8080") - KAPOW_BOOT_TIMEOUT = IntVar(default=10) + KAPOW_BOOT_TIMEOUT = IntVar(default=1000) KAPOW_DEBUG_TESTS = BooleanVar(default=False) From 822e4f85df32d32bc33337b5c353adac12025d08 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Mon, 11 Nov 2019 18:02:07 +0100 Subject: [PATCH 05/27] spec: clarify the order of the execution of the tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roberto Abdelkader Martínez Pérez --- spec/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/README.md b/spec/README.md index a9523c4..8cb1403 100644 --- a/spec/README.md +++ b/spec/README.md @@ -142,6 +142,8 @@ whole lifetime of the server. * All successful API calls will return a representation of the *final* state attained by the objects which have been addressed (either requested, set or deleted). +* When several error conditions can happen at the same time, the order of the + checks is implementation-defined. For instance, given this request: ```http @@ -390,6 +392,8 @@ response. * Regarding HTTP request and response bodies: * The response body will be empty in case of error. * It will transport binary data in other case. +* When several error conditions can happen at the same time, the order of the + checks is implementation-defined. ## API Elements From 653fc3eb32bd2a56f63528628315452029a9421c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Tue, 12 Nov 2019 13:04:29 +0100 Subject: [PATCH 06/27] Escape input --- poc/examples/nmap/nmap.pow | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poc/examples/nmap/nmap.pow b/poc/examples/nmap/nmap.pow index 37ec075..ef42eb9 100755 --- a/poc/examples/nmap/nmap.pow +++ b/poc/examples/nmap/nmap.pow @@ -16,4 +16,4 @@ # limitations under the License. # -kapow route add -X GET '/list/{ip}' -c 'nmap -sL $(kapow get /request/matches/ip) | kapow set /response/body' +kapow route add -X GET '/list/{ip}' -c 'nmap -sL "$(kapow get /request/matches/ip)" | kapow set /response/body' From 1566dcf30f24fc8d4bbe235e94548f9522fb2460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Tue, 12 Nov 2019 13:05:25 +0100 Subject: [PATCH 07/27] Sanitize input --- poc/examples/pandoc/pandoc.pow | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poc/examples/pandoc/pandoc.pow b/poc/examples/pandoc/pandoc.pow index fb245c5..5558cd3 100755 --- a/poc/examples/pandoc/pandoc.pow +++ b/poc/examples/pandoc/pandoc.pow @@ -17,8 +17,8 @@ # kapow route add -X POST --entrypoint '/bin/zsh -c' '/convert/{from}/{to}' - <<-'EOF' - pandoc --from=$(kapow get /request/matches/from) \ - --to=$(kapow get /request/matches/to) \ + pandoc --from="$(kapow get /request/matches/from)" \ + --to="$(kapow get /request/matches/to)" \ --output=>(kapow set /response/body) \ =(kapow get /request/body) EOF From 049081c8b0c7e24a05d2ec8594f6ab3f14abd147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Tue, 12 Nov 2019 13:06:31 +0100 Subject: [PATCH 08/27] Update topdf --- poc/examples/pdfeditor/topdf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poc/examples/pdfeditor/topdf b/poc/examples/pdfeditor/topdf index 8e2dddc..f89d0c0 100755 --- a/poc/examples/pdfeditor/topdf +++ b/poc/examples/pdfeditor/topdf @@ -17,7 +17,7 @@ # tmpfile=$(mktemp --suffix=.pdf) -pandoc --from=$(kapow get /request/form/from) --to=pdf --output=${tmpfile} -t latex =(kapow get /request/form/content) +pandoc --from="$(kapow get /request/form/from)" --to=pdf --output=${tmpfile} -t latex =(kapow get /request/form/content) if [ $? -eq 0 ]; then kapow set /response/headers/Content-Type application/pdf kapow set /response/body < ${tmpfile} From 19bc0df9683d435b10de862e46fed3125a385c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Tue, 12 Nov 2019 13:16:30 +0100 Subject: [PATCH 09/27] Old poc examples were full of injections due to migration strategy. Will be rewritten in doc. --- poc/examples/eval.pow | 19 --------- poc/examples/eval.testme | 8 ---- poc/examples/nmap-web.html | 35 ----------------- poc/examples/nmap-web.pow | 55 --------------------------- poc/examples/nmap/Dockerfile | 7 ---- poc/examples/nmap/nmap.pow | 19 --------- poc/examples/operator.pow | 41 -------------------- poc/examples/pandoc/pandoc.pow | 26 ------------- poc/examples/pandoc/testme | 8 ---- poc/examples/pdfeditor/pdfeditor.html | 41 -------------------- poc/examples/pdfeditor/pdfeditor.pow | 20 ---------- poc/examples/pdfeditor/topdf | 28 -------------- poc/examples/tcpdump/README.md | 16 -------- poc/examples/tcpdump/tcpdump.pow | 1 - poc/examples/torrent.pow | 43 --------------------- 15 files changed, 367 deletions(-) delete mode 100755 poc/examples/eval.pow delete mode 100755 poc/examples/eval.testme delete mode 100644 poc/examples/nmap-web.html delete mode 100755 poc/examples/nmap-web.pow delete mode 100644 poc/examples/nmap/Dockerfile delete mode 100755 poc/examples/nmap/nmap.pow delete mode 100755 poc/examples/operator.pow delete mode 100755 poc/examples/pandoc/pandoc.pow delete mode 100755 poc/examples/pandoc/testme delete mode 100644 poc/examples/pdfeditor/pdfeditor.html delete mode 100755 poc/examples/pdfeditor/pdfeditor.pow delete mode 100755 poc/examples/pdfeditor/topdf delete mode 100644 poc/examples/tcpdump/README.md delete mode 100644 poc/examples/tcpdump/tcpdump.pow delete mode 100755 poc/examples/torrent.pow diff --git a/poc/examples/eval.pow b/poc/examples/eval.pow deleted file mode 100755 index acc18bd..0000000 --- a/poc/examples/eval.pow +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# -# Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -kapow route add -X POST '/eval' -c '$($(kapow get /request/body) | kapow set /response/stream)' diff --git a/poc/examples/eval.testme b/poc/examples/eval.testme deleted file mode 100755 index a17bfc9..0000000 --- a/poc/examples/eval.testme +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env sh - -curl -X POST --data-binary @- http://localhost:8080/eval < - - - - Nmap - - -
-
- Nmap parameters -
- - -

- Can pass hostnames, IP addresses, networks, etc. e.g.: - scanme.nmap.org, microsoft.com/24, 192.168.0.1; - 10.0.0-255.1-254 -

-
-
- - -

- Only scan specified ports. e.g.: 22; 1-65535; - U:53,111,137,T:21-25,80,139,8080,S:9 -

-
-
- - -
-
-
- - diff --git a/poc/examples/nmap-web.pow b/poc/examples/nmap-web.pow deleted file mode 100755 index 6fb0755..0000000 --- a/poc/examples/nmap-web.pow +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -# -# Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# -# Nmap produces an XML report, suitable for rendering in a web browser -# - -# Call examples: -# -# $ browser http://localhost:8080 -# -# $ curl -v http://localhost:8080/nmap.xml -d 'target_spec=127.0.0.1&port_ranges=9000' -# - -kapow route add -X GET / - <<-'EOF' - cat nmap-web.html | kapow set /response/body -EOF - -kapow route add -X GET /nmap.xsl - <<-'EOF' - curl --silent https://svn.nmap.org/nmap/docs/nmap.xsl \ - | kapow set /response/body -EOF - -kapow route add -X POST /nmap.xml - <<-'EOF' - - TARGET_SPEC=$(kapow get /request/form/target_spec) - : ${TARGET_SPEC:=127.0.0.1} - - PORT_RANGES=$(kapow get /request/form/port_ranges) - : ${PORT_RANGES:=8080} - - nmap \ - -Pn \ - -n \ - -p "$PORT_RANGES" \ - -oX - \ - --stylesheet /nmap.xsl \ - "$TARGET_SPEC" \ - | kapow set /response/body -EOF diff --git a/poc/examples/nmap/Dockerfile b/poc/examples/nmap/Dockerfile deleted file mode 100644 index 5fe5224..0000000 --- a/poc/examples/nmap/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM bbvalabsci/kapow:latest - -RUN apk add nmap - -COPY nmap.pow /tmp/ - -CMD ["server", "/tmp/nmap.pow"] diff --git a/poc/examples/nmap/nmap.pow b/poc/examples/nmap/nmap.pow deleted file mode 100755 index ef42eb9..0000000 --- a/poc/examples/nmap/nmap.pow +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# -# Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -kapow route add -X GET '/list/{ip}' -c 'nmap -sL "$(kapow get /request/matches/ip)" | kapow set /response/body' diff --git a/poc/examples/operator.pow b/poc/examples/operator.pow deleted file mode 100755 index 27bca22..0000000 --- a/poc/examples/operator.pow +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -# -# Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -kapow route add /list/files -c 'ls -la $(kapow get /request/params/path) | kapow set /response/body' - -kapow route add /list/processes -c 'ps -aux | kapow set /response/body' - -kapow route add /show/cpuinfo -c 'kapow set /response/body < /proc/cpuinfo' - -kapow route add /show/memory -c 'free -m | kapow set /response/body' - -kapow route add /show/disk -c 'df -h | kapow set /response/body' - -kapow route add /show/connections -c 'ss -pluton | kapow set /response/body' - -kapow route add /show/mounts -c 'mount | kapow set /response/body' - -kapow route add /tail/dmesg - <<-'EOF' - kapow set /response/headers/Content-Type text/plain - dmesg -w | kapow set /response/stream -EOF - -kapow route add /tail/journal - <<-'EOF' - kapow set /response/headers/Content-Type text/plain - journalctl -f | kapow set /response/stream -EOF diff --git a/poc/examples/pandoc/pandoc.pow b/poc/examples/pandoc/pandoc.pow deleted file mode 100755 index 5558cd3..0000000 --- a/poc/examples/pandoc/pandoc.pow +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# -# Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -kapow route add -X POST --entrypoint '/bin/zsh -c' '/convert/{from}/{to}' - <<-'EOF' - pandoc --from="$(kapow get /request/matches/from)" \ - --to="$(kapow get /request/matches/to)" \ - --output=>(kapow set /response/body) \ - =(kapow get /request/body) -EOF -kapow route add -X GET '/formats/input' -c 'pandoc --list-input-formats | kapow set /response/body' -kapow route add -X GET '/formats/output' -c 'pandoc --list-output-formats | grep -v pdf | kapow set /response/body' diff --git a/poc/examples/pandoc/testme b/poc/examples/pandoc/testme deleted file mode 100755 index e2fabd3..0000000 --- a/poc/examples/pandoc/testme +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env sh - -curl -X POST --data-binary @- http://localhost:8080/convert/markdown/man < - - PDF Editor - - - -
-
-
-
-
-
-
-
AWYSIWYG PDF Editor
-
-
InputFormat
- -
-
-
InputFormat
- -
-
-
Preview!
-
-
-
-
-
- -
-
-
-
-
- - - diff --git a/poc/examples/pdfeditor/pdfeditor.pow b/poc/examples/pdfeditor/pdfeditor.pow deleted file mode 100755 index 06eb815..0000000 --- a/poc/examples/pdfeditor/pdfeditor.pow +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# -# Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -kapow route add -X POST --entrypoint ./topdf '/editor/pdf' -kapow route add / -c 'kapow set /response/headers/Content-Type text/html && kapow set /response/body < pdfeditor.html' diff --git a/poc/examples/pdfeditor/topdf b/poc/examples/pdfeditor/topdf deleted file mode 100755 index f89d0c0..0000000 --- a/poc/examples/pdfeditor/topdf +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/zsh - -# -# Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -tmpfile=$(mktemp --suffix=.pdf) -pandoc --from="$(kapow get /request/form/from)" --to=pdf --output=${tmpfile} -t latex =(kapow get /request/form/content) -if [ $? -eq 0 ]; then - kapow set /response/headers/Content-Type application/pdf - kapow set /response/body < ${tmpfile} - kapow set /response/status 200 -else - kapow set /response/status 500 -fi -rm -f ${tmpfile} diff --git a/poc/examples/tcpdump/README.md b/poc/examples/tcpdump/README.md deleted file mode 100644 index de84f51..0000000 --- a/poc/examples/tcpdump/README.md +++ /dev/null @@ -1,16 +0,0 @@ -Remote tcpdump sniffer with source filtering -============================================ - -1. Add any filter you want to the `tcpdump` command inside `tcpdump.pow` to filter - any traffic you don't want to be sniffed! -2. For the sake of simplicity, run `sudo -E kapow server tcpdump.pow`. In a - production environment, `tcpdump` should be run with the appropiate permissions, - but kapow can (and should) run as an unprivileged user. -3. In your local machine run: - ```bash - curl http://localhost:8080/sniff/ | sudo -E wireshark -k -i - - ``` - Again, for the sake of simplicity, `Wireshark` is running as root. If you don't want - to run it this way, follow this guide: - https://gist.github.com/MinaMikhailcom/0825906230cbbe478faf4d08abe9d11a -4. Profit! diff --git a/poc/examples/tcpdump/tcpdump.pow b/poc/examples/tcpdump/tcpdump.pow deleted file mode 100644 index 36b83d9..0000000 --- a/poc/examples/tcpdump/tcpdump.pow +++ /dev/null @@ -1 +0,0 @@ -kapow route add /sniff/{iface} -c 'tcpdump -i "$(kapow get /request/matches/iface)" -U -s0 -w - "not port 8080" | kapow set /response/stream' diff --git a/poc/examples/torrent.pow b/poc/examples/torrent.pow deleted file mode 100755 index ee31e7d..0000000 --- a/poc/examples/torrent.pow +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -# -# Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -kapow route add / - <<-'EOF' - kapow set /response/headers/Content-Type text/html - kapow set /response/body <<-HTML - - - Add me to your bookmarks! - - - HTML -EOF - -kapow route add /save/magnet -e '/bin/bash -c' - <<-'EOF' - link=$(kapow get /request/params/link) - [ -z $link ] && kapow set /response/status 400 && exit 0 - - watch_folder=/tmp - cd $watch_folder - [[ "$link" =~ xt=urn:btih:([^&/]+) ]] || exit; - echo "d10:magnet-uri${#link}:${link}e" > "meta-${BASH_REMATCH[1]}.torrent" - - kapow set /response/status 302 - kapow set /response/headers/Location /torrent/list -EOF - -kapow route add /torrent/list -c 'kapow set /response/body "Not Implemented Yet"' From 2d5743af843ec714b735471737fe50ea6b5c07ee Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Wed, 13 Nov 2019 18:18:44 +0100 Subject: [PATCH 10/27] Add blank line between legal header and package declaration Otherwise, the legal header is rendered as package documentation. --- internal/client/get.go | 1 + internal/client/get_test.go | 1 + internal/client/route_add.go | 1 + internal/client/route_add_test.go | 1 + internal/client/route_list.go | 1 + internal/client/route_list_test.go | 1 + internal/client/route_remove.go | 1 + internal/client/route_remove_test.go | 1 + internal/client/set.go | 1 + internal/client/set_test.go | 1 + internal/cmd/get.go | 1 + internal/cmd/route.go | 1 + internal/cmd/server.go | 1 + internal/cmd/set.go | 1 + internal/cmd/validations.go | 1 + internal/http/reason.go | 1 + internal/http/reason_test.go | 1 + internal/http/request.go | 1 + internal/http/request_test.go | 1 + internal/server/control/control.go | 1 + internal/server/control/control_test.go | 1 + internal/server/data/decorator.go | 1 + internal/server/data/decorator_test.go | 1 + internal/server/data/resource.go | 1 + internal/server/data/resource_test.go | 1 + internal/server/data/server.go | 1 + internal/server/data/server_test.go | 1 + internal/server/data/state.go | 1 + internal/server/data/state_test.go | 1 + internal/server/model/handler.go | 1 + internal/server/model/route.go | 1 + internal/server/server.go | 1 + internal/server/user/mux/gorillize.go | 1 + internal/server/user/mux/gorillize_test.go | 1 + internal/server/user/mux/handlerbuilder.go | 1 + internal/server/user/mux/handlerbuilder_test.go | 1 + internal/server/user/mux/mux.go | 1 + internal/server/user/mux/mux_test.go | 1 + internal/server/user/server.go | 1 + internal/server/user/spawn/spawn.go | 1 + internal/server/user/spawn/spawn_test.go | 1 + internal/server/user/state.go | 1 + internal/server/user/state_test.go | 1 + main.go | 1 + 44 files changed, 44 insertions(+) diff --git a/internal/client/get.go b/internal/client/get.go index d392fe0..f0816ee 100644 --- a/internal/client/get.go +++ b/internal/client/get.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package client import ( diff --git a/internal/client/get_test.go b/internal/client/get_test.go index 60c4b6d..4d4d21e 100644 --- a/internal/client/get_test.go +++ b/internal/client/get_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package client import ( diff --git a/internal/client/route_add.go b/internal/client/route_add.go index e008203..f1cdffe 100644 --- a/internal/client/route_add.go +++ b/internal/client/route_add.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package client import ( diff --git a/internal/client/route_add_test.go b/internal/client/route_add_test.go index 6c74e28..e66779f 100644 --- a/internal/client/route_add_test.go +++ b/internal/client/route_add_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package client import ( diff --git a/internal/client/route_list.go b/internal/client/route_list.go index b2c2053..a22f86f 100644 --- a/internal/client/route_list.go +++ b/internal/client/route_list.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package client import ( diff --git a/internal/client/route_list_test.go b/internal/client/route_list_test.go index bb6a80f..e5eadb5 100644 --- a/internal/client/route_list_test.go +++ b/internal/client/route_list_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package client import ( diff --git a/internal/client/route_remove.go b/internal/client/route_remove.go index d26f599..53329ed 100644 --- a/internal/client/route_remove.go +++ b/internal/client/route_remove.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package client import ( diff --git a/internal/client/route_remove_test.go b/internal/client/route_remove_test.go index ac42b15..21fa460 100644 --- a/internal/client/route_remove_test.go +++ b/internal/client/route_remove_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package client import ( diff --git a/internal/client/set.go b/internal/client/set.go index 0df76d1..745c1ab 100644 --- a/internal/client/set.go +++ b/internal/client/set.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package client import ( diff --git a/internal/client/set_test.go b/internal/client/set_test.go index f18d76c..d13f5b9 100644 --- a/internal/client/set_test.go +++ b/internal/client/set_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package client_test import ( diff --git a/internal/cmd/get.go b/internal/cmd/get.go index 1fdbe56..497a859 100644 --- a/internal/cmd/get.go +++ b/internal/cmd/get.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package cmd import ( diff --git a/internal/cmd/route.go b/internal/cmd/route.go index 51d9499..3925517 100644 --- a/internal/cmd/route.go +++ b/internal/cmd/route.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package cmd import ( diff --git a/internal/cmd/server.go b/internal/cmd/server.go index f176b91..14719c8 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package cmd import ( diff --git a/internal/cmd/set.go b/internal/cmd/set.go index 1f81a36..f3e2f95 100644 --- a/internal/cmd/set.go +++ b/internal/cmd/set.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package cmd import ( diff --git a/internal/cmd/validations.go b/internal/cmd/validations.go index 2e99b98..19012c8 100644 --- a/internal/cmd/validations.go +++ b/internal/cmd/validations.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package cmd import ( diff --git a/internal/http/reason.go b/internal/http/reason.go index 464f3dc..89d8732 100644 --- a/internal/http/reason.go +++ b/internal/http/reason.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package http import ( diff --git a/internal/http/reason_test.go b/internal/http/reason_test.go index d71ff82..aa17b93 100644 --- a/internal/http/reason_test.go +++ b/internal/http/reason_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package http import ( diff --git a/internal/http/request.go b/internal/http/request.go index 970a8aa..a01063d 100644 --- a/internal/http/request.go +++ b/internal/http/request.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package http import ( diff --git a/internal/http/request_test.go b/internal/http/request_test.go index b0785dc..86e9921 100644 --- a/internal/http/request_test.go +++ b/internal/http/request_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package http import ( diff --git a/internal/server/control/control.go b/internal/server/control/control.go index 216e8ff..19d4310 100644 --- a/internal/server/control/control.go +++ b/internal/server/control/control.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package control import ( diff --git a/internal/server/control/control_test.go b/internal/server/control/control_test.go index 6f792c6..975039e 100644 --- a/internal/server/control/control_test.go +++ b/internal/server/control/control_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package control import ( diff --git a/internal/server/data/decorator.go b/internal/server/data/decorator.go index 62695e7..ec5909d 100644 --- a/internal/server/data/decorator.go +++ b/internal/server/data/decorator.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package data import ( diff --git a/internal/server/data/decorator_test.go b/internal/server/data/decorator_test.go index b66fd8a..dbf451a 100644 --- a/internal/server/data/decorator_test.go +++ b/internal/server/data/decorator_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package data import ( diff --git a/internal/server/data/resource.go b/internal/server/data/resource.go index d4a8a70..8b1076d 100644 --- a/internal/server/data/resource.go +++ b/internal/server/data/resource.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package data import ( diff --git a/internal/server/data/resource_test.go b/internal/server/data/resource_test.go index 1d5e387..e723f0f 100644 --- a/internal/server/data/resource_test.go +++ b/internal/server/data/resource_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package data import ( diff --git a/internal/server/data/server.go b/internal/server/data/server.go index 6139428..75abae8 100644 --- a/internal/server/data/server.go +++ b/internal/server/data/server.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package data import ( diff --git a/internal/server/data/server_test.go b/internal/server/data/server_test.go index eaeb932..e3c848b 100644 --- a/internal/server/data/server_test.go +++ b/internal/server/data/server_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package data import ( diff --git a/internal/server/data/state.go b/internal/server/data/state.go index e95a294..94c4cfc 100644 --- a/internal/server/data/state.go +++ b/internal/server/data/state.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package data import ( diff --git a/internal/server/data/state_test.go b/internal/server/data/state_test.go index 7ca5e15..f082c3a 100644 --- a/internal/server/data/state_test.go +++ b/internal/server/data/state_test.go @@ -15,6 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package data import ( diff --git a/internal/server/model/handler.go b/internal/server/model/handler.go index b96ac44..8d79b35 100644 --- a/internal/server/model/handler.go +++ b/internal/server/model/handler.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package model import ( diff --git a/internal/server/model/route.go b/internal/server/model/route.go index 900fd59..f630dce 100644 --- a/internal/server/model/route.go +++ b/internal/server/model/route.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package model // Route contains the data needed to represent a Kapow! user route. diff --git a/internal/server/server.go b/internal/server/server.go index 7c8da7e..766ec3c 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package server import ( diff --git a/internal/server/user/mux/gorillize.go b/internal/server/user/mux/gorillize.go index a089f07..9ebcc2d 100644 --- a/internal/server/user/mux/gorillize.go +++ b/internal/server/user/mux/gorillize.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package mux import ( diff --git a/internal/server/user/mux/gorillize_test.go b/internal/server/user/mux/gorillize_test.go index 69ba2f4..f531f2d 100644 --- a/internal/server/user/mux/gorillize_test.go +++ b/internal/server/user/mux/gorillize_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package mux import ( diff --git a/internal/server/user/mux/handlerbuilder.go b/internal/server/user/mux/handlerbuilder.go index 64fe7d7..09dbdfb 100644 --- a/internal/server/user/mux/handlerbuilder.go +++ b/internal/server/user/mux/handlerbuilder.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package mux import ( diff --git a/internal/server/user/mux/handlerbuilder_test.go b/internal/server/user/mux/handlerbuilder_test.go index 3e2d7f3..da5dde1 100644 --- a/internal/server/user/mux/handlerbuilder_test.go +++ b/internal/server/user/mux/handlerbuilder_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package mux import ( diff --git a/internal/server/user/mux/mux.go b/internal/server/user/mux/mux.go index 9946f5f..508cbcb 100644 --- a/internal/server/user/mux/mux.go +++ b/internal/server/user/mux/mux.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package mux import ( diff --git a/internal/server/user/mux/mux_test.go b/internal/server/user/mux/mux_test.go index 5cbc04f..771ad78 100644 --- a/internal/server/user/mux/mux_test.go +++ b/internal/server/user/mux/mux_test.go @@ -15,6 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package mux import ( diff --git a/internal/server/user/server.go b/internal/server/user/server.go index bf3800a..15f0706 100644 --- a/internal/server/user/server.go +++ b/internal/server/user/server.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package user import ( diff --git a/internal/server/user/spawn/spawn.go b/internal/server/user/spawn/spawn.go index fb13231..50f4f4d 100644 --- a/internal/server/user/spawn/spawn.go +++ b/internal/server/user/spawn/spawn.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package spawn import ( diff --git a/internal/server/user/spawn/spawn_test.go b/internal/server/user/spawn/spawn_test.go index f5ebae3..ef77791 100644 --- a/internal/server/user/spawn/spawn_test.go +++ b/internal/server/user/spawn/spawn_test.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package spawn import ( diff --git a/internal/server/user/state.go b/internal/server/user/state.go index fc7e3a5..ed240b5 100644 --- a/internal/server/user/state.go +++ b/internal/server/user/state.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package user import ( diff --git a/internal/server/user/state_test.go b/internal/server/user/state_test.go index bb0c13e..2ba52f7 100644 --- a/internal/server/user/state_test.go +++ b/internal/server/user/state_test.go @@ -15,6 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package user import ( diff --git a/main.go b/main.go index 9a7a88e..6fd8089 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package main import ( From f43e47f4f182d1714c9057864fc5a4c6bda70c30 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Thu, 14 Nov 2019 11:57:44 +0100 Subject: [PATCH 11/27] Remove leftover script --- master.pow | 1 - 1 file changed, 1 deletion(-) delete mode 100644 master.pow diff --git a/master.pow b/master.pow deleted file mode 100644 index 451a01b..0000000 --- a/master.pow +++ /dev/null @@ -1 +0,0 @@ -go run . route add -c 'echo foo' / \ No newline at end of file From ec788f68c25aa5952e110b7092ce3c37b4fe6041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Hurtado?= Date: Thu, 14 Nov 2019 12:09:52 +0100 Subject: [PATCH 12/27] Added documentation comments to the control package --- internal/server/control/control.go | 28 ++++++++++++++++++++++------ internal/server/control/server.go | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 internal/server/control/server.go diff --git a/internal/server/control/control.go b/internal/server/control/control.go index 19d4310..091cf76 100644 --- a/internal/server/control/control.go +++ b/internal/server/control/control.go @@ -19,7 +19,6 @@ package control import ( "encoding/json" "io/ioutil" - "log" "net/http" "github.com/google/uuid" @@ -29,11 +28,8 @@ import ( "github.com/BBVA/kapow/internal/server/user" ) -// Run must start the control server in a specific address -func Run(bindAddr string) { - log.Fatal(http.ListenAndServe(bindAddr, configRouter())) -} - +// configRouter Populates the server mux with all the supported routes. The +// server exposes list, get, delete and add route endpoints. func configRouter() *mux.Router { r := mux.NewRouter() r.HandleFunc("/routes/{id}", removeRoute). @@ -47,8 +43,11 @@ func configRouter() *mux.Router { return r } +// funcRemove Method used to ask the route model module to delete a route var funcRemove func(id string) error = user.Routes.Delete +// removeRoute Handler that removes the requested route. If doesn't exists +// returns 404 and an error entity func removeRoute(res http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) id := vars["id"] @@ -56,11 +55,15 @@ func removeRoute(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusNotFound) return } + res.WriteHeader(http.StatusNoContent) } +// funcList Method used to ask the route model module for the list of routes var funcList func() []model.Route = user.Routes.List +// listRoutes Handler that retrieves a list of the existing routes. An empty +// list is returned when no routes exist func listRoutes(res http.ResponseWriter, req *http.Request) { list := funcList() @@ -70,13 +73,20 @@ func listRoutes(res http.ResponseWriter, req *http.Request) { _, _ = res.Write(listBytes) } +// funcAdd Method used to ask the route model module to append a new route var funcAdd func(model.Route) model.Route = user.Routes.Append + +// idGenerator UUID generator for new routes var idGenerator = uuid.NewUUID +// pathValidator Validates that a path complies with the gorilla mux +// requirements var pathValidator func(string) error = func(path string) error { return mux.NewRouter().NewRoute().BuildOnly().Path(path).GetError() } +// addRoute Handler that adds a new route. Makes all parameter validation and +// creates the a new is for the route func addRoute(res http.ResponseWriter, req *http.Request) { var route model.Route @@ -86,10 +96,12 @@ func addRoute(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusBadRequest) return } + if route.Method == "" { res.WriteHeader(http.StatusUnprocessableEntity) return } + if route.Pattern == "" { res.WriteHeader(http.StatusUnprocessableEntity) return @@ -106,6 +118,7 @@ func addRoute(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusInternalServerError) return } + route.ID = id.String() created := funcAdd(route) @@ -116,8 +129,11 @@ func addRoute(res http.ResponseWriter, req *http.Request) { _, _ = res.Write(createdBytes) } +// funcGet Method used to ask the route model module for the details of a route var funcGet func(string) (model.Route, error) = user.Routes.Get +// getRoute Handler that retrieves the details of a route. If the route doesn't +// exists returns 404 and an error entity func getRoute(res http.ResponseWriter, req *http.Request) { id := mux.Vars(req)["id"] if r, err := funcGet(id); err != nil { diff --git a/internal/server/control/server.go b/internal/server/control/server.go new file mode 100644 index 0000000..5afafea --- /dev/null +++ b/internal/server/control/server.go @@ -0,0 +1,27 @@ +/* + * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package control + +import ( + "log" + "net/http" +) + +// Run Starts the control server listening in bindAddr +func Run(bindAddr string) { + log.Fatal(http.ListenAndServe(bindAddr, configRouter())) +} From e6b779dfbb04ae7f6c0e527354fad0728c2c5a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Hurtado?= Date: Thu, 14 Nov 2019 13:51:16 +0100 Subject: [PATCH 13/27] Specification modified to include changes in error responses --- spec/README.md | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/spec/README.md b/spec/README.md index 8cb1403..ba95891 100644 --- a/spec/README.md +++ b/spec/README.md @@ -135,10 +135,10 @@ whole lifetime of the server. * The HTTP status code (e.g., `400`, which is a bad request). The target audience of this information is the client code. The client can thus use this information to control the program flow. - * The HTTP reason phrase. The target audience in this case is the human - operating the client. The human can use this information to make a - decision on how to proceed. - * The body is optional + * The body is optional depending on the request method and the status code. For + error responses (4xx and 5xx) a json body is included with a reason phrase. + The target audience in this case is the human operating the client. The + human can use this information to make a decision on how to proceed. * All successful API calls will return a representation of the *final* state attained by the objects which have been addressed (either requested, set or deleted). @@ -168,6 +168,15 @@ Content-Length: 189 ] ``` +While an error response may look like this: +```http +404 Not Found +Content-Type: application/json +Content-Length: 25 + +{"reason": "Not Found"} +``` + ## API Elements @@ -247,8 +256,8 @@ A new id is created for the appended route so it can be referenced later. } ``` * **Error Responses**: - * **Code**: `400 Malformed JSON` - * **Code**: `422 Invalid Route` + * **Code**: `400`; **Reason**: `Malformed JSON` + * **Code**: `422`; **Reason**: `Invalid Route` * **Sample Call**:
```sh $ curl -X POST --data-binary @- $KAPOW_URL/routes < ```sh $ curl -X PUT --data-binary @- $KAPOW_URL/routes < ```sh $ curl -X DELETE $KAPOW_URL/routes/ROUTE_1f186c92_f906_4506_9788_a1f541b11d0f @@ -362,7 +371,7 @@ Retrieves the information about the route identified by `{id}`. } ``` * **Error Responses**: - * **Code**: `404 Not Found` + * **Code**: `404`; Reason: `Route Not Found` * **Sample Call**:
```sh $ curl -X GET $KAPOW_URL/routes/ROUTE_1f186c92_f906_4506_9788_a1f541b11d0f @@ -390,8 +399,10 @@ response. operating the client. The human can use this information to make a decision on how to proceed. * Regarding HTTP request and response bodies: - * The response body will be empty in case of error. - * It will transport binary data in other case. + * In case of error the response body will be a json entity containing a reason + phrase. The target audience in this case is the human operating the client. + The human can use this information to make a decision on how to proceed. + * It will transport binary data in any other case. * When several error conditions can happen at the same time, the order of the checks is implementation-defined. @@ -518,11 +529,11 @@ path doesn't exist or is invalid. **Header**: `Content-Type: application/octet-stream`
**Content**: The value of the resource. Note that it may be empty. * **Error Responses**: - * **Code**: `400 Invalid Resource Path`
+ * **Code**: `400`; Reason: `Invalid Resource Path`
**Notes**: Check the list of valid resource paths at the top of this section. - * **Code**: `404 Handler ID Not Found`
+ * **Code**: `404`; Reason: `Handler ID Not Found`
**Notes**: Refers to the handler resource itself. - * **Code**: `404 Resource Item Not Found`
+ * **Code**: `404`; Reason: `Resource Item Not Found`
**Notes**: Refers to the named item in the corresponding data API resource. * **Sample Call**:
```sh @@ -541,9 +552,9 @@ path doesn't exist or is invalid. * **Success Responses**: * **Code**: `200 OK` * **Error Responses**: - * **Code**: `400 Invalid Resource Path`
+ * **Code**: `400`; Reason: `Invalid Resource Path`
**Notes**: Check the list of valid resource paths at the top of this section. - * **Code**: `404 Handler ID Not Found`
+ * **Code**: `404`; Reason: `Handler ID Not Found`
**Notes**: Refers to the handler resource itself. * **Sample Call**:
```sh From 3e2bc44c0b705b628334d5e42511bd39d053b899 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Thu, 14 Nov 2019 17:50:35 +0100 Subject: [PATCH 14/27] spec/Makefile: on fix target, skip tagged scenarios and features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roberto Abdelkader Martínez Pérez --- spec/test/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/test/Makefile b/spec/test/Makefile index 571fcb8..62ca762 100644 --- a/spec/test/Makefile +++ b/spec/test/Makefile @@ -11,6 +11,6 @@ wip: test: lint pipenv run behave --no-capture --tags=~@skip fix: lint - KAPOW_DEBUG_TESTS=1 pipenv run behave --stop --no-capture + KAPOW_DEBUG_TESTS=1 pipenv run behave --stop --no-capture --tags=~@skip catalog: pipenv run behave --format steps.usage --dry-run --no-summary -q From 9ebc989bc7e8930e2daf8780ce76bfe3db79c4a1 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Thu, 14 Nov 2019 17:53:32 +0100 Subject: [PATCH 15/27] poc/Makefile: add 'fix' target (mirroring spec/Makefile) --- poc/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/poc/Makefile b/poc/Makefile index 5853f17..9c5a4ff 100644 --- a/poc/Makefile +++ b/poc/Makefile @@ -7,3 +7,6 @@ sync: test: sync KAPOW_DATAAPI_URL=http://localhost:8081 pipenv run make -C ../spec/test + +fix: + KAPOW_DATAAPI_URL=http://localhost:8081 pipenv run make -C ../spec/test fix From ee9bb8df9ce1536c912e15421a4f4ec726874c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Hurtado?= Date: Thu, 14 Nov 2019 15:05:19 +0100 Subject: [PATCH 16/27] New error handling in poc and features --- poc/bin/kapow | 25 +++++++++++-------- .../control/append/error_malformed.feature | 8 +++++- .../append/error_unprocessable.feature | 16 ++++++++++-- .../control/delete/error_notfound.feature | 8 +++++- .../control/get/error_notfound.feature | 8 +++++- .../control/insert/error_malformed.feature | 8 +++++- .../insert/error_unprocessable.feature | 8 +++++- .../handler/error_handleridnotfound.feature | 8 +++++- .../handler/error_invalidresource.feature | 8 +++++- .../data/handler/error_itemnotfound.feature | 8 +++++- spec/test/features/steps/steps.py | 10 ++++++++ 11 files changed, 95 insertions(+), 20 deletions(-) diff --git a/poc/bin/kapow b/poc/bin/kapow index e9d6e66..cccb574 100755 --- a/poc/bin/kapow +++ b/poc/bin/kapow @@ -178,14 +178,14 @@ async def get_field(request): try: connection = CONNECTIONS[id] except KeyError: - response = web.Response(status=404, reason="Handler ID Not Found") + response = web.json_response(data=error_body("Handler ID Not Found"), status=404, reason="Not Found") else: try: content = await connection.get(field) except ValueError: - return web.Response(status=400, reason="Invalid Resource Path") + return web.json_response(data=error_body("Invalid Resource Path"), status=400, reason="Bad Request") except KeyError: - return web.Response(status=404, reason="Resource Item Not Found") + return web.json_response(data=error_body("Resource Item Not Found"), status=404, reason="Not Found") if isinstance(content, StreamReader): response = web.StreamResponse(status=200, reason="OK") @@ -210,8 +210,10 @@ async def set_field(request): try: connection = CONNECTIONS[id] + except ValueError: + return web.json_response(data=error_body("Invalid Resource Path"), status=400, reason="Bad Request") except KeyError: - response = web.Response(status=404, reason="Handler ID Not Found") + response = web.json_response(data=error_body("Handler ID Not Found"), status=404, reason="Not Found") else: try: await connection.set(field, request.content) @@ -275,6 +277,9 @@ def handle_route(entrypoint, command): ######################################################################## +def error_body(reason): + return {"reason": reason} + def get_routes(app): async def _get_routes(request): """Return the list of registered routes.""" @@ -302,7 +307,7 @@ def get_route(app): "entrypoint": r.entrypoint, "command": r.command}) else: - return web.Response(status=404, reason="Not Found") + return web.json_response(data=error_body("Route Not Found"), status=404, reason="Not Found") return _get_route @@ -312,7 +317,7 @@ def insert_route(app): try: content = await request.json() except ValueError: - return web.Response(status=400, reason="Malformed JSON") + return web.json_response(data=error_body("Malformed JSON"), status=400, reason="Bad Request") try: index = int(content["index"]) @@ -330,7 +335,7 @@ def insert_route(app): + [route] + app["user_routes"][index:])) except (InvalidRouteError, KeyError, AssertionError, ValueError) as exc: - return web.Response(status=422, reason="Invalid Route") + return web.json_response(data=error_body("Invalid Route"), status=422, reason="Unprocessable Entity") else: app["user_routes"].insert(index, route) return web.json_response({"id": route.id, @@ -348,7 +353,7 @@ def append_route(app): try: content = await request.json() except ValueError as exc: - return web.Response(status=400, reason="Malformed JSON") + return web.json_response(data=error_body("Malformed JSON"), status=400, reason="Bad Request") try: method = content.get("method", "GET") @@ -362,7 +367,7 @@ def append_route(app): handler=handle_route(entrypoint, command)) app.change_routes(app["user_routes"] + [route]) except (InvalidRouteError, KeyError) as exc: - return web.Response(status=422, reason="Invalid Route") + return web.json_response(data=error_body("Invalid Route"), status=422, reason="Unprocessable Entity") else: app["user_routes"].append(route) return web.json_response({"id": route.id, @@ -381,7 +386,7 @@ def delete_route(app): id = request.match_info["id"] routes = [r for r in app["user_routes"] if r.id != id] if len(routes) == len(app["user_routes"]): - return web.Response(status=404, reason="Not Found") + return web.json_response(data=error_body("Route Not Found"), status=404, reason="Not Found") else: app.change_routes(routes) app["user_routes"] = routes diff --git a/spec/test/features/control/append/error_malformed.feature b/spec/test/features/control/append/error_malformed.feature index e64414d..493375c 100644 --- a/spec/test/features/control/append/error_malformed.feature +++ b/spec/test/features/control/append/error_malformed.feature @@ -28,4 +28,10 @@ Feature: Kapow! server reject append requests with malformed JSON bodies. Hi! I am an invalid JSON document. """ Then I get 400 as response code - And I get "Malformed JSON" as response reason phrase + And the response header "Content-Type" contains "application/json" + And I get the following response body: + """ + { + "reason": "Malformed JSON" + } + """ diff --git a/spec/test/features/control/append/error_unprocessable.feature b/spec/test/features/control/append/error_unprocessable.feature index 1a21b46..06c2a0d 100644 --- a/spec/test/features/control/append/error_unprocessable.feature +++ b/spec/test/features/control/append/error_unprocessable.feature @@ -31,7 +31,13 @@ Feature: Kapow! server rejects requests with semantic errors. } """ Then I get 422 as response code - And I get "Invalid Route" as response reason phrase + And the response header "Content-Type" contains "application/json" + And I get the following response body: + """ + { + "reason": "Invalid Route" + } + """ Scenario: Error because bad route format. If a request contains an invalid expression in the @@ -48,4 +54,10 @@ Feature: Kapow! server rejects requests with semantic errors. } """ Then I get 422 as response code - And I get "Invalid Route" as response reason phrase + And the response header "Content-Type" contains "application/json" + And I get the following response body: + """ + { + "reason": "Invalid Route" + } + """ diff --git a/spec/test/features/control/delete/error_notfound.feature b/spec/test/features/control/delete/error_notfound.feature index d70292a..18d2a90 100644 --- a/spec/test/features/control/delete/error_notfound.feature +++ b/spec/test/features/control/delete/error_notfound.feature @@ -24,4 +24,10 @@ Feature: Fail to delete a route in Kapow! server. Given I have a just started Kapow! server 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 the response header "Content-Type" contains "application/json" + And I get the following response body: + """ + { + "reason": "Route Not Found" + } + """ diff --git a/spec/test/features/control/get/error_notfound.feature b/spec/test/features/control/get/error_notfound.feature index 69e1e31..dfdb951 100644 --- a/spec/test/features/control/get/error_notfound.feature +++ b/spec/test/features/control/get/error_notfound.feature @@ -24,4 +24,10 @@ Feature: Fail to retrieve route details in Kapow! server. Given I have a just started Kapow! server When I get 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 the response header "Content-Type" contains "application/json" + And I get the following response body: + """ + { + "reason": "Route Not Found" + } + """ diff --git a/spec/test/features/control/insert/error_malformed.feature b/spec/test/features/control/insert/error_malformed.feature index d2453db..20596bc 100644 --- a/spec/test/features/control/insert/error_malformed.feature +++ b/spec/test/features/control/insert/error_malformed.feature @@ -37,4 +37,10 @@ Feature: Kapow! server rejects insertion requests with malformed JSON bodies. } """ Then I get 400 as response code - And I get "Malformed JSON" as response reason phrase + And the response header "Content-Type" contains "application/json" + And I get the following response body: + """ + { + "reason": "Malformed JSON" + } + """ diff --git a/spec/test/features/control/insert/error_unprocessable.feature b/spec/test/features/control/insert/error_unprocessable.feature index 04bc4a0..18468da 100644 --- a/spec/test/features/control/insert/error_unprocessable.feature +++ b/spec/test/features/control/insert/error_unprocessable.feature @@ -67,4 +67,10 @@ Feature: Kapow! server rejects insertion requests with semantic errors. } """ Then I get 422 as response code - And I get "Invalid Route" as response reason phrase + And the response header "Content-Type" contains "application/json" + And I get the following response body: + """ + { + "reason": "Invalid Route" + } + """ diff --git a/spec/test/features/data/handler/error_handleridnotfound.feature b/spec/test/features/data/handler/error_handleridnotfound.feature index 34b13f2..464891e 100644 --- a/spec/test/features/data/handler/error_handleridnotfound.feature +++ b/spec/test/features/data/handler/error_handleridnotfound.feature @@ -25,4 +25,10 @@ Feature: Fail to retrieve resources from nonexistent handler in Kapow! server. Given I have a running Kapow! server When I get the resource "/request/path" for the handler with id "XXXXXXXXXX" Then I get 404 as response code - And I get "Handler ID Not Found" as response reason phrase + And the response header "Content-Type" contains "application/json" + And I get the following response body: + """ + { + "reason": "Handler ID Not Found" + } + """ diff --git a/spec/test/features/data/handler/error_invalidresource.feature b/spec/test/features/data/handler/error_invalidresource.feature index 73dcc94..fc911f1 100644 --- a/spec/test/features/data/handler/error_invalidresource.feature +++ b/spec/test/features/data/handler/error_invalidresource.feature @@ -27,4 +27,10 @@ Feature: Fail to retrieve an invalid resource for a handler in Kapow! server. When I send a request to the testing route "/foo" And I get the resource "/invented/path" Then I get 400 as response code - And I get "Invalid Resource Path" as response reason phrase + And the response header "Content-Type" contains "application/json" + And I get the following response body: + """ + { + "reason": "Invalid Resource Path" + } + """ diff --git a/spec/test/features/data/handler/error_itemnotfound.feature b/spec/test/features/data/handler/error_itemnotfound.feature index 887446a..16dd246 100644 --- a/spec/test/features/data/handler/error_itemnotfound.feature +++ b/spec/test/features/data/handler/error_itemnotfound.feature @@ -28,4 +28,10 @@ Feature: Fail to retrieve nonexistent resource items in Kapow! server. When I send a request to the testing route "/foo" And I get the resource "/request/params/meloinvento" Then I get 404 as response code - And I get "Resource Item Not Found" as response reason phrase + And the response header "Content-Type" contains "application/json" + And I get the following response body: + """ + { + "reason": "Resource Item Not Found" + } + """ diff --git a/spec/test/features/steps/steps.py b/spec/test/features/steps/steps.py index 6e04527..39d987a 100644 --- a/spec/test/features/steps/steps.py +++ b/spec/test/features/steps/steps.py @@ -185,6 +185,16 @@ def step_impl(context, code): assert context.testing_response.status_code == int(code), f"Got {context.testing_response.status_code} instead" +@then('the response header {header_name} contains {value}') +def step_impl(context, header_name, value): + assert context.response.headers.get(header_name) == value, f"Got {context.response.headers.get(header_name)} instead" + + +@then('the testing response header {header_name} contains {value}') +def step_impl(context, header_name, value): + assert context.testing_response.headers.get(header_name) == value, f"Got {context.testing_response.headers.get(header_name)} instead" + + @then('I get "{reason}" as response reason phrase') def step_impl(context, reason): assert context.response.reason == reason, f"Got {context.response.reason} instead" From 03de51e6ca4370e3ed4195d066306ff2f12b0c2e Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Thu, 14 Nov 2019 17:13:34 +0100 Subject: [PATCH 17/27] Fix missing quotes in step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: César Gallego Rodríguez Co-Authored-By: Roberto Abdelkader Martínez Pérez --- spec/test/features/steps/steps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/test/features/steps/steps.py b/spec/test/features/steps/steps.py index 39d987a..3c968ad 100644 --- a/spec/test/features/steps/steps.py +++ b/spec/test/features/steps/steps.py @@ -185,9 +185,9 @@ def step_impl(context, code): assert context.testing_response.status_code == int(code), f"Got {context.testing_response.status_code} instead" -@then('the response header {header_name} contains {value}') +@then('the response header "{header_name}" contains "{value}"') def step_impl(context, header_name, value): - assert context.response.headers.get(header_name) == value, f"Got {context.response.headers.get(header_name)} instead" + assert context.response.headers.get(header_name).split(';')[0] == value, f"Got {context.response.headers.get(header_name)} instead" @then('the testing response header {header_name} contains {value}') From 3af5def406b13bf93f0837fd076199adca978338 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Thu, 14 Nov 2019 17:40:47 +0100 Subject: [PATCH 18/27] poc: make error_body(reason) return a JSON with extraneous fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We then ensure that the cucumber steps only test the presence of the required fields, not just comparing it byte-wise. Co-authored-by: Roberto Abdelkader Martínez Pérez --- poc/bin/kapow | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poc/bin/kapow b/poc/bin/kapow index cccb574..5c371fb 100755 --- a/poc/bin/kapow +++ b/poc/bin/kapow @@ -278,7 +278,7 @@ def handle_route(entrypoint, command): def error_body(reason): - return {"reason": reason} + return {"reason": reason, "foo": "bar"} def get_routes(app): async def _get_routes(request): From 402bbf00d7ffc954c3b4cb9b89f2ab5bd8e0a995 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Fri, 15 Nov 2019 17:11:30 +0100 Subject: [PATCH 19/27] Fix style in comment --- internal/server/control/control.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/server/control/control.go b/internal/server/control/control.go index 091cf76..fe0a21b 100644 --- a/internal/server/control/control.go +++ b/internal/server/control/control.go @@ -46,7 +46,7 @@ func configRouter() *mux.Router { // funcRemove Method used to ask the route model module to delete a route var funcRemove func(id string) error = user.Routes.Delete -// removeRoute Handler that removes the requested route. If doesn't exists +// removeRoute Handler that removes the requested route. If it doesn't exist, // returns 404 and an error entity func removeRoute(res http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) From 2c32daedcbe7c75a84e132dfa7f00444c0c27019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Mon, 18 Nov 2019 10:31:00 +0100 Subject: [PATCH 20/27] Default value in case there is no header --- spec/test/features/steps/steps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/test/features/steps/steps.py b/spec/test/features/steps/steps.py index 3c968ad..4fc6ffb 100644 --- a/spec/test/features/steps/steps.py +++ b/spec/test/features/steps/steps.py @@ -187,7 +187,7 @@ def step_impl(context, code): @then('the response header "{header_name}" contains "{value}"') def step_impl(context, header_name, value): - assert context.response.headers.get(header_name).split(';')[0] == value, f"Got {context.response.headers.get(header_name)} instead" + assert context.response.headers.get(header_name, "").split(';')[0] == value, f"Got {context.response.headers.get(header_name)} instead" @then('the testing response header {header_name} contains {value}') From edff842c3b5aeec6f5e6b37292938d4e9d50fd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Hurtado?= Date: Mon, 18 Nov 2019 14:32:41 +0100 Subject: [PATCH 21/27] New srverrors package added. New error handling added to control package --- internal/server/control/control.go | 17 ++--- internal/server/control/control_test.go | 87 +++++++++++++++---------- internal/server/srverrors/error.go | 19 ++++++ internal/server/srverrors/error_test.go | 47 +++++++++++++ 4 files changed, 128 insertions(+), 42 deletions(-) create mode 100644 internal/server/srverrors/error.go create mode 100644 internal/server/srverrors/error_test.go diff --git a/internal/server/control/control.go b/internal/server/control/control.go index fe0a21b..d29d790 100644 --- a/internal/server/control/control.go +++ b/internal/server/control/control.go @@ -25,6 +25,7 @@ import ( "github.com/gorilla/mux" "github.com/BBVA/kapow/internal/server/model" + "github.com/BBVA/kapow/internal/server/srverrors" "github.com/BBVA/kapow/internal/server/user" ) @@ -52,7 +53,7 @@ func removeRoute(res http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) id := vars["id"] if err := funcRemove(id); err != nil { - res.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, "Route Not Found", res) return } @@ -93,29 +94,29 @@ func addRoute(res http.ResponseWriter, req *http.Request) { payload, _ := ioutil.ReadAll(req.Body) err := json.Unmarshal(payload, &route) if err != nil { - res.WriteHeader(http.StatusBadRequest) + srverrors.WriteErrorResponse(http.StatusBadRequest, "Malformed JSON", res) return } if route.Method == "" { - res.WriteHeader(http.StatusUnprocessableEntity) + srverrors.WriteErrorResponse(http.StatusUnprocessableEntity, "Invalid Route", res) return } if route.Pattern == "" { - res.WriteHeader(http.StatusUnprocessableEntity) + srverrors.WriteErrorResponse(http.StatusUnprocessableEntity, "Invalid Route", res) return } err = pathValidator(route.Pattern) if err != nil { - res.WriteHeader(http.StatusUnprocessableEntity) + srverrors.WriteErrorResponse(http.StatusUnprocessableEntity, "Invalid Route", res) return } id, err := idGenerator() if err != nil { - res.WriteHeader(http.StatusInternalServerError) + srverrors.WriteErrorResponse(http.StatusInternalServerError, "Internal Server Error", res) return } @@ -124,8 +125,8 @@ func addRoute(res http.ResponseWriter, req *http.Request) { created := funcAdd(route) createdBytes, _ := json.Marshal(created) - res.WriteHeader(http.StatusCreated) res.Header().Set("Content-Type", "application/json") + res.WriteHeader(http.StatusCreated) _, _ = res.Write(createdBytes) } @@ -137,7 +138,7 @@ var funcGet func(string) (model.Route, error) = user.Routes.Get func getRoute(res http.ResponseWriter, req *http.Request) { id := mux.Vars(req)["id"] if r, err := funcGet(id); err != nil { - res.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, "Route Not Found", res) } else { res.Header().Set("Content-Type", "application/json") rBytes, _ := json.Marshal(r) diff --git a/internal/server/control/control_test.go b/internal/server/control/control_test.go index 975039e..c40b70b 100644 --- a/internal/server/control/control_test.go +++ b/internal/server/control/control_test.go @@ -19,6 +19,7 @@ package control import ( "encoding/json" "errors" + "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -30,9 +31,33 @@ import ( "github.com/gorilla/mux" "github.com/BBVA/kapow/internal/server/model" + "github.com/BBVA/kapow/internal/server/srverrors" "github.com/BBVA/kapow/internal/server/user" ) +func checkErrorResponse(r *http.Response, expectedErrcode int, expectedReason string) []error { + errList := make([]error, 0) + + if r.StatusCode != expectedErrcode { + errList = append(errList, fmt.Errorf("HTTP status mismatch. Expected: %d, got: %d", expectedErrcode, r.StatusCode)) + } + + if v := r.Header.Get("Content-Type"); v != "application/json; charset=utf-8" { + errList = append(errList, fmt.Errorf("Content-Type header mismatch. Expected: %q, got: %q", "application/json; charset=utf-8", v)) + } + + errMsg := srverrors.ServerErrMessage{} + if bodyBytes, err := ioutil.ReadAll(r.Body); err != nil { + errList = append(errList, fmt.Errorf("Unexpected error reading response body: %v", err)) + } else if err := json.Unmarshal(bodyBytes, &errMsg); err != nil { + errList = append(errList, fmt.Errorf("Response body contains invalid JSON entity: %v", err)) + } else if errMsg.Reason != expectedReason { + errList = append(errList, fmt.Errorf("Unexpected reason in response. Expected: %q, got: %q", expectedReason, errMsg.Reason)) + } + + return errList +} + func TestConfigRouterHasRoutesWellConfigured(t *testing.T) { testCases := []struct { pattern, method string @@ -101,17 +126,15 @@ func TestAddRouteReturnsBadRequestWhenMalformedJSONBody(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/routes", strings.NewReader(reqPayload)) resp := httptest.NewRecorder() - handler := http.HandlerFunc(addRoute) - handler.ServeHTTP(resp, req) - if resp.Code != http.StatusBadRequest { - t.Errorf("HTTP status mismatch. Expected: %d, got: %d", http.StatusBadRequest, resp.Code) + addRoute(resp, req) + + for _, e := range checkErrorResponse(resp.Result(), http.StatusBadRequest, "Malformed JSON") { + t.Error(e.Error()) } - } func TestAddRouteReturns422ErrorWhenMandatoryFieldsMissing(t *testing.T) { - handler := http.HandlerFunc(addRoute) tc := []struct { payload, testCase string testMustFail bool @@ -167,18 +190,19 @@ func TestAddRouteReturns422ErrorWhenMandatoryFieldsMissing(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/routes", strings.NewReader(test.payload)) resp := httptest.NewRecorder() - handler.ServeHTTP(resp, req) + addRoute(resp, req) + r := resp.Result() if test.testMustFail { - if resp.Code != http.StatusUnprocessableEntity { - t.Errorf("HTTP status mismatch in case %s. Expected: %d, got: %d", test.testCase, http.StatusUnprocessableEntity, resp.Code) + for _, e := range checkErrorResponse(r, http.StatusUnprocessableEntity, "Invalid Route") { + t.Error(e.Error()) } } else if !test.testMustFail { - if resp.Code != http.StatusCreated { - t.Errorf("HTTP status mismatch in case %s. Expected: %d, got: %d", test.testCase, http.StatusUnprocessableEntity, resp.Code) + if r.StatusCode != http.StatusCreated { + t.Errorf("HTTP status mismatch in case %s. Expected: %d, got: %d", test.testCase, http.StatusUnprocessableEntity, r.StatusCode) } - if ct := resp.Header().Get("Content-Type"); ct != "application/json" { - t.Errorf("Incorrect content type in response. Expected: application/json, got: %s", ct) + if ct := r.Header.Get("Content-Type"); ct != "application/json" { + t.Errorf("Incorrect content type in response. Expected: application/json, got: %q", ct) } } } @@ -193,7 +217,6 @@ func TestAddRouteGeneratesRouteID(t *testing.T) { }` req := httptest.NewRequest(http.MethodPost, "/routes", strings.NewReader(reqPayload)) resp := httptest.NewRecorder() - handler := http.HandlerFunc(addRoute) var genID string funcAdd = func(input model.Route) model.Route { genID = input.ID @@ -204,7 +227,7 @@ func TestAddRouteGeneratesRouteID(t *testing.T) { defer func() { pathValidator = origPathValidator }() pathValidator = func(path string) error { return nil } - handler.ServeHTTP(resp, req) + addRoute(resp, req) if _, err := uuid.Parse(genID); err != nil { t.Error("ID not generated properly") @@ -220,7 +243,6 @@ func TestAddRoute500sWhenIDGeneratorFails(t *testing.T) { }` req := httptest.NewRequest(http.MethodPost, "/routes", strings.NewReader(reqPayload)) resp := httptest.NewRecorder() - handler := http.HandlerFunc(addRoute) origPathValidator := pathValidator defer func() { pathValidator = origPathValidator }() @@ -230,14 +252,13 @@ func TestAddRoute500sWhenIDGeneratorFails(t *testing.T) { defer func() { idGenerator = idGenOrig }() idGenerator = func() (uuid.UUID, error) { var uuid uuid.UUID - return uuid, errors.New( - "End of Time reached; Try again before, or in the next Big Bang cycle") + return uuid, errors.New("End of Time reached; Try again before, or in the next Big Bang cycle") } - handler.ServeHTTP(resp, req) + addRoute(resp, req) - if resp.Result().StatusCode != http.StatusInternalServerError { - t.Errorf("HTTP status mismatch. Expected: %d, got: %d", http.StatusInternalServerError, resp.Result().StatusCode) + for _, e := range checkErrorResponse(resp.Result(), http.StatusInternalServerError, "Internal Server Error") { + t.Error(e.Error()) } } @@ -251,7 +272,6 @@ func TestAddRouteReturnsCreated(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/routes", strings.NewReader(reqPayload)) resp := httptest.NewRecorder() - handler := http.HandlerFunc(addRoute) var genID string funcAdd = func(input model.Route) model.Route { expected := model.Route{ID: input.ID, Method: "GET", Pattern: "/hello", Entrypoint: "/bin/sh -c", Command: "echo Hello World | kapow set /response/body"} @@ -267,7 +287,7 @@ func TestAddRouteReturnsCreated(t *testing.T) { defer func() { pathValidator = origPathValidator }() pathValidator = func(path string) error { return nil } - handler.ServeHTTP(resp, req) + addRoute(resp, req) if resp.Code != http.StatusCreated { t.Errorf("HTTP status mismatch. Expected: %d, got: %d", http.StatusCreated, resp.Code) @@ -297,15 +317,14 @@ func TestAddRoute422sWhenInvalidRoute(t *testing.T) { }` req := httptest.NewRequest(http.MethodPost, "/routes", strings.NewReader(reqPayload)) resp := httptest.NewRecorder() - handler := http.HandlerFunc(addRoute) origPathValidator := pathValidator defer func() { pathValidator = origPathValidator }() pathValidator = func(path string) error { return errors.New("Invalid route") } - handler.ServeHTTP(resp, req) + addRoute(resp, req) - if resp.Code != http.StatusUnprocessableEntity { - t.Error("Invalid route registered") + for _, e := range checkErrorResponse(resp.Result(), http.StatusUnprocessableEntity, "Invalid Route") { + t.Error(e.Error()) } } @@ -315,7 +334,6 @@ func TestRemoveRouteReturnsNotFound(t *testing.T) { handler := mux.NewRouter() handler.HandleFunc("/routes/{id}", removeRoute). Methods("DELETE") - funcRemove = func(id string) error { if id == "ROUTE_XXXXXXXXXXXXXXXXXX" { return errors.New(id) @@ -325,8 +343,9 @@ func TestRemoveRouteReturnsNotFound(t *testing.T) { } handler.ServeHTTP(resp, req) - if resp.Code != http.StatusNotFound { - t.Errorf("HTTP status mismatch. Expected: %d, got: %d", http.StatusNotFound, resp.Code) + + for _, e := range checkErrorResponse(resp.Result(), http.StatusNotFound, "Route Not Found") { + t.Error(e.Error()) } } @@ -345,6 +364,7 @@ func TestRemoveRouteReturnsNoContent(t *testing.T) { } handler.ServeHTTP(resp, req) + if resp.Code != http.StatusNoContent { t.Errorf("HTTP status mismatch. Expected: %d, got: %d", http.StatusNoContent, resp.Code) } @@ -415,13 +435,12 @@ func TestGetRouteReturns404sWhenRouteDoesntExist(t *testing.T) { handler.ServeHTTP(w, r) - resp := w.Result() - if resp.StatusCode != http.StatusNotFound { - t.Errorf("HTTP status mismatch. Expected: %d, got: %d", http.StatusNotFound, resp.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Route Not Found") { + t.Error(e.Error()) } } -func TestGetRouteSetsCorrctContentType(t *testing.T) { +func TestGetRouteSetsCorrectContentType(t *testing.T) { handler := mux.NewRouter() handler.HandleFunc("/routes/{id}", getRoute). Methods("GET") diff --git a/internal/server/srverrors/error.go b/internal/server/srverrors/error.go new file mode 100644 index 0000000..cc40f54 --- /dev/null +++ b/internal/server/srverrors/error.go @@ -0,0 +1,19 @@ +package srverrors + +import ( + "encoding/json" + "net/http" +) + +type ServerErrMessage struct { + Reason string +} + +func WriteErrorResponse(statusCode int, reasonMsg string, res http.ResponseWriter) { + respBody := ServerErrMessage{} + respBody.Reason = reasonMsg + bb, _ := json.Marshal(respBody) + res.Header().Add("Content-Type", "application/json; charset=utf-8") + res.WriteHeader(statusCode) + _, _ = res.Write(bb) +} diff --git a/internal/server/srverrors/error_test.go b/internal/server/srverrors/error_test.go new file mode 100644 index 0000000..adda7b5 --- /dev/null +++ b/internal/server/srverrors/error_test.go @@ -0,0 +1,47 @@ +package srverrors_test + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/BBVA/kapow/internal/server/srverrors" +) + +func TestWriteErrorResponseSetsAppJsonContentType(t *testing.T) { + w := httptest.NewRecorder() + + srverrors.WriteErrorResponse(0, "Not Important Here", w) + + if v := w.Result().Header.Get("Content-Type"); v != "application/json; charset=utf-8" { + t.Errorf("Content-Type header mismatch. Expected: %q, got: %q", "application/json; charset=utf-8", v) + } +} + +func TestWriteErrorResponseSetsRequestedStatusCode(t *testing.T) { + w := httptest.NewRecorder() + + srverrors.WriteErrorResponse(http.StatusGone, "Not Important Here", w) + + if v := w.Result().StatusCode; v != http.StatusGone { + t.Errorf("Status code mismatch. Expected: %d, got: %d", http.StatusGone, v) + } +} + +func TestWriteErrorResponseSetsBodyCorrectly(t *testing.T) { + expectedReason := "Something Not Found" + w := httptest.NewRecorder() + + srverrors.WriteErrorResponse(http.StatusNotFound, expectedReason, w) + + errMsg := srverrors.ServerErrMessage{} + if bodyBytes, err := ioutil.ReadAll(w.Result().Body); err != nil { + t.Errorf("Unexpected error reading response body: %v", err) + } else if err := json.Unmarshal(bodyBytes, &errMsg); err != nil { + t.Errorf("Response body contains invalid JSON entity: %v", err) + } else if errMsg.Reason != expectedReason { + t.Errorf("Unexpected reason in response. Expected: %q, got: %q", expectedReason, errMsg.Reason) + } +} From e4b3a4f7183bde71cab8194ee48837b59bf5e683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Hurtado?= Date: Mon, 18 Nov 2019 14:52:52 +0100 Subject: [PATCH 22/27] New error handling added to server and decorator module in data package --- internal/server/data/decorator.go | 3 ++- internal/server/data/decorator_test.go | 5 ++-- internal/server/data/server.go | 5 +++- internal/server/data/server_test.go | 32 +++++++++++++++++++++++--- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/internal/server/data/decorator.go b/internal/server/data/decorator.go index ec5909d..bd708be 100644 --- a/internal/server/data/decorator.go +++ b/internal/server/data/decorator.go @@ -20,6 +20,7 @@ import ( "net/http" "github.com/BBVA/kapow/internal/server/model" + "github.com/BBVA/kapow/internal/server/srverrors" "github.com/gorilla/mux" ) @@ -39,7 +40,7 @@ func checkHandler(fn resourceHandler) func(http.ResponseWriter, *http.Request) { if h, ok := Handlers.Get(handlerID); ok { fn(w, r, h) } else { - w.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, "Handler ID Not Found", w) } } } diff --git a/internal/server/data/decorator_test.go b/internal/server/data/decorator_test.go index dbf451a..420b7bb 100644 --- a/internal/server/data/decorator_test.go +++ b/internal/server/data/decorator_test.go @@ -127,9 +127,8 @@ func TestCheckHandlerReturnsAFunctionsThat404sWhenHandlerDoesNotExist(t *testing fn(w, r) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Errorf("Status code mismatch. Expected 404. Got %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Handler ID Not Found") { + t.Error(e.Error()) } } diff --git a/internal/server/data/server.go b/internal/server/data/server.go index 75abae8..140b984 100644 --- a/internal/server/data/server.go +++ b/internal/server/data/server.go @@ -20,6 +20,7 @@ import ( "log" "net/http" + "github.com/BBVA/kapow/internal/server/srverrors" "github.com/gorilla/mux" ) @@ -36,7 +37,9 @@ func configRouter(rs []routeSpec) (r *mux.Router) { } r.HandleFunc( "/handlers/{handlerID}/{resource:.*}", - func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) }) + func(w http.ResponseWriter, r *http.Request) { + srverrors.WriteErrorResponse(http.StatusBadRequest, "Invalid Resource Path", w) + }) return r } diff --git a/internal/server/data/server_test.go b/internal/server/data/server_test.go index e3c848b..30cadf5 100644 --- a/internal/server/data/server_test.go +++ b/internal/server/data/server_test.go @@ -17,13 +17,40 @@ package data import ( + "encoding/json" + "fmt" + "io/ioutil" "net/http" "net/http/httptest" "testing" "github.com/BBVA/kapow/internal/server/model" + "github.com/BBVA/kapow/internal/server/srverrors" ) +func checkErrorResponse(r *http.Response, expectedErrcode int, expectedReason string) []error { + errList := make([]error, 0) + + if r.StatusCode != expectedErrcode { + errList = append(errList, fmt.Errorf("HTTP status mismatch. Expected: %d, got: %d", expectedErrcode, r.StatusCode)) + } + + if v := r.Header.Get("Content-Type"); v != "application/json; charset=utf-8" { + errList = append(errList, fmt.Errorf("Content-Type header mismatch. Expected: %q, got: %q", "application/json; charset=utf-8", v)) + } + + errMsg := srverrors.ServerErrMessage{} + if bodyBytes, err := ioutil.ReadAll(r.Body); err != nil { + errList = append(errList, fmt.Errorf("Unexpected error reading response body: %v", err)) + } else if err := json.Unmarshal(bodyBytes, &errMsg); err != nil { + errList = append(errList, fmt.Errorf("Response body contains invalid JSON entity: %v", err)) + } else if errMsg.Reason != expectedReason { + errList = append(errList, fmt.Errorf("Unexpected reason in response. Expected: %q, got: %q", expectedReason, errMsg.Reason)) + } + + return errList +} + func TestConfigRouterReturnsRouterWithDecoratedRoutes(t *testing.T) { var handlerID string rs := []routeSpec{ @@ -50,8 +77,7 @@ func TestConfigRouterReturnsRouterThat400sOnUnconfiguredResources(t *testing.T) m.ServeHTTP(w, httptest.NewRequest("GET", "/handlers/FOO/dummy", nil)) - res := w.Result() - if res.StatusCode != http.StatusBadRequest { - t.Errorf("Status code mismatch. Expected 400. Got %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusBadRequest, "Invalid Resource Path") { + t.Error(e.Error()) } } From 4c7eae8ebb843de127402512332491b5ad910d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Hurtado?= Date: Mon, 18 Nov 2019 15:01:02 +0100 Subject: [PATCH 23/27] New error handling added to getRequestBody function in data package --- internal/server/data/resource.go | 3 ++- internal/server/data/resource_test.go | 5 ++--- internal/server/srverrors/error.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/server/data/resource.go b/internal/server/data/resource.go index 8b1076d..caf3e13 100644 --- a/internal/server/data/resource.go +++ b/internal/server/data/resource.go @@ -24,6 +24,7 @@ import ( "strconv" "github.com/BBVA/kapow/internal/server/model" + "github.com/BBVA/kapow/internal/server/srverrors" "github.com/gorilla/mux" ) @@ -32,7 +33,7 @@ func getRequestBody(w http.ResponseWriter, r *http.Request, h *model.Handler) { n, err := io.Copy(w, h.Request.Body) if err != nil { if n == 0 { - w.WriteHeader(http.StatusInternalServerError) + srverrors.WriteErrorResponse(http.StatusInternalServerError, "Internal Server Error", w) } else { // Only way to abort current connection as of go 1.13 // https://github.com/golang/go/issues/16542 diff --git a/internal/server/data/resource_test.go b/internal/server/data/resource_test.go index e723f0f..73e7c32 100644 --- a/internal/server/data/resource_test.go +++ b/internal/server/data/resource_test.go @@ -121,9 +121,8 @@ func TestGetRequestBody500sWhenHandlerRequestErrors(t *testing.T) { getRequestBody(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusInternalServerError { - t.Error("status not 500") + for _, e := range checkErrorResponse(w.Result(), http.StatusInternalServerError, "Internal Server Error") { + t.Error(e.Error()) } } diff --git a/internal/server/srverrors/error.go b/internal/server/srverrors/error.go index cc40f54..83ed512 100644 --- a/internal/server/srverrors/error.go +++ b/internal/server/srverrors/error.go @@ -13,7 +13,7 @@ func WriteErrorResponse(statusCode int, reasonMsg string, res http.ResponseWrite respBody := ServerErrMessage{} respBody.Reason = reasonMsg bb, _ := json.Marshal(respBody) - res.Header().Add("Content-Type", "application/json; charset=utf-8") + res.Header().Set("Content-Type", "application/json; charset=utf-8") res.WriteHeader(statusCode) _, _ = res.Write(bb) } From 1249b234583bb71b6a74176a3ede2bb80737848d Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Mon, 18 Nov 2019 17:44:39 +0100 Subject: [PATCH 24/27] Fix case mismatch in error JSON (adding tag) --- internal/server/srverrors/error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/server/srverrors/error.go b/internal/server/srverrors/error.go index 83ed512..77c7083 100644 --- a/internal/server/srverrors/error.go +++ b/internal/server/srverrors/error.go @@ -6,7 +6,7 @@ import ( ) type ServerErrMessage struct { - Reason string + Reason string `json:"reason"` } func WriteErrorResponse(statusCode int, reasonMsg string, res http.ResponseWriter) { From 7bca5b8577f88acdbce8164d86bff9c11e9ec1a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Hurtado?= Date: Mon, 18 Nov 2019 19:09:55 +0100 Subject: [PATCH 25/27] New error handling added to module resource in package data. Minor coding improvements. --- internal/server/control/control_test.go | 12 +-- internal/server/data/decorator_test.go | 2 +- internal/server/data/resource.go | 43 +++++----- internal/server/data/resource_test.go | 104 ++++++++++-------------- internal/server/data/server_test.go | 2 +- spec/README.md | 4 + 6 files changed, 79 insertions(+), 88 deletions(-) diff --git a/internal/server/control/control_test.go b/internal/server/control/control_test.go index c40b70b..9d1738d 100644 --- a/internal/server/control/control_test.go +++ b/internal/server/control/control_test.go @@ -130,7 +130,7 @@ func TestAddRouteReturnsBadRequestWhenMalformedJSONBody(t *testing.T) { addRoute(resp, req) for _, e := range checkErrorResponse(resp.Result(), http.StatusBadRequest, "Malformed JSON") { - t.Error(e.Error()) + t.Error(e) } } @@ -194,7 +194,7 @@ func TestAddRouteReturns422ErrorWhenMandatoryFieldsMissing(t *testing.T) { r := resp.Result() if test.testMustFail { for _, e := range checkErrorResponse(r, http.StatusUnprocessableEntity, "Invalid Route") { - t.Error(e.Error()) + t.Error(e) } } else if !test.testMustFail { if r.StatusCode != http.StatusCreated { @@ -258,7 +258,7 @@ func TestAddRoute500sWhenIDGeneratorFails(t *testing.T) { addRoute(resp, req) for _, e := range checkErrorResponse(resp.Result(), http.StatusInternalServerError, "Internal Server Error") { - t.Error(e.Error()) + t.Error(e) } } @@ -324,7 +324,7 @@ func TestAddRoute422sWhenInvalidRoute(t *testing.T) { addRoute(resp, req) for _, e := range checkErrorResponse(resp.Result(), http.StatusUnprocessableEntity, "Invalid Route") { - t.Error(e.Error()) + t.Error(e) } } @@ -345,7 +345,7 @@ func TestRemoveRouteReturnsNotFound(t *testing.T) { handler.ServeHTTP(resp, req) for _, e := range checkErrorResponse(resp.Result(), http.StatusNotFound, "Route Not Found") { - t.Error(e.Error()) + t.Error(e) } } @@ -436,7 +436,7 @@ func TestGetRouteReturns404sWhenRouteDoesntExist(t *testing.T) { handler.ServeHTTP(w, r) for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Route Not Found") { - t.Error(e.Error()) + t.Error(e) } } diff --git a/internal/server/data/decorator_test.go b/internal/server/data/decorator_test.go index 420b7bb..1c75cdd 100644 --- a/internal/server/data/decorator_test.go +++ b/internal/server/data/decorator_test.go @@ -128,7 +128,7 @@ func TestCheckHandlerReturnsAFunctionsThat404sWhenHandlerDoesNotExist(t *testing fn(w, r) for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Handler ID Not Found") { - t.Error(e.Error()) + t.Error(e) } } diff --git a/internal/server/data/resource.go b/internal/server/data/resource.go index caf3e13..d55fc72 100644 --- a/internal/server/data/resource.go +++ b/internal/server/data/resource.go @@ -28,12 +28,18 @@ import ( "github.com/gorilla/mux" ) +const ( + ResourceItemNotFound = "Resource Item Not Found" + NonIntegerValue = "Non Integer Value" + InvalidStatusCode = "Invalid Status Code" +) + func getRequestBody(w http.ResponseWriter, r *http.Request, h *model.Handler) { w.Header().Add("Content-Type", "application/octet-stream") n, err := io.Copy(w, h.Request.Body) if err != nil { if n == 0 { - srverrors.WriteErrorResponse(http.StatusInternalServerError, "Internal Server Error", w) + srverrors.WriteErrorResponse(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), w) } else { // Only way to abort current connection as of go 1.13 // https://github.com/golang/go/issues/16542 @@ -65,7 +71,7 @@ func getRequestMatches(w http.ResponseWriter, r *http.Request, h *model.Handler) if value, ok := vars[name]; ok { _, _ = w.Write([]byte(value)) } else { - w.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, ResourceItemNotFound, w) } } @@ -75,7 +81,7 @@ func getRequestParams(w http.ResponseWriter, r *http.Request, h *model.Handler) if values, ok := h.Request.URL.Query()[name]; ok { _, _ = w.Write([]byte(values[0])) } else { - w.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, ResourceItemNotFound, w) } } @@ -85,7 +91,7 @@ func getRequestHeaders(w http.ResponseWriter, r *http.Request, h *model.Handler) if values, ok := h.Request.Header[textproto.CanonicalMIMEHeaderKey(name)]; ok { _, _ = w.Write([]byte(values[0])) } else { - w.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, ResourceItemNotFound, w) } } @@ -95,7 +101,7 @@ func getRequestCookies(w http.ResponseWriter, r *http.Request, h *model.Handler) if cookie, err := h.Request.Cookie(name); err == nil { _, _ = w.Write([]byte(cookie.Value)) } else { - w.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, ResourceItemNotFound, w) } } @@ -111,11 +117,11 @@ func getRequestForm(w http.ResponseWriter, r *http.Request, h *model.Handler) { // We tried to exercise this execution path but didn't know how. err := h.Request.ParseForm() if err != nil { - w.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, ResourceItemNotFound, w) } else if values, ok := h.Request.Form[name]; ok { _, _ = w.Write([]byte(values[0])) } else { - w.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, ResourceItemNotFound, w) } } @@ -126,7 +132,7 @@ func getRequestFileName(w http.ResponseWriter, r *http.Request, h *model.Handler if err == nil { _, _ = w.Write([]byte(header.Filename)) } else { - w.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, ResourceItemNotFound, w) } } @@ -137,7 +143,7 @@ func getRequestFileContent(w http.ResponseWriter, r *http.Request, h *model.Hand if err == nil { _, _ = io.Copy(w, file) } else { - w.WriteHeader(http.StatusNotFound) + srverrors.WriteErrorResponse(http.StatusNotFound, ResourceItemNotFound, w) } } @@ -146,17 +152,16 @@ func getRequestFileContent(w http.ResponseWriter, r *http.Request, h *model.Hand func setResponseStatus(w http.ResponseWriter, r *http.Request, h *model.Handler) { sb, err := ioutil.ReadAll(r.Body) if err != nil { - w.WriteHeader(http.StatusInternalServerError) + srverrors.WriteErrorResponse(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), w) return } - si, err := strconv.Atoi(string(sb)) - if http.StatusText(si) == "" { - w.WriteHeader(http.StatusBadRequest) - } else if err == nil { - h.Writer.WriteHeader(int(si)) + if si, err := strconv.Atoi(string(sb)); err != nil { + srverrors.WriteErrorResponse(http.StatusUnprocessableEntity, NonIntegerValue, w) + } else if http.StatusText(si) == "" { + srverrors.WriteErrorResponse(http.StatusBadRequest, InvalidStatusCode, w) } else { - w.WriteHeader(http.StatusBadRequest) + h.Writer.WriteHeader(int(si)) } } @@ -164,7 +169,7 @@ func setResponseHeaders(w http.ResponseWriter, r *http.Request, h *model.Handler name := mux.Vars(r)["name"] vb, err := ioutil.ReadAll(r.Body) if err != nil { - w.WriteHeader(http.StatusInternalServerError) + srverrors.WriteErrorResponse(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), w) return } @@ -180,7 +185,7 @@ func setResponseCookies(w http.ResponseWriter, r *http.Request, h *model.Handler name := mux.Vars(r)["name"] vb, err := ioutil.ReadAll(r.Body) if err != nil { - w.WriteHeader(http.StatusInternalServerError) + srverrors.WriteErrorResponse(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), w) return } @@ -193,6 +198,6 @@ func setResponseBody(w http.ResponseWriter, r *http.Request, h *model.Handler) { if n > 0 { panic("Truncated body") } - w.WriteHeader(http.StatusInternalServerError) + srverrors.WriteErrorResponse(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), w) } } diff --git a/internal/server/data/resource_test.go b/internal/server/data/resource_test.go index 73e7c32..7ed8fae 100644 --- a/internal/server/data/resource_test.go +++ b/internal/server/data/resource_test.go @@ -121,8 +121,8 @@ func TestGetRequestBody500sWhenHandlerRequestErrors(t *testing.T) { getRequestBody(w, r, &h) - for _, e := range checkErrorResponse(w.Result(), http.StatusInternalServerError, "Internal Server Error") { - t.Error(e.Error()) + for _, e := range checkErrorResponse(w.Result(), http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) { + t.Error(e) } } @@ -369,9 +369,8 @@ func TestGetRequestMatchesReturnsNotFoundWhenMatchDoesntExists(t *testing.T) { getRequestMatches(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Errorf("Status code mismatch. Expected: 404. Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Resource Item Not Found") { + t.Error(e) } } @@ -433,9 +432,8 @@ func TestGetRequestParams404sWhenParamDoesntExist(t *testing.T) { getRequestParams(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Errorf("Status code mismatch. Expected: 404. Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Resource Item Not Found") { + t.Error(e) } } @@ -478,14 +476,15 @@ func TestGetRequestHeadersSetsOctectStreamContentType(t *testing.T) { Request: httptest.NewRequest("GET", "/", nil), Writer: httptest.NewRecorder(), } + h.Request.Header.Set("bar", "BAZ") r := createMuxRequest("/handlers/HANDLERID/request/headers/{name}", "/handlers/HANDLERID/request/headers/bar", "GET", nil) w := httptest.NewRecorder() getRequestHeaders(w, r, &h) res := w.Result() - if res.Header.Get("Content-Type") != "application/octet-stream" { - t.Error("Content Type mismatch") + if v := res.Header.Get("Content-Type"); v != "application/octet-stream" { + t.Errorf("Content Type mismatch. Expected: application/octet-stream. Got: %q", v) } } @@ -567,9 +566,8 @@ func TestGetRequestHeaders404sWhenHeaderDoesntExist(t *testing.T) { getRequestHeaders(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Error("Status code mismatch") + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Resource Item Not Found") { + t.Error(e) } } @@ -652,9 +650,8 @@ func TestGetRequestCookies404sIfCookieDoesntExist(t *testing.T) { getRequestCookies(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Errorf("Status code mismatch. Expected: 404, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Resource Item Not Found") { + t.Error(e) } } @@ -754,9 +751,8 @@ func TestGetRequestForm404sWhenFieldDoesntExist(t *testing.T) { getRequestForm(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Errorf("Status code mismatch. Expected: 404, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Resource Item Not Found") { + t.Error(e) } } @@ -810,9 +806,8 @@ func TestGetRequestForm404sWhenFormDoesntExist(t *testing.T) { getRequestForm(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Errorf("Status code mismatch. Expected: 404, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Resource Item Not Found") { + t.Error(e) } } @@ -885,9 +880,8 @@ func TestGetRequestFileName404sWhenFileDoesntExist(t *testing.T) { getRequestFileName(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Errorf("Status code mismatch. Expected: 404, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Resource Item Not Found") { + t.Error(e) } } @@ -902,9 +896,8 @@ func TestGetRequestFileName404sWhenFormDoesntExist(t *testing.T) { getRequestFileName(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Errorf("Status code mismatch. Expected: 404, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Resource Item Not Found") { + t.Error(e) } } @@ -966,9 +959,8 @@ func TestGetRequestFileContent404sWhenFileDoesntExist(t *testing.T) { getRequestFileContent(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Errorf("Status code mismatch. Expected: 404, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Resource Item Not Found") { + t.Error(e) } } @@ -983,9 +975,8 @@ func TestGetRequestFileContent404sWhenFormDoesntExist(t *testing.T) { getRequestFileContent(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusNotFound { - t.Errorf("Status code mismatch. Expected: 404, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusNotFound, "Resource Item Not Found") { + t.Error(e) } } @@ -1009,9 +1000,8 @@ func TestGetRequestFileContent500sWhenHandlerRequestErrors(t *testing.T) { getRequestFileContent(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusInternalServerError { - t.Error("status not 500", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) { + t.Error(e) } } @@ -1058,9 +1048,8 @@ func TestSetResponseStatus400sWhenNonparseableStatusCode(t *testing.T) { setResponseStatus(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusBadRequest { - t.Errorf("Status code mismatch. Expected: 400, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusUnprocessableEntity, "Non Integer Value") { + t.Error(e) } } @@ -1074,9 +1063,8 @@ func TestSetResponseStatus500sWhenErrorReadingRequest(t *testing.T) { setResponseStatus(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusInternalServerError { - t.Errorf("Status code mismatch. Expected: 500, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) { + t.Error(e) } } @@ -1092,9 +1080,8 @@ func TestSetResponseStatus400sWhenStatusCodeNotSupportedByGo(t *testing.T) { setResponseStatus(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusBadRequest { - t.Errorf("Status code mismatch. Expected: 400, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusBadRequest, "Invalid Status Code") { + t.Error(e) } } @@ -1162,9 +1149,8 @@ func TestSetResponseHeaders500sWhenErrorReadingRequest(t *testing.T) { setResponseHeaders(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusInternalServerError { - t.Errorf("Status code mismatch. Expected: 500, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) { + t.Error(e) } } @@ -1181,9 +1167,8 @@ func TestSetResponseHeaders400sOnInvalidHeaderKey(t *testing.T) { setResponseHeaders(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusBadRequest { - t.Errorf("Status code mismatch. Expected: 400, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusBadRequest, "Invalid Header Name") { + t.Error(e) } } @@ -1200,9 +1185,8 @@ func TestSetResponseHeaders400sOnInvalidHeaderValue(t *testing.T) { setResponseHeaders(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusBadRequest { - t.Errorf("Status code mismatch. Expected: 400, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusBadRequest, "Invalid Header Value") { + t.Error(e) } } @@ -1250,9 +1234,8 @@ func TestSetResponseCookies500sWhenErrorReadingRequest(t *testing.T) { setResponseCookies(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusInternalServerError { - t.Errorf("Status code mismatch. Expected: 500, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) { + t.Error(e) } } @@ -1322,9 +1305,8 @@ func TestSetResponseBody500sWhenReaderFailsInFirstRead(t *testing.T) { setResponseBody(w, r, &h) - res := w.Result() - if res.StatusCode != http.StatusInternalServerError { - t.Errorf("Status code mismatch. Expected: 500, Got: %d", res.StatusCode) + for _, e := range checkErrorResponse(w.Result(), http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) { + t.Error(e) } } diff --git a/internal/server/data/server_test.go b/internal/server/data/server_test.go index 30cadf5..7481e5a 100644 --- a/internal/server/data/server_test.go +++ b/internal/server/data/server_test.go @@ -78,6 +78,6 @@ func TestConfigRouterReturnsRouterThat400sOnUnconfiguredResources(t *testing.T) m.ServeHTTP(w, httptest.NewRequest("GET", "/handlers/FOO/dummy", nil)) for _, e := range checkErrorResponse(w.Result(), http.StatusBadRequest, "Invalid Resource Path") { - t.Error(e.Error()) + t.Error(e) } } diff --git a/spec/README.md b/spec/README.md index ba95891..aadca97 100644 --- a/spec/README.md +++ b/spec/README.md @@ -554,6 +554,10 @@ path doesn't exist or is invalid. * **Error Responses**: * **Code**: `400`; Reason: `Invalid Resource Path`
**Notes**: Check the list of valid resource paths at the top of this section. + * **Code**: `422`; Reason: `Non Integer Value`
+ **Notes**: When setting the status code with a non integer value. + * **Code**: `400`; Reason: `Invalid Status Code`
+ **Notes**: When setting a non-supported status code. * **Code**: `404`; Reason: `Handler ID Not Found`
**Notes**: Refers to the handler resource itself. * **Sample Call**:
From c82cfd5bc259d7f17117f6b0a59201240d488cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Tue, 19 Nov 2019 09:30:06 +0100 Subject: [PATCH 26/27] Go Report Card badge --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 9569931..6d552e6 100644 --- a/README.rst +++ b/README.rst @@ -4,6 +4,9 @@ .. image:: https://circleci.com/gh/BBVA/kapow/tree/master.svg?style=svg :target: https://circleci.com/gh/BBVA/kapow/tree/master +.. image:: https://goreportcard.com/badge/github.com/bbva/kapow + :target: https://goreportcard.com/report/github.com/bbva/kapow + **Kapow!** If you can script it, you can HTTP it. From bc917ff166bf51dd306a2c1a29980f05776bf9a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Tue, 19 Nov 2019 11:03:42 +0100 Subject: [PATCH 27/27] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 6d552e6..b0723f7 100644 --- a/README.rst +++ b/README.rst @@ -53,7 +53,7 @@ contains: .. code-block:: sh kapow route add /backups \ - -c 'cloudx storage ls /backups | grep $(kapow get /request/params/query) | kapow set /response/body' + -c 'cloudx storage ls /backups | grep "$(kapow get /request/params/query)" | kapow set /response/body' and execute it in the remote host with the command: