Skip to content

Commit

Permalink
Merge pull request #7 from linkpoolio/enhancement/coverage
Browse files Browse the repository at this point in the history
Improve test coverage, remove redundant post form
  • Loading branch information
jleeh authored Apr 16, 2019
2 parents c3b1a73 + bb3b8d3 commit d912546
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 45 deletions.
40 changes: 12 additions & 28 deletions bridge/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"gopkg.in/guregu/null.v3"
"io/ioutil"
"net/http"
"net/url"
"os"
"time"
)
Expand All @@ -25,8 +24,7 @@ const (
type Opts struct {
Name string `json:"name"`
Path string `json:"path"`
Lambda bool `json:"lambda"`
Port int `json:"port"`
Lambda bool `json:"Lambda"`
}

// Result represents a Chainlink JobRun
Expand Down Expand Up @@ -130,7 +128,7 @@ type Bridge interface {
}

// Server holds pointers to the bridges indexed by their paths
// and the bridge to be mounted in lambda.
// and the bridge to be mounted in Lambda.
type Server struct {
pathMap map[string]Bridge
ldaBridge Bridge
Expand Down Expand Up @@ -178,11 +176,11 @@ func NewServer(bridges ...Bridge) *Server {
// If the inbuilt http server is being used, bridges can specify many external adaptors
// as long if exclusive paths are given.
//
// If multiple adaptors are included with lambda/gcp enabled, then the first bridge that
// If multiple adaptors are included with Lambda/gcp enabled, then the first bridge that
// has it enabled will be given as the Handler.
func (s *Server) Start(port int) {
if len(os.Getenv("LAMBDA")) > 0 {
lambda.Start(s.lambda)
lambda.Start(s.Lambda)
} else {
logrus.WithField("port", port).Info("Starting the bridge server")
logrus.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), s.Mux()))
Expand Down Expand Up @@ -246,7 +244,7 @@ func (s *Server) Handler(w http.ResponseWriter, r *http.Request) {
}
}

func (s *Server) lambda(r *Result) (interface{}, error) {
func (s *Server) Lambda(r *Result) (interface{}, error) {
if obj, err := s.ldaBridge.Run(NewHelper(r.Data)); err != nil {
r.SetErrored(err)
} else if data, err := ParseInterface(obj); err != nil {
Expand Down Expand Up @@ -300,11 +298,9 @@ func (h *Helper) GetIntParam(key string) int64 {
// CallOpts are the options given into a http call method
type CallOpts struct {
Auth Auth `json:"-"`
Param map[string]interface{} `json:"param"`
ParamPassthrough bool `json:"paramPassthrough"`
FormPassthrough bool `json:"formPassthrough"`
Query map[string]interface{} `json:"query"`
QueryPassthrough bool `json:"queryPassthrough"`
Body string `json:"body"`
PostForm url.Values `json:"postForm"`
ExpectedCode int `json:"expectedCode"`
}

Expand All @@ -327,8 +323,8 @@ func (h *Helper) HTTPCallWithOpts(method, url string, obj interface{}, opts Call
// HTTPCallRawWithOpts performs a HTTP call with any method and returns the raw byte body and any error
// Supported options:
// - Authentication methods for the API (query param, headers)
// - Query parameters via `opts.Param`
// - Passthrough through all json keys within the request `data` object via `opts.ParamPassthrough`
// - Query parameters via `opts.Query`
// - Passthrough through all json keys within the request `data` object via `opts.QueryPassthrough`
// - Pass in a body to send with the request via `opts.Body`
// - Send in post form kv via `opts.PostForm`
// - Return an error if the returning http status code is different to `opts.ExpectedCode`
Expand All @@ -339,25 +335,13 @@ func (h *Helper) HTTPCallRawWithOpts(method, url string, opts CallOpts) ([]byte,
}
req.Header.Add("Content-Type", "application/json")

req.PostForm = opts.PostForm
if opts.FormPassthrough {
for k, v := range h.Data.Map() {
if v.IsArray() {
for _, v2 := range v.Array() {
req.PostForm[k] = append(req.PostForm[k], v2.String())
}
} else {
req.PostForm[k] = append(req.PostForm[k], v.String())
}
}
}
q := req.URL.Query()
if opts.ParamPassthrough {
if opts.QueryPassthrough {
for k, v := range h.Data.Map() {
q.Add(k, fmt.Sprintf("%s", v))
}
} else {
for k, v := range opts.Param {
for k, v := range opts.Query {
q.Add(k, fmt.Sprintf("%s", v))
}
}
Expand Down Expand Up @@ -398,7 +382,7 @@ func NewAuth(authType string, key string, value string) Auth {
return a
}

// Param is the Auth implementation that requires GET param set
// Query is the Auth implementation that requires GET param set
type Param struct {
Key string
Value string
Expand Down
48 changes: 48 additions & 0 deletions bridge/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ import (
"testing"
)

func TestJSON_Merge(t *testing.T) {
j1, err := ParseInterface(map[string]string{
"alice": "bob",
"carl": "dennis",
})
assert.Nil(t, err)
j2, err := ParseInterface(map[string]string{
"alice": "bob",
"eric": "fred",
"geoff": "harry",
})
j3, err := j1.Merge(j2)
assert.Nil(t, err)
assert.Equal(t, "bob", j3.Get("alice").String())
assert.Equal(t, "fred", j3.Get("eric").String())
}

// Copied from the example to use as a test fixture
type CryptoCompare struct{}

Expand Down Expand Up @@ -231,4 +248,35 @@ func TestServer_Mux_CryptoCompare(t *testing.T) {
assert.True(t, ok)
_, ok = data["EUR"]
assert.True(t, ok)
}

func TestServer_Lambda_CryptoCompare(t *testing.T) {
s := NewServer(&CryptoCompare{})

r := &Result{}
r.JobRunID = "1234"

obj, err := s.Lambda(r)
assert.Nil(t, err)
json, err := ParseInterface(obj)
assert.Nil(t, err)

assert.Equal(t, "1234", json.Get("jobRunId").String())

data := json.Get("data").Map()
_, ok := data["USD"]
assert.True(t, ok)
_, ok = data["JPY"]
assert.True(t, ok)
_, ok = data["EUR"]
assert.True(t, ok)
}

func TestAuth_Header(t *testing.T) {
a := NewAuth(AuthHeader, "API-KEY", "key")
req, err := http.NewRequest(http.MethodGet, "http://test", nil)
assert.Nil(t, err)
a.Authenticate(req)

assert.Equal(t, "key", req.Header.Get("API-KEY"))
}
2 changes: 2 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ignore:
- "examples"
6 changes: 3 additions & 3 deletions examples/apiaggregator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ type Result struct {
// - `type` string Aggregation type to use
// For example:
// {
// "api": [
// "api": [
// "https://www.bitstamp.net/api/v2/ticker/btcusd/",
// "https://api.pro.coinbase.com/products/btc-usd/ticker"
// ],
// "paths": ["$.last", "$.price"],
// ],
// "paths": ["$.last", "$.price"],
// "type": "median"
// }
type APIAggregator struct{}
Expand Down
2 changes: 1 addition & 1 deletion examples/wolframalpha/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (cc *WolframAlpha) Run(h *bridge.Helper) (interface{}, error) {
"https://api.wolframalpha.com/v1/result",
bridge.CallOpts{
Auth: bridge.NewAuth(bridge.AuthParam, "appid", os.Getenv("APP_ID")),
Param: map[string]interface{}{
Query: map[string]interface{}{
"i": h.GetParam("query"),
},
},
Expand Down
1 change: 1 addition & 0 deletions json/invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{
12 changes: 12 additions & 0 deletions json/placeholder.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"name": "Placeholder",
"method": "GET",
"url": "https://reqres.in/api/users",
"opts": {
"query": {
"page": "page"
}
}
}
]
4 changes: 2 additions & 2 deletions json/rapidapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"env": "API_KEY"
},
"opts": {
"paramPassthrough": true
"queryPassthrough": true
}
},
{
Expand All @@ -22,7 +22,7 @@
"env": "API_KEY"
},
"opts": {
"formPassthrough": true
"queryPassthrough": true
}
}
]
14 changes: 3 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,10 @@ func NewJSONBridges(uri string) ([]bridge.Bridge, error) {
func (ja *JSON) Run(h *bridge.Helper) (interface{}, error) {
r := make(map[string]interface{})
p := make(map[string]interface{})
f := make(map[string][]string)
for k, v := range ja.bridge.Opts.Param {
for k, v := range ja.bridge.Opts.Query {
p[k] = h.GetParam(fmt.Sprintf("%s", v))
}
ja.bridge.Opts.Param = p
for k, s := range ja.bridge.Opts.PostForm {
for _, v := range s {
f[k] = append(f[k], h.GetParam(v))
}
}
ja.bridge.Opts.PostForm = f
ja.bridge.Opts.Query = p
var url string
if len(ja.bridge.URL) == 0 {
url = h.GetParam("url")
Expand All @@ -95,15 +88,14 @@ func (ja *JSON) Opts() *bridge.Opts {
return &bridge.Opts{
Name: ja.bridge.Name,
Path: ja.bridge.Path,
Port: 8080,
Lambda: true,
}
}

// Handler is the entrypoint for GCP functions
func Handler(w http.ResponseWriter, r *http.Request) {
env := os.Getenv("BRIDGE")
if len(env) != 0 {
if len(env) == 0 {
w.Write([]byte("No bridge set"))
return
} else if b, err := NewJSONBridges(env); err != nil {
Expand Down
100 changes: 100 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package main

import (
"bytes"
"encoding/json"
"github.com/linkpoolio/bridges/bridge"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"testing"
)

Expand All @@ -25,6 +31,9 @@ func TestNewJSONBridges(t *testing.T) {
"from_currency": "GBP",
"to_currency": "EUR",
}, []string{"Error Message"}},
{"json/placeholder.json", "Placeholder", map[string]interface{}{
"page": 1,
}, []string{"page", "per_page", "total_pages"}},
}
for _, c := range cases {
t.Run(c.path, func(t *testing.T) {
Expand Down Expand Up @@ -55,3 +64,94 @@ func TestNewJSONBridges(t *testing.T) {
})
}
}

func TestNewJSONBridges_Errors(t *testing.T) {
_, err := NewJSONBridges("")
assert.Equal(t, "Empty bridge URI given", err.Error())

_, err = NewJSONBridges("json/invalid.json")
assert.Equal(t, "unexpected end of JSON input", err.Error())

_, err = NewJSONBridges("http://invalidqwerty.com")
assert.Contains(t, err.Error(), "no such host")
}

func TestHandler(t *testing.T) {
p := map[string]interface{}{
"jobRunId": "1234",
}
pb, err := json.Marshal(p)
assert.Nil(t, err)

req, err := http.NewRequest(http.MethodPost, "/", bytes.NewReader(pb))
assert.Nil(t, err)
rr := httptest.NewRecorder()

err = os.Setenv("BRIDGE", "json/cryptocompare.json")
assert.Nil(t, err)
Handler(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
err = os.Unsetenv("BRIDGE")
assert.Nil(t, err)

body, err := ioutil.ReadAll(rr.Body)
assert.Nil(t, err)
json, err := bridge.Parse(body)
assert.Nil(t, err)

assert.Equal(t, "1234", json.Get("jobRunId").String())

data := json.Get("data").Map()
_, ok := data["USD"]
assert.True(t, ok)
_, ok = data["JPY"]
assert.True(t, ok)
_, ok = data["EUR"]
assert.True(t, ok)
}

func TestHandler_NilBridge(t *testing.T) {
p := map[string]interface{}{
"jobRunId": "1234",
}
pb, err := json.Marshal(p)
assert.Nil(t, err)

req, err := http.NewRequest(http.MethodPost, "/", bytes.NewReader(pb))
assert.Nil(t, err)
rr := httptest.NewRecorder()

err = os.Setenv("BRIDGE", "")
assert.Nil(t, err)
Handler(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
err = os.Unsetenv("BRIDGE")
assert.Nil(t, err)

body, err := ioutil.ReadAll(rr.Body)
assert.Nil(t, err)
assert.Equal(t, "No bridge set", string(body))
}

func TestHandler_InvalidBridge(t *testing.T) {
p := map[string]interface{}{
"jobRunId": "1234",
}
pb, err := json.Marshal(p)
assert.Nil(t, err)

req, err := http.NewRequest(http.MethodPost, "/", bytes.NewReader(pb))
assert.Nil(t, err)
rr := httptest.NewRecorder()

err = os.Setenv("BRIDGE", "json/invalid.json")
assert.Nil(t, err)
Handler(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
err = os.Unsetenv("BRIDGE")
assert.Nil(t, err)

body, err := ioutil.ReadAll(rr.Body)
assert.Nil(t, err)
assert.Equal(t, "unexpected end of JSON input", string(body))
}

0 comments on commit d912546

Please sign in to comment.