Merge remote-tracking branch 'origin/master' into feature-new-doc
This commit is contained in:
+4
-2
@@ -1,6 +1,10 @@
|
||||
Welcome to *Kapow!*
|
||||
===================
|
||||
|
||||
.. image:: https://goreportcard.com/badge/github.com/bbva/kapow
|
||||
:target: https://goreportcard.com/report/github.com/bbva/kapow
|
||||
|
||||
|
||||
With *Kapow!* you can publish simple **shell scripts** as **HTTP services** easily.
|
||||
|
||||
*Kapow!* with an example
|
||||
@@ -38,5 +42,3 @@ This is the only line you'll need:
|
||||
|
||||
Mention license and contributing
|
||||
|
||||
|
||||
|
||||
|
||||
-226
@@ -1,226 +0,0 @@
|
||||
# Installing Kapow!
|
||||
|
||||
Kapow! has a reference implementation in Go that is under active development right
|
||||
now. If you want to start using Kapow! you can:
|
||||
* Download a binary (linux, at this moment) from our
|
||||
[releases](https://github.com/BBVA/kapow/releases) section
|
||||
* Install the package with the `get` command (you need the Go runtime installed
|
||||
and [configured](https://golang.org/cmd/go/))
|
||||
```sh
|
||||
go get -u github.com/BBVA/kapow
|
||||
```
|
||||
|
||||
|
||||
# Examples
|
||||
|
||||
Below are some examples on how to define and invoke routes in Kapow!
|
||||
|
||||
As you will see `kapow` binary is both a server and a CLI that you can use to configure
|
||||
a running server. The server exposes an [API](/spec#http-control-api) that you
|
||||
can use directly if you want.
|
||||
|
||||
In order to get information from the request that fired the script execution and
|
||||
to help you compose the response, the server exposes
|
||||
some [resources](/spec#handlers) to interact with from the script.
|
||||
|
||||
|
||||
## The mandatory Hello World (for WWW fans)
|
||||
|
||||
First, you create a pow file named `greet.pow` with the following contents:
|
||||
```sh
|
||||
kapow route add /greet -c 'name=$(kapow get /request/params/name); echo Hello ${name:-World} | kapow set /response/body'
|
||||
```
|
||||
|
||||
note that you have to escape it as the command will run on a shell itself. Then, you
|
||||
execute:
|
||||
```sh
|
||||
kapow server greet.pow
|
||||
```
|
||||
|
||||
to start a Kapow! server exposing your service. Now you can check that it works
|
||||
as intended with good ole’ `curl`:
|
||||
```sh
|
||||
curl localhost:8080/greet
|
||||
Hello World
|
||||
|
||||
curl localhost:8080/greet?name=friend
|
||||
Hello friend
|
||||
```
|
||||
|
||||
If you want to work with JSON you can use this version of the pow
|
||||
`greet-json.pow`
|
||||
```sh
|
||||
kapow route add -X POST /greet -c 'who=$(kapow get /request/body | jq -r .name); kapow set /response/status 201; jq --arg value "${who:-World}" -n \{name:\$value\} | kapow set /response/body'
|
||||
```
|
||||
|
||||
that uses [jq](https://stedolan.github.io/jq/) to allow you to work with JSON
|
||||
from the command line. Check that it works with
|
||||
```sh
|
||||
curl -X POST -H 'Content-Type: application/json' -d '{"name": "friend"}' localhost:8080/greet
|
||||
{"name": "friend" }
|
||||
|
||||
curl -X POST -H 'Content-Type: application/json' -d '' localhost:8080/greet
|
||||
{"name": "World"}
|
||||
```
|
||||
|
||||
|
||||
## The mandatory Echo (for UNIX fans)
|
||||
|
||||
First, you create a pow file named `echo.pow` with the following contents:
|
||||
```sh
|
||||
kapow route add -X POST /echo -c 'kapow get /request/body | kapow set /response/body'
|
||||
```
|
||||
|
||||
then, you execute:
|
||||
```sh
|
||||
kapow server echo.pow
|
||||
```
|
||||
|
||||
and you can check that it works as intended with good ole’ `curl`:
|
||||
```sh
|
||||
curl -X POST -d '1,2,3... testing' localhost:8080/echo
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, testing
|
||||
```
|
||||
|
||||
If you send a big file and want to see the content back as a real-time stream
|
||||
you can use this version `echo-stream.pow`
|
||||
```sh
|
||||
kapow route add -X POST /echo -c 'kapow get /request/body | kapow set /response/stream'
|
||||
```
|
||||
|
||||
|
||||
## The multiline fun
|
||||
|
||||
Unless you're a hardcore Perl golfer, you'll probably need to write your stuff
|
||||
over more than one line in order to avoid the mess we saw on our JSON greet
|
||||
version.
|
||||
|
||||
Don't worry, we need to write several lines, too. Bash, in its magnificent
|
||||
UNIX® style, provides us with the
|
||||
[here-documents](https://www.gnu.org/software/bash/manual/bash.html#Here-Documents)
|
||||
mechanism that we can leverage precisely for this purpose.
|
||||
|
||||
Imagine that we want to return both the standard output and a generated file from a
|
||||
command execution. Let's write a `log-and-stuff.pow` file with the following content:
|
||||
```sh
|
||||
kapow route add /log_and_stuff - <<-'EOF'
|
||||
echo this is a quite long sentence and other stuff | tee log.txt | kapow set /response/body
|
||||
cat log.txt | kapow set /response/body
|
||||
EOF
|
||||
```
|
||||
|
||||
then we serve it with `kapow`:
|
||||
```sh
|
||||
kapow server log-and-stuff.pow
|
||||
```
|
||||
|
||||
Yup. As simple as that. You can check it.
|
||||
```sh
|
||||
curl localhost:8080/log_and_stuff
|
||||
this is a quite long sentence and other stuff
|
||||
this is a quite long sentence and other stuff
|
||||
```
|
||||
|
||||
|
||||
## Interact with other systems
|
||||
|
||||
You can leverage all the power of the shell in your scripts and interact with
|
||||
other systems by using all the available tools. Write a
|
||||
`log-and-stuff-callback.pow` file with the following content:
|
||||
```sh
|
||||
kapow route add /log_and_stuff - <<-'EOF'
|
||||
callback_url="$(kapow get /request/params/callback)"
|
||||
echo this is a quite long sentence and other stuff | tee log.txt | kapow set /response/body
|
||||
echo sending to $callback_url | kapow set /response/body
|
||||
curl -X POST --data-binary @log.txt $callback_url | kapow set /response/body
|
||||
EOF
|
||||
```
|
||||
|
||||
serve it with `kapow`:
|
||||
```sh
|
||||
kapow server log-and-stuff-callback.pow
|
||||
```
|
||||
|
||||
and finally check it.
|
||||
```sh
|
||||
curl localhost:8080/log_and_stuff?callback=nowhere.com
|
||||
this is a quite long sentence and other stuff
|
||||
sending to nowhere.com
|
||||
<html>
|
||||
<head><title>405 Not Allowed</title></head>
|
||||
<body>
|
||||
<center><h1>405 Not Allowed</h1></center>
|
||||
<hr><center>nginx</center>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
You must be aware that you must have all the dependencies you use in your
|
||||
scripts installed in the host that will run the Kapow! server.
|
||||
|
||||
In addition, a pow file can contain as many routes as you like, so you can start
|
||||
a server with several routes configured in one shot.
|
||||
|
||||
|
||||
# Sample Docker usage
|
||||
|
||||
## Clone the project
|
||||
|
||||
```sh
|
||||
git clone https://github.com/BBVA/kapow.git
|
||||
```
|
||||
|
||||
|
||||
## Build the kapow! docker image
|
||||
|
||||
```sh
|
||||
make docker
|
||||
```
|
||||
|
||||
Now you have a container image with all the above pow files copied in /tmp so
|
||||
you can start each example by running
|
||||
```sh
|
||||
docker run --rm -p 8080:8080 docker server example.pow
|
||||
```
|
||||
|
||||
|
||||
## Build a docker image for running the nmap example
|
||||
```sh
|
||||
cd /path/to/kapow/poc/examples/nmap; docker build -t kapow-nmap .
|
||||
```
|
||||
|
||||
|
||||
## Run kapow
|
||||
```sh
|
||||
docker run \
|
||||
-d \
|
||||
-p 8080:8080 \
|
||||
kapow-nmap
|
||||
```
|
||||
which will output something like this:
|
||||
```sh
|
||||
e7da20c7d9a39624b5c56157176764671e5d2d8f1bf306b3ede898d66fe3f4bf
|
||||
```
|
||||
|
||||
|
||||
## Test /list endpoint
|
||||
|
||||
In another terminal, try running:
|
||||
```sh
|
||||
curl http://localhost:8080/list/github.com
|
||||
```
|
||||
|
||||
which will respond something like:
|
||||
```sh
|
||||
Starting Nmap 7.70 ( https://nmap.org ) at 2019-05-10 14:01 UTC
|
||||
Nmap scan report for github.com (140.82.118.3)
|
||||
rDNS record for 140.82.118.3: lb-140-82-118-3-ams.github.com
|
||||
Nmap done: 1 IP address (0 hosts up) scanned in 0.04 seconds
|
||||
```
|
||||
|
||||
et voilà !
|
||||
|
||||
|
||||
# License
|
||||
|
||||
This project is distributed under the [Apache License 2.0](/LICENSE).
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package client_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,26 +13,24 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package control
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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).
|
||||
@@ -46,20 +44,27 @@ 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 it doesn't exist,
|
||||
// returns 404 and an error entity
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -69,58 +74,71 @@ 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
route.ID = id.String()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
res.WriteHeader(http.StatusNotFound)
|
||||
srverrors.WriteErrorResponse(http.StatusNotFound, "Route Not Found", res)
|
||||
} else {
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
rBytes, _ := json.Marshal(r)
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package control
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -29,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
|
||||
@@ -100,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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAddRouteReturns422ErrorWhenMandatoryFieldsMissing(t *testing.T) {
|
||||
handler := http.HandlerFunc(addRoute)
|
||||
tc := []struct {
|
||||
payload, testCase string
|
||||
testMustFail bool
|
||||
@@ -166,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)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,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
|
||||
@@ -203,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")
|
||||
@@ -219,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 }()
|
||||
@@ -229,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,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"}
|
||||
@@ -266,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)
|
||||
@@ -296,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,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)
|
||||
@@ -324,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,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)
|
||||
}
|
||||
@@ -414,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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRouteSetsCorrctContentType(t *testing.T) {
|
||||
func TestGetRouteSetsCorrectContentType(t *testing.T) {
|
||||
handler := mux.NewRouter()
|
||||
handler.HandleFunc("/routes/{id}", getRoute).
|
||||
Methods("GET")
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
@@ -13,12 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/BBVA/kapow/internal/server/model"
|
||||
"github.com/BBVA/kapow/internal/server/srverrors"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -38,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
@@ -126,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
@@ -23,15 +24,22 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/BBVA/kapow/internal/server/model"
|
||||
"github.com/BBVA/kapow/internal/server/srverrors"
|
||||
"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 {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
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
|
||||
@@ -63,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,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
|
||||
}
|
||||
|
||||
@@ -178,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
|
||||
}
|
||||
|
||||
@@ -191,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
@@ -120,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, 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/BBVA/kapow/internal/server/srverrors"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -35,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
|
||||
}
|
||||
|
||||
|
||||
@@ -13,16 +13,44 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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{
|
||||
@@ -49,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package srverrors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ServerErrMessage struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
func WriteErrorResponse(statusCode int, reasonMsg string, res http.ResponseWriter) {
|
||||
respBody := ServerErrMessage{}
|
||||
respBody.Reason = reasonMsg
|
||||
bb, _ := json.Marshal(respBody)
|
||||
res.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
res.WriteHeader(statusCode)
|
||||
_, _ = res.Write(bb)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package spawn
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package spawn
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
go run . route add -c 'echo foo' /
|
||||
+4
-1
@@ -6,4 +6,7 @@ sync:
|
||||
pipenv sync --dev
|
||||
|
||||
test: sync
|
||||
pipenv run make -C ../spec/test
|
||||
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
|
||||
|
||||
+15
-10
@@ -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, "foo": "bar"}
|
||||
|
||||
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
|
||||
|
||||
@@ -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)'
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
curl -X POST --data-binary @- http://localhost:8080/eval <<EOF
|
||||
touch /tmp/kapow_was_here
|
||||
EOF
|
||||
|
||||
echo 'Proof of success:'
|
||||
ls -l /tmp/kapow_was_here
|
||||
@@ -1,35 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Nmap</title>
|
||||
</head>
|
||||
<body>
|
||||
<form id="nmap-params" method="post" action="nmap.xml">
|
||||
<fieldset>
|
||||
<legend>Nmap parameters</legend>
|
||||
<div>
|
||||
<label for="target_spec">Target Specification:</label>
|
||||
<input name="target_spec" type="text" placeholder="ip, domain, network, range" value="127.0.0.1" required autofocus>
|
||||
<p>
|
||||
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
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label for="port_ranges">Port Ranges:</label>
|
||||
<input name="port_ranges" type="text" placeholder="port, range, list" value="8080" required>
|
||||
<p>
|
||||
Only scan specified ports. e.g.: 22; 1-65535;
|
||||
U:53,111,137,T:21-25,80,139,8080,S:9
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<input name="scan" type="submit" value="Scan">
|
||||
<input name="reset" type="reset" value="Reset">
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
|
||||
@@ -1,7 +0,0 @@
|
||||
FROM bbvalabsci/kapow:latest
|
||||
|
||||
RUN apk add nmap
|
||||
|
||||
COPY nmap.pow /tmp/
|
||||
|
||||
CMD ["server", "/tmp/nmap.pow"]
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
curl -X POST --data-binary @- http://localhost:8080/convert/markdown/man <<EOF
|
||||
# This is not a pipe
|
||||
|
||||
1. hello
|
||||
1. goodbye
|
||||
EOF
|
||||
@@ -1,41 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>PDF Editor</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/siimple/dist/siimple.min.css">
|
||||
<head>
|
||||
<body>
|
||||
<div class="siimple-content siimple-content--extra-large">
|
||||
<div class="siimple-grid">
|
||||
<div class="siimple-grid-row">
|
||||
<div class="siimple-grid-col siimple-grid-col--12">
|
||||
<div class="siimple-grid-col siimple-grid-col--6">
|
||||
<div class="siimple-form">
|
||||
<form action="/editor/pdf" method="post" target="result" id="editor">
|
||||
<div class="siimple-form-title">AWYSIWYG PDF Editor</div>
|
||||
<div class="siimple-form-field">
|
||||
<div class="siimple-form-field-label">InputFormat</div>
|
||||
<select name="from">
|
||||
<option value="markdown">Markdown</option>
|
||||
<option value="rst">ReStructuredText</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="siimple-form-field">
|
||||
<div class="siimple-form-field-label">InputFormat</div>
|
||||
<textarea class="siimple-textarea siimple-textarea--fluid" rows="25" name="content">Example text</textarea>
|
||||
</div>
|
||||
<div class="siimple-form-field">
|
||||
<div class="siimple-btn siimple-btn--blue" onclick="document.getElementById('editor').submit();">Preview!</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="siimple-grid-col siimple-grid-col--6">
|
||||
<iframe name="result" src="" style="height: 100%; width: 100%;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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'
|
||||
@@ -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}
|
||||
@@ -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/<network-interface> | 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!
|
||||
@@ -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'
|
||||
@@ -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
|
||||
<html>
|
||||
<body>
|
||||
<a href='javascript: Array.from(document.querySelectorAll("a")).filter(x => x.href.indexOf("magnet") != -1 ).map(x => x.href = "http://localhost:8080/save/magnet?link="+encodeURI(x.href))'>Add me to your bookmarks!</a>
|
||||
</body>
|
||||
</html>
|
||||
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"'
|
||||
+42
-21
@@ -131,14 +131,19 @@ 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 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).
|
||||
* When several error conditions can happen at the same time, the order of the
|
||||
checks is implementation-defined.
|
||||
|
||||
For instance, given this request:
|
||||
```http
|
||||
@@ -163,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
|
||||
|
||||
@@ -242,8 +256,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`; **Reason**: `Malformed JSON`
|
||||
* **Code**: `422`; **Reason**: `Invalid Route`
|
||||
* **Sample Call**:<br />
|
||||
```sh
|
||||
$ curl -X POST --data-binary @- $KAPOW_URL/routes <<EOF
|
||||
@@ -295,8 +309,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`; Reason: `Malformed JSON`
|
||||
* **Code**: `422`; Reason: `Invalid Route`
|
||||
* **Sample Call**:<br />
|
||||
```sh
|
||||
$ curl -X PUT --data-binary @- $KAPOW_URL/routes <<EOF`
|
||||
@@ -329,7 +343,7 @@ Removes the route identified by `{id}`.
|
||||
* **Success Responses**:
|
||||
* **Code**: `204 No Content`
|
||||
* **Error Responses**:
|
||||
* **Code**: `404 Not Found`
|
||||
* **Code**: `404`; Reason: `Route Not Found`
|
||||
* **Sample Call**:<br />
|
||||
```sh
|
||||
$ curl -X DELETE $KAPOW_URL/routes/ROUTE_1f186c92_f906_4506_9788_a1f541b11d0f
|
||||
@@ -357,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**:<br />
|
||||
```sh
|
||||
$ curl -X GET $KAPOW_URL/routes/ROUTE_1f186c92_f906_4506_9788_a1f541b11d0f
|
||||
@@ -381,11 +395,16 @@ response.
|
||||
* 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 body. On read requests, containing the data retrieved as
|
||||
'application/octet-stream' mime type.
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
|
||||
## API Elements
|
||||
@@ -510,12 +529,11 @@ path doesn't exist or is invalid.
|
||||
**Header**: `Content-Type: application/octet-stream`<br />
|
||||
**Content**: The value of the resource. Note that it may be empty.
|
||||
* **Error Responses**:
|
||||
* **Code**: `400 Bad Request`<br />
|
||||
**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`<br />
|
||||
* **Code**: `400`; Reason: `Invalid Resource Path`<br />
|
||||
**Notes**: Check the list of valid resource paths at the top of this section.
|
||||
* **Code**: `404`; Reason: `Handler ID Not Found`<br />
|
||||
**Notes**: Refers to the handler resource itself.
|
||||
* **Code**: `404 Not Found`<br />
|
||||
* **Code**: `404`; Reason: `Resource Item Not Found`<br />
|
||||
**Notes**: Refers to the named item in the corresponding data API resource.
|
||||
* **Sample Call**:<br />
|
||||
```sh
|
||||
@@ -534,10 +552,13 @@ path doesn't exist or is invalid.
|
||||
* **Success Responses**:
|
||||
* **Code**: `200 OK`
|
||||
* **Error Responses**:
|
||||
* **Code**: `400 Bad Request`<br />
|
||||
**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`<br />
|
||||
* **Code**: `400`; Reason: `Invalid Resource Path`<br />
|
||||
**Notes**: Check the list of valid resource paths at the top of this section.
|
||||
* **Code**: `422`; Reason: `Non Integer Value`<br />
|
||||
**Notes**: When setting the status code with a non integer value.
|
||||
* **Code**: `400`; Reason: `Invalid Status Code`<br />
|
||||
**Notes**: When setting a non-supported status code.
|
||||
* **Code**: `404`; Reason: `Handler ID Not Found`<br />
|
||||
**Notes**: Refers to the handler resource itself.
|
||||
* **Sample Call**:<br />
|
||||
```sh
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -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,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"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
[
|
||||
|
||||
@@ -25,14 +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
|
||||
|
||||
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
|
||||
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
|
||||
And the response header "Content-Type" contains "application/json"
|
||||
And I get the following response body:
|
||||
"""
|
||||
{
|
||||
"reason": "Handler ID Not Found"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -34,7 +34,7 @@ Feature: Retrieve request resources from a handler in Kapow! server.
|
||||
| body | | bodyVal1 |
|
||||
And I get the resource "<resourcePath>"
|
||||
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:
|
||||
"""
|
||||
<value>
|
||||
|
||||
@@ -41,7 +41,7 @@ Feature: Setting values for handler response resources in Kapow! server.
|
||||
And I set the resource "<resourcePath>" with value "<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 "<value>" for the response "<fieldType>" named "<elementName>" in the testing request
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, "").split(';')[0] == 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"
|
||||
|
||||
Reference in New Issue
Block a user