Skip to content

Commit

Permalink
oauth config, template function, unit and apitest, added bounce-query… (
Browse files Browse the repository at this point in the history
#52)

* oauth config, template function, unit and apitest, added bounce-query to support it

* Fixed typo

Co-authored-by: Martin Rode <martin.rode@programmfabrik.de>
  • Loading branch information
unai-programmfabrik and martinrode authored Jun 8, 2021
1 parent cfdb289 commit 2805b3b
Show file tree
Hide file tree
Showing 13 changed files with 210 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ webtest:
go tool cover -html=testcoverage.out

apitest:
./apitest --stop-on-fail -d test/
./apitest -c apitest.test.yml --stop-on-fail -d test/

gox: deps
go get github.com/mitchellh/gox
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1895,6 +1895,31 @@ will return this response:
}
```
### `bounce-query`
The endpoint `bounce-query` returns the a response that includes in its `body` the request `query string` as it is.
This is useful in endpoints where a body cannot be configured, like oAuth urls, so we can simulate responses in the request for testing.
```yaml
{
"request": {
"endpoint": "bounce-query?here=is&all=stuff",
"method": "POST",
"body": {}
}
}
```
will return this response:
```yaml
{
"response": {
"body": "here=is&all=stuff"
}
}
```
## HTTP Server Proxy
The proxy different stores can be used to both store and read their stored requests
Expand Down
2 changes: 2 additions & 0 deletions api_testsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ func (ats *Suite) parseAndRunTest(v interface{}, manifestDir, testFilePath strin
return false
}
loader.ServerURL = serverURL
loader.OAuthClient = ats.Config.OAuthClient

isParallelPathSpec := false
switch t := v.(type) {
Expand Down Expand Up @@ -354,6 +355,7 @@ func (ats *Suite) loadManifest() ([]byte, error) {
return nil, fmt.Errorf("can not load server url into manifest (%s): %s", ats.manifestPath, err)
}
loader.ServerURL = serverURL
loader.OAuthClient = ats.Config.OAuthClient
manifestFile, err := filesystem.Fs.Open(ats.manifestPath)
if err != nil {
return res, fmt.Errorf("error opening manifestPath (%s): %s", ats.manifestPath, err)
Expand Down
6 changes: 6 additions & 0 deletions apitest.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apitest:
oauth_client:
my_client:
endpoint:
token_url: "http://localhost:9999/bounce-query?access_token=mytoken"
secret: "foobar"
4 changes: 4 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/programmfabrik/apitest/pkg/lib/filesystem"
"github.com/programmfabrik/apitest/pkg/lib/util"

"github.com/sirupsen/logrus"
"github.com/spf13/afero"
Expand All @@ -26,6 +27,7 @@ type ConfigStruct struct {
File string `mapstructure:"file"`
Format string `mapstructure:"format"`
} `mapstructure:"report"`
OAuthClient util.OAuthClientsConfig `mapstructure:"oauth_client"`
}
}

Expand Down Expand Up @@ -56,6 +58,7 @@ type TestToolConfig struct {
TestDirectories []string
LogNetwork bool
LogVerbose bool
OAuthClient util.OAuthClientsConfig
}

// NewTestToolConfig is mostly used for testing purpose. We can setup our config with this function
Expand All @@ -65,6 +68,7 @@ func NewTestToolConfig(serverURL string, rootDirectory []string, logNetwork bool
rootDirectorys: rootDirectory,
LogNetwork: logNetwork,
LogVerbose: logVerbose,
OAuthClient: Config.Apitest.OAuthClient,
}
err = config.extractTestDirectories()
return config, err
Expand Down
7 changes: 2 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@ go 1.13

require (
github.com/clbanning/mxj v1.8.4
github.com/gabriel-vasile/mimetype v0.3.22
github.com/gorilla/mux v1.7.4
github.com/k0kubun/pp v3.0.1+incompatible
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-sqlite3 v1.14.4
github.com/mitchellh/gox v1.0.1 // indirect
github.com/moul/http2curl v1.0.0
github.com/pkg/errors v0.8.1
github.com/programmfabrik/go-test-utils v0.0.0-20191114143449-b8e16b04adb1
github.com/sergi/go-diff v1.0.0
github.com/sirupsen/logrus v1.4.2
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/spf13/afero v1.2.2
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.5.0
github.com/tidwall/gjson v1.3.4
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
)
11 changes: 11 additions & 0 deletions http_server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bytes"
"context"
"encoding/json"
"io"
Expand Down Expand Up @@ -37,6 +38,9 @@ func (ats *Suite) StartHttpServer() {
// bounce binary response with information in headers
mux.Handle("/bounce", logH(http.HandlerFunc(bounceBinary)))

// bounce query response with query in response body, as it is
mux.Handle("/bounce-query", logH(http.HandlerFunc(bounceQuery)))

// Start listening into proxy
ats.httpServerProxy = httpproxy.New(ats.HttpServer.Proxy)
ats.httpServerProxy.RegisterRoutes(mux, "/")
Expand Down Expand Up @@ -195,6 +199,13 @@ func bounceBinary(w http.ResponseWriter, r *http.Request) {
io.Copy(w, r.Body)
}

// bounceQuery returns the request query in response body
// for those cases where a body cannt be provided
func bounceQuery(w http.ResponseWriter, r *http.Request) {
rBody := bytes.NewBufferString(r.URL.RawQuery)
io.Copy(w, rBody)
}

func cookiesMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ckHeader := r.Header.Values("X-Test-Set-Cookies")
Expand Down
10 changes: 10 additions & 0 deletions pkg/lib/template/template_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"
"text/template"

"github.com/pkg/errors"
"github.com/programmfabrik/apitest/pkg/lib/datastore"

"github.com/programmfabrik/apitest/pkg/lib/cjson"
Expand Down Expand Up @@ -73,6 +74,7 @@ type Loader struct {
datastore *datastore.Datastore
HTTPServerHost string
ServerURL *url.URL
OAuthClient util.OAuthClientsConfig
}

func NewLoader(datastore *datastore.Datastore) Loader {
Expand Down Expand Up @@ -358,6 +360,14 @@ func (loader *Loader) Render(
}
return reflect.ValueOf(v).IsZero()
},
"oauth2_token": func(client string, login string, password string) (interface{}, error) {
oAuthClient, ok := loader.OAuthClient[client]
if !ok {
return nil, errors.Errorf("OAuth client %s not configured", client)
}
oAuthClient.Key = client
return oAuthClient.GetAuthToken(login, password)
},
}
tmpl, err := template.New("tmpl").Funcs(funcMap).Parse(string(tmplBytes))
if err != nil {
Expand Down
39 changes: 39 additions & 0 deletions pkg/lib/util/oauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package util

import (
"context"
"net/http"
"time"

"golang.org/x/oauth2"
)

// OAuthClientsConfig is our config for multiple oAuth clients
type OAuthClientsConfig map[string]OAuthClientConfig

// OAuthClientConfig is our config for a single oAuth client
type OAuthClientConfig struct {
Key string
Endpoint OAuthEndpointConfig `mapstructure:"endpoint"`
Secret string `mapstructure:"secret"`
}

// OAuthEndpointConfig is our config for an oAuth endpoint
type OAuthEndpointConfig struct {
TokenURL string `mapstructure:"token_url"`
}

// GetAuthToken sends request to oAuth token endpoint
// to get a token on behalf of a user
func (c OAuthClientConfig) GetAuthToken(username string, password string) (*oauth2.Token, error) {
cfg := oauth2.Config{
ClientID: c.Key,
ClientSecret: c.Secret,
Endpoint: oauth2.Endpoint{
TokenURL: c.Endpoint.TokenURL,
},
}
httpClient := &http.Client{Timeout: 5 * time.Second}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
return cfg.PasswordCredentialsToken(ctx, username, password)
}
68 changes: 68 additions & 0 deletions pkg/lib/util/oauth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package util

import (
"encoding/base64"
"fmt"
"net/http"
"net/http/httptest"
"testing"
)

func TestGetToken(t *testing.T) {
theToken := "thetoken"
theClient := "my_client"
theSecret := "foobar"
basicAuth := theClient + ":" + theSecret
basicAuthHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(basicAuth))
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader != basicAuthHeader {
w.WriteHeader(403)
return
}
fmt.Fprintf(w, `access_token=%s`, theToken)
}))
defer ts.Close()

cfg := OAuthClientConfig{
Key: "nobody",
Endpoint: OAuthEndpointConfig{
TokenURL: ts.URL,
},
Secret: "whatever",
}
_, err := cfg.GetAuthToken("hey", "yo")
if err == nil {
t.Fatal("Expected error")
}

cfg = OAuthClientConfig{
Key: theClient,
Endpoint: OAuthEndpointConfig{
TokenURL: ts.URL,
},
Secret: "whatever",
}
_, err = cfg.GetAuthToken("hey", "yo")
if err == nil {
t.Fatal("Expected error")
}

cfg = OAuthClientConfig{
Key: theClient,
Endpoint: OAuthEndpointConfig{
TokenURL: ts.URL,
},
Secret: theSecret,
}
token, err := cfg.GetAuthToken("hey", "yo")
if err != nil {
t.Fatal(err)
}
if token == nil {
t.Fatal("No token nor error returned")
}
if token.AccessToken != theToken {
t.Fatalf("Received token: %s , expected: %s", token.AccessToken, theToken)
}
}
19 changes: 19 additions & 0 deletions test/oauth2/bounce_token.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "Bounce token",
"request": {
"server_url": "http://localhost:9999",
"endpoint": "bounce-json",
"method": "POST",
"body": {
"token": {{ datastore "access_token" | marshal }}
}
},
"response": {
"statuscode": 200,
"body": {
"body": {
"token": "mytoken"
}
}
}
}
17 changes: 17 additions & 0 deletions test/oauth2/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"http_server": {
"addr": ":9999",
"dir": "./",
"testmode": false,
"proxy": {
"oauth2": {
"mode": "passthru"
}
}
},
"name": "oauth2",
"tests": [
"@store_token.json",
"@bounce_token.json"
]
}
6 changes: 6 additions & 0 deletions test/oauth2/store_token.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "Store oauth2 access token",
"store": {
"access_token": {{ oauth2_token "my_client" "user" "password" | marshal | qjson "access_token" }}
}
}

0 comments on commit 2805b3b

Please sign in to comment.