mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-01 15:10:44 +00:00
Initial implementation of HTTP mock testing framework (#310)
* Initial implementation of HTTP mock testing framework Convert to VCR testing server. Segregate live testing via build tags. Converted Binance to VCR server Convert Bitstamp to VCR mocking tests Added VCR mock testing for localbitcoins * Add server generation for concurrent testing * Fix linter issues * Fix linter issue * fix race - potentially * revert auto assigning of host vals * Fix requested changes * Adds mock testing for ANX Switch to using TestMain functionality Added cron job usage for travis-ci to live testing Added appveyor scheduled build check for live testing * WOOPS * silly correction * Fixes fantastic linter issues * fixed another whoopsie * WOOO! * Adds gemini mock testing with additional fixes * Add docs and sharedvalue * Added tls using httptest package * Fixed issues * added explicit mock recording reference to error * Fix requested changes * strip port from mock files as they are not needed on tls server * Change incorrect names * fix requested changes * lbank update * Fix another issue * Updated readme
This commit is contained in:
committed by
Adrian Gallagher
parent
a81ddead9e
commit
6d8ba0a96a
174
exchanges/mock/README.md
Normal file
174
exchanges/mock/README.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# GoCryptoTrader package Mock
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-corp/gocryptotrader)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/mock)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This Mock package is part of the GoCryptoTrader codebase.
|
||||
|
||||
## This is still in active development
|
||||
|
||||
You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
|
||||
|
||||
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY)
|
||||
|
||||
## Mock Testing Suite
|
||||
|
||||
### Current Features
|
||||
|
||||
+ REST recording service
|
||||
+ REST mock response server
|
||||
|
||||
### How to enable
|
||||
|
||||
+ Mock testing is enabled by default in some exchanges; to disable and run live endpoint testing parse -tags=mock_test_off as a go test param.
|
||||
|
||||
+ To record a live endpoint create two files for an exchange.
|
||||
|
||||
### file one - your_current_exchange_name_live_test.go
|
||||
|
||||
```go
|
||||
//+build mock_test_off
|
||||
|
||||
// This will build if build tag mock_test_off is parsed and will do live testing
|
||||
// using all tests in (exchange)_test.go
|
||||
package your_current_exchange_name
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"log"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
var mockTests = false
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cfg := config.GetConfig()
|
||||
cfg.LoadConfig("../../testdata/configtest.json")
|
||||
your_current_exchange_nameConfig, err := cfg.GetExchangeConfig("your_current_exchange_name")
|
||||
if err != nil {
|
||||
log.Fatal("Test Failed - your_current_exchange_name Setup() init error", err)
|
||||
}
|
||||
your_current_exchange_nameConfig.AuthenticatedAPISupport = true
|
||||
your_current_exchange_nameConfig.APIKey = apiKey
|
||||
your_current_exchange_nameConfig.APISecret = apiSecret
|
||||
l.SetDefaults()
|
||||
l.Setup(&your_current_exchange_nameConfig)
|
||||
log.Printf(sharedtestvalues.LiveTesting, l.GetName(), l.APIUrl)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
```
|
||||
|
||||
### file two - your_current_exchange_name_mock_test.go
|
||||
|
||||
```go
|
||||
//+build !mock_test_off
|
||||
|
||||
// This will build if build tag mock_test_off is not parsed and will try to mock
|
||||
// all tests in _test.go
|
||||
package your_current_exchange_name
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"log"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/mock"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
const mockfile = "../../testdata/http_mock/your_current_exchange_name/your_current_exchange_name.json"
|
||||
|
||||
var mockTests = true
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cfg := config.GetConfig()
|
||||
cfg.LoadConfig("../../testdata/configtest.json")
|
||||
your_current_exchange_nameConfig, err := cfg.GetExchangeConfig("your_current_exchange_name")
|
||||
if err != nil {
|
||||
log.Fatal("Test Failed - your_current_exchange_name Setup() init error", err)
|
||||
}
|
||||
your_current_exchange_nameConfig.AuthenticatedAPISupport = true
|
||||
your_current_exchange_nameConfig.APIKey = apiKey
|
||||
your_current_exchange_nameConfig.APISecret = apiSecret
|
||||
l.SetDefaults()
|
||||
l.Setup(&your_current_exchange_nameConfig)
|
||||
|
||||
serverDetails, newClient, err := mock.NewVCRServer(mockfile)
|
||||
if err != nil {
|
||||
log.Fatalf("Test Failed - Mock server error %s", err)
|
||||
}
|
||||
|
||||
g.HTTPClient = newClient
|
||||
g.APIUrl = serverDetails
|
||||
|
||||
log.Printf(sharedtestvalues.MockTesting, l.GetName(), l.APIUrl)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+ Once those files are completed go through each invidual test function and add
|
||||
|
||||
```go
|
||||
var s SomeExchange
|
||||
|
||||
func TestDummyTest(t *testing.T) {
|
||||
s.APIURL = exchangeDefaultURL // This will overwrite the current mock url at localhost
|
||||
s.Verbose = true // This will show you some fancy debug output
|
||||
s.HTTPRecording = true // This will record the request and response payloads
|
||||
|
||||
err := s.SomeExchangeEndpointFunction()
|
||||
// check error
|
||||
}
|
||||
```
|
||||
|
||||
+ After this is completed it should populate a new mocktest.json file for you with the relavent payloads in testdata
|
||||
+ To check if the recording was successful, comment out recording and apiurl changes, then re-run test.
|
||||
|
||||
```go
|
||||
var s SomeExchange
|
||||
|
||||
func TestDummyTest(t *testing.T) {
|
||||
// s.APIURL = exchangeDefaultURL // This will overwrite the current mock url at localhost
|
||||
s.Verbose = true // This will show you some fancy debug output
|
||||
// s.HTTPRecording = true // This will record the request and response payloads
|
||||
|
||||
err := s.SomeExchangeEndpointFunction()
|
||||
// check error
|
||||
}
|
||||
```
|
||||
|
||||
+ The payload should be the same.
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
## Contribution
|
||||
|
||||
Please feel free to submit any pull requests or suggest any desired features to be added.
|
||||
|
||||
When submitting a PR, please abide by our coding guidelines:
|
||||
|
||||
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
|
||||
+ Pull requests need to be based on and opened against the `master` branch.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB***
|
||||
|
||||
71
exchanges/mock/common.go
Normal file
71
exchanges/mock/common.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MatchURLVals matches url.Value query strings
|
||||
func MatchURLVals(v1, v2 url.Values) bool {
|
||||
if len(v1) != len(v2) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(v1) == 0 && len(v2) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for key, val := range v1 {
|
||||
if key == "nonce" || key == "signature" || key == "timestamp" || key == "tonce" || key == "key" { // delta values
|
||||
if _, ok := v2[key]; !ok {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if val2, ok := v2[key]; ok {
|
||||
if strings.Join(val2, "") == strings.Join(val, "") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DeriveURLValsFromJSONMap gets url vals from a map[string]string encoded JSON body
|
||||
func DeriveURLValsFromJSONMap(payload []byte) (url.Values, error) {
|
||||
var vals = url.Values{}
|
||||
if string(payload) == "" {
|
||||
return vals, nil
|
||||
}
|
||||
intermediary := make(map[string]interface{})
|
||||
err := json.Unmarshal(payload, &intermediary)
|
||||
if err != nil {
|
||||
return vals, err
|
||||
}
|
||||
|
||||
for k, v := range intermediary {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
vals.Add(k, val)
|
||||
case bool:
|
||||
vals.Add(k, strconv.FormatBool(val))
|
||||
case float64:
|
||||
vals.Add(k, strconv.FormatFloat(val, 'f', -1, 64))
|
||||
case map[string]interface{}, []interface{}, nil:
|
||||
vals.Add(k, fmt.Sprintf("%v", val))
|
||||
default:
|
||||
log.Println(reflect.TypeOf(val))
|
||||
return vals, errors.New("unhandled conversion type, please add as needed")
|
||||
}
|
||||
}
|
||||
|
||||
return vals, nil
|
||||
}
|
||||
140
exchanges/mock/common_test.go
Normal file
140
exchanges/mock/common_test.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMatchURLVals(t *testing.T) {
|
||||
testVal, testVal2, testVal3, emptyVal := url.Values{}, url.Values{}, url.Values{}, url.Values{}
|
||||
testVal.Add("test", "test")
|
||||
testVal2.Add("test2", "test2")
|
||||
testVal3.Add("test", "diferentValString")
|
||||
|
||||
nonceVal1, nonceVal2 := url.Values{}, url.Values{}
|
||||
nonceVal1.Add("nonce", "012349723587")
|
||||
nonceVal2.Add("nonce", "9327373874")
|
||||
|
||||
var expected = false
|
||||
received := MatchURLVals(testVal, emptyVal)
|
||||
if received != expected {
|
||||
t.Errorf("Test Failed - MatchURLVals error expected %v received %v",
|
||||
expected,
|
||||
received)
|
||||
}
|
||||
|
||||
received = MatchURLVals(emptyVal, testVal)
|
||||
if received != expected {
|
||||
t.Errorf("Test Failed - MatchURLVals error expected %v received %v",
|
||||
expected,
|
||||
received)
|
||||
}
|
||||
|
||||
received = MatchURLVals(testVal, testVal2)
|
||||
if received != expected {
|
||||
t.Errorf("Test Failed - MatchURLVals error expected %v received %v",
|
||||
expected,
|
||||
received)
|
||||
}
|
||||
|
||||
received = MatchURLVals(testVal2, testVal)
|
||||
if received != expected {
|
||||
t.Errorf("Test Failed - MatchURLVals error expected %v received %v",
|
||||
expected,
|
||||
received)
|
||||
}
|
||||
|
||||
received = MatchURLVals(testVal, testVal3)
|
||||
if received != expected {
|
||||
t.Errorf("Test Failed - MatchURLVals error expected %v received %v",
|
||||
expected,
|
||||
received)
|
||||
}
|
||||
|
||||
received = MatchURLVals(nonceVal1, testVal2)
|
||||
if received != expected {
|
||||
t.Errorf("Test Failed - MatchURLVals error expected %v received %v",
|
||||
expected,
|
||||
received)
|
||||
}
|
||||
|
||||
expected = true
|
||||
received = MatchURLVals(emptyVal, emptyVal)
|
||||
if received != expected {
|
||||
t.Errorf("Test Failed - MatchURLVals error expected %v received %v",
|
||||
expected,
|
||||
received)
|
||||
}
|
||||
|
||||
received = MatchURLVals(testVal, testVal)
|
||||
if received != expected {
|
||||
t.Errorf("Test Failed - MatchURLVals error expected %v received %v",
|
||||
expected,
|
||||
received)
|
||||
}
|
||||
|
||||
received = MatchURLVals(nonceVal1, nonceVal2)
|
||||
if received != expected {
|
||||
t.Errorf("Test Failed - MatchURLVals error expected %v received %v",
|
||||
expected,
|
||||
received)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveURLValsFromJSON(t *testing.T) {
|
||||
test1 := struct {
|
||||
Things []string `json:"things"`
|
||||
Data struct {
|
||||
Numbers []int `json:"numbers"`
|
||||
Number float64 `json:"number"`
|
||||
SomeString string `json:"somestring"`
|
||||
} `json:"data"`
|
||||
}{
|
||||
Things: []string{"hello", "world"},
|
||||
Data: struct {
|
||||
Numbers []int `json:"numbers"`
|
||||
Number float64 `json:"number"`
|
||||
SomeString string `json:"somestring"`
|
||||
}{
|
||||
Numbers: []int{1, 3, 3, 7},
|
||||
Number: 3.14,
|
||||
SomeString: "hello, peoples",
|
||||
},
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(test1)
|
||||
if err != nil {
|
||||
t.Error("Test Failed - marshal error", err)
|
||||
}
|
||||
|
||||
_, err = DeriveURLValsFromJSONMap(payload)
|
||||
if err != nil {
|
||||
t.Error("Test Failed - DeriveURLValsFromJSON error", err)
|
||||
}
|
||||
|
||||
test2 := map[string]string{
|
||||
"val": "1",
|
||||
"val2": "2",
|
||||
"val3": "3",
|
||||
"val4": "4",
|
||||
"val5": "5",
|
||||
"val6": "6",
|
||||
"val7": "7",
|
||||
}
|
||||
|
||||
payload, err = json.Marshal(test2)
|
||||
if err != nil {
|
||||
t.Error("Test Failed - marshal error", err)
|
||||
}
|
||||
|
||||
vals, err := DeriveURLValsFromJSONMap(payload)
|
||||
if err != nil {
|
||||
t.Error("Test Failed - DeriveURLValsFromJSON error", err)
|
||||
}
|
||||
|
||||
if vals["val"][0] != "1" {
|
||||
t.Error("Test Failed - DeriveURLValsFromJSON unexpected value",
|
||||
vals["val"][0])
|
||||
}
|
||||
}
|
||||
439
exchanges/mock/recording.go
Normal file
439
exchanges/mock/recording.go
Normal file
@@ -0,0 +1,439 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
)
|
||||
|
||||
// HTTPResponse defines expected response from the end point including request
|
||||
// data for pathing on the VCR server
|
||||
type HTTPResponse struct {
|
||||
Data json.RawMessage `json:"data"`
|
||||
QueryString string `json:"queryString"`
|
||||
BodyParams string `json:"bodyParams"`
|
||||
Headers map[string][]string `json:"headers"`
|
||||
}
|
||||
|
||||
// HTTPRecord will record the request and response to a default JSON file for
|
||||
// mocking purposes
|
||||
func HTTPRecord(res *http.Response, service string, respContents []byte) error {
|
||||
if res == nil {
|
||||
return errors.New("http.Response cannot be nil")
|
||||
}
|
||||
|
||||
if res.Request == nil {
|
||||
return errors.New("http.Request cannot be nil")
|
||||
}
|
||||
|
||||
if res.Request.Method == "" {
|
||||
return errors.New("request method not supplied")
|
||||
}
|
||||
|
||||
if service == "" {
|
||||
return errors.New("service not supplied cannot access correct mock file")
|
||||
}
|
||||
service = strings.ToLower(service)
|
||||
|
||||
fileout := filepath.Join(DefaultDirectory, service, service+".json")
|
||||
|
||||
contents, err := common.ReadFile(fileout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var m VCRMock
|
||||
err = json.Unmarshal(contents, &m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m.Routes == nil {
|
||||
m.Routes = make(map[string]map[string][]HTTPResponse)
|
||||
}
|
||||
|
||||
var httpResponse HTTPResponse
|
||||
cleanedContents, err := CheckResponsePayload(respContents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(cleanedContents, &httpResponse.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var body string
|
||||
if res.Request.GetBody != nil {
|
||||
bodycopy, bodyErr := res.Request.GetBody()
|
||||
if bodyErr != nil {
|
||||
return bodyErr
|
||||
}
|
||||
payload, bodyErr := ioutil.ReadAll(bodycopy)
|
||||
if bodyErr != nil {
|
||||
return bodyErr
|
||||
}
|
||||
body = string(payload)
|
||||
}
|
||||
|
||||
switch res.Request.Header.Get(contentType) {
|
||||
case applicationURLEncoded:
|
||||
vals, urlErr := url.ParseQuery(body)
|
||||
if urlErr != nil {
|
||||
return urlErr
|
||||
}
|
||||
|
||||
httpResponse.BodyParams, urlErr = GetFilteredURLVals(vals)
|
||||
if urlErr != nil {
|
||||
return urlErr
|
||||
}
|
||||
|
||||
case textPlain:
|
||||
payload := res.Request.Header.Get("X-Gemini-Payload")
|
||||
j, dErr := common.Base64Decode(payload)
|
||||
if dErr != nil {
|
||||
return dErr
|
||||
}
|
||||
|
||||
httpResponse.BodyParams = string(j)
|
||||
|
||||
default:
|
||||
httpResponse.BodyParams = body
|
||||
}
|
||||
|
||||
httpResponse.Headers, err = GetFilteredHeader(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpResponse.QueryString, err = GetFilteredURLVals(res.Request.URL.Query())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, ok := m.Routes[res.Request.URL.Path]
|
||||
if !ok {
|
||||
m.Routes[res.Request.URL.Path] = make(map[string][]HTTPResponse)
|
||||
m.Routes[res.Request.URL.Path][res.Request.Method] = []HTTPResponse{httpResponse}
|
||||
} else {
|
||||
mockResponses, ok := m.Routes[res.Request.URL.Path][res.Request.Method]
|
||||
if !ok {
|
||||
m.Routes[res.Request.URL.Path][res.Request.Method] = []HTTPResponse{httpResponse}
|
||||
} else {
|
||||
switch res.Request.Method { // Based off method - check add or replace
|
||||
case http.MethodGet:
|
||||
for i := range mockResponses {
|
||||
mockQuery, urlErr := url.ParseQuery(mockResponses[i].QueryString)
|
||||
if urlErr != nil {
|
||||
return urlErr
|
||||
}
|
||||
|
||||
if MatchURLVals(mockQuery, res.Request.URL.Query()) {
|
||||
mockResponses = append(mockResponses[:i], mockResponses[i+1:]...) // Delete Old
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case http.MethodPost:
|
||||
for i := range mockResponses {
|
||||
cType, ok := mockResponses[i].Headers[contentType]
|
||||
if !ok {
|
||||
return errors.New("cannot find content type within mock responses")
|
||||
}
|
||||
|
||||
jCType := strings.Join(cType, "")
|
||||
var found bool
|
||||
switch jCType {
|
||||
case applicationURLEncoded:
|
||||
respQueryVals, urlErr := url.ParseQuery(body)
|
||||
if urlErr != nil {
|
||||
return urlErr
|
||||
}
|
||||
|
||||
mockRespVals, urlErr := url.ParseQuery(mockResponses[i].BodyParams)
|
||||
if urlErr != nil {
|
||||
log.Fatal(urlErr)
|
||||
}
|
||||
|
||||
if MatchURLVals(respQueryVals, mockRespVals) {
|
||||
// if found will delete instance and overwrite with new
|
||||
// data
|
||||
mockResponses = append(mockResponses[:i], mockResponses[i+1:]...)
|
||||
found = true
|
||||
}
|
||||
|
||||
case applicationJSON, textPlain:
|
||||
reqVals, jErr := DeriveURLValsFromJSONMap([]byte(body))
|
||||
if jErr != nil {
|
||||
return jErr
|
||||
}
|
||||
|
||||
mockVals, jErr := DeriveURLValsFromJSONMap([]byte(mockResponses[i].BodyParams))
|
||||
if jErr != nil {
|
||||
return jErr
|
||||
}
|
||||
|
||||
if MatchURLVals(reqVals, mockVals) {
|
||||
// if found will delete instance and overwrite with new
|
||||
// data
|
||||
mockResponses = append(mockResponses[:i], mockResponses[i+1:]...)
|
||||
found = true
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unhandled content type %s", jCType)
|
||||
}
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unhandled request method %s", res.Request.Method)
|
||||
}
|
||||
|
||||
m.Routes[res.Request.URL.Path][res.Request.Method] = append(mockResponses, httpResponse)
|
||||
}
|
||||
}
|
||||
|
||||
payload, err := json.MarshalIndent(m, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return common.WriteFile(fileout, payload)
|
||||
}
|
||||
|
||||
// GetFilteredHeader filters excluded http headers for insertion into a mock
|
||||
// test file
|
||||
func GetFilteredHeader(res *http.Response) (http.Header, error) {
|
||||
items, err := GetExcludedItems()
|
||||
if err != nil {
|
||||
return res.Header, err
|
||||
}
|
||||
|
||||
for i := range items.Headers {
|
||||
if res.Request.Header.Get(items.Headers[i]) != "" {
|
||||
res.Request.Header.Set(items.Headers[i], "")
|
||||
}
|
||||
}
|
||||
|
||||
return res.Request.Header, nil
|
||||
}
|
||||
|
||||
// GetFilteredURLVals filters excluded url value variables for insertion into a
|
||||
// mock test file
|
||||
func GetFilteredURLVals(vals url.Values) (string, error) {
|
||||
items, err := GetExcludedItems()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for key, val := range vals {
|
||||
for i := range items.Variables {
|
||||
if strings.EqualFold(items.Variables[i], val[0]) {
|
||||
vals.Set(key, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
return vals.Encode(), nil
|
||||
}
|
||||
|
||||
// CheckResponsePayload checks to see if there are any response body variables
|
||||
// that should not be there.
|
||||
func CheckResponsePayload(data []byte) ([]byte, error) {
|
||||
items, err := GetExcludedItems()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var intermediary interface{}
|
||||
err = json.Unmarshal(data, &intermediary)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload, err := CheckJSON(intermediary, &items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.MarshalIndent(payload, "", " ")
|
||||
}
|
||||
|
||||
// Reflection consts
|
||||
const (
|
||||
Int64 = "int64"
|
||||
Float64 = "float64"
|
||||
Slice = "slice"
|
||||
String = "string"
|
||||
Bool = "bool"
|
||||
Invalid = "invalid"
|
||||
)
|
||||
|
||||
// CheckJSON recursively parses json data to retract keywords, quite intensive.
|
||||
func CheckJSON(data interface{}, excluded *Exclusion) (interface{}, error) {
|
||||
var context map[string]interface{}
|
||||
if reflect.TypeOf(data).String() == "[]interface {}" {
|
||||
var sData []interface{}
|
||||
for i := range data.([]interface{}) {
|
||||
checkedData, err := CheckJSON(data.([]interface{})[i], excluded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sData = append(sData, checkedData)
|
||||
}
|
||||
return sData, nil
|
||||
}
|
||||
|
||||
conv, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(conv, &context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(context) == 0 {
|
||||
// Nil for some reason, should error out before in json.Unmarshal
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for key, val := range context {
|
||||
switch reflect.ValueOf(val).Kind().String() {
|
||||
case String:
|
||||
if IsExcluded(key, excluded.Variables) {
|
||||
context[key] = "" // Zero val string
|
||||
}
|
||||
case Int64:
|
||||
if IsExcluded(key, excluded.Variables) {
|
||||
context[key] = 0 // Zero val int
|
||||
}
|
||||
case Float64:
|
||||
if IsExcluded(key, excluded.Variables) {
|
||||
context[key] = 0.0 // Zero val float
|
||||
}
|
||||
case Slice:
|
||||
slice := val.([]interface{})
|
||||
if len(slice) < 1 {
|
||||
// Empty slice found
|
||||
context[key] = slice
|
||||
} else {
|
||||
if _, ok := slice[0].(map[string]interface{}); ok {
|
||||
var cleanSlice []interface{}
|
||||
for i := range slice {
|
||||
cleanMap, sErr := CheckJSON(slice[i], excluded)
|
||||
if sErr != nil {
|
||||
return nil, sErr
|
||||
}
|
||||
cleanSlice = append(cleanSlice, cleanMap)
|
||||
}
|
||||
context[key] = cleanSlice
|
||||
} else if IsExcluded(key, excluded.Variables) {
|
||||
context[key] = nil // Zero val slice
|
||||
}
|
||||
}
|
||||
|
||||
case Bool, Invalid: // Skip these bad boys for now
|
||||
default:
|
||||
// Recursively check map data
|
||||
contextValue, err := CheckJSON(val, excluded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context[key] = contextValue
|
||||
}
|
||||
}
|
||||
|
||||
return context, nil
|
||||
}
|
||||
|
||||
// IsExcluded cross references the key with the excluded variables
|
||||
func IsExcluded(key string, excludedVars []string) bool {
|
||||
for i := range excludedVars {
|
||||
if strings.EqualFold(key, excludedVars[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var excludedList Exclusion
|
||||
var m sync.Mutex
|
||||
var set bool
|
||||
var exclusionFile = DefaultDirectory + "exclusion.json"
|
||||
|
||||
var defaultExcludedHeaders = []string{"Key",
|
||||
"X-Mbx-Apikey",
|
||||
"Rest-Key",
|
||||
"Apiauth-Key"}
|
||||
var defaultExcludedVariables = []string{"bsb",
|
||||
"user",
|
||||
"name",
|
||||
"real_name",
|
||||
"receiver_name",
|
||||
"account_number",
|
||||
"username"}
|
||||
|
||||
// Exclusion defines a list of items to be excluded from the main mock output
|
||||
// this attempts a catch all approach and needs to be updated per exchange basis
|
||||
type Exclusion struct {
|
||||
Headers []string `json:"headers"`
|
||||
Variables []string `json:"variables"`
|
||||
}
|
||||
|
||||
// GetExcludedItems checks to see if the variable is in the exclusion list as to
|
||||
// not display secure items in mock file generator output
|
||||
func GetExcludedItems() (Exclusion, error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
if !set {
|
||||
file, err := ioutil.ReadFile(exclusionFile)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "no such file or directory") {
|
||||
return excludedList, err
|
||||
}
|
||||
|
||||
excludedList.Headers = defaultExcludedHeaders
|
||||
excludedList.Variables = defaultExcludedVariables
|
||||
|
||||
data, mErr := json.MarshalIndent(excludedList, "", " ")
|
||||
if mErr != nil {
|
||||
return excludedList, mErr
|
||||
}
|
||||
|
||||
mErr = ioutil.WriteFile(exclusionFile, data, os.ModePerm)
|
||||
if mErr != nil {
|
||||
return excludedList, mErr
|
||||
}
|
||||
|
||||
} else {
|
||||
err = json.Unmarshal(file, &excludedList)
|
||||
if err != nil {
|
||||
return excludedList, err
|
||||
}
|
||||
|
||||
if len(excludedList.Headers) == 0 || len(excludedList.Variables) == 0 {
|
||||
return excludedList, errors.New("exclusion list does not have names")
|
||||
}
|
||||
}
|
||||
|
||||
set = true
|
||||
}
|
||||
|
||||
return excludedList, nil
|
||||
}
|
||||
186
exchanges/mock/recording_test.go
Normal file
186
exchanges/mock/recording_test.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetFilteredHeader(t *testing.T) {
|
||||
resp := http.Response{}
|
||||
resp.Request = &http.Request{}
|
||||
resp.Request.Header = http.Header{}
|
||||
resp.Request.Header.Set("Key", "RiskyVals")
|
||||
fMap, err := GetFilteredHeader(&resp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if fMap.Get("Key") != "" {
|
||||
t.Error("Test Failed - risky vals where not replaced correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFilteredURLVals(t *testing.T) {
|
||||
superSecretData := "Dr Seuss"
|
||||
shadyVals := url.Values{}
|
||||
shadyVals.Set("real_name", superSecretData)
|
||||
cleanVals, err := GetFilteredURLVals(shadyVals)
|
||||
if err != nil {
|
||||
t.Error("Test Failed - GetFilteredURLVals error", err)
|
||||
}
|
||||
|
||||
if strings.Contains(cleanVals, superSecretData) {
|
||||
t.Error("Test Failed - Super secret data found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckResponsePayload(t *testing.T) {
|
||||
testbody := struct {
|
||||
SomeJSON string `json:"stuff"`
|
||||
}{
|
||||
SomeJSON: "REAAAAHHHHH",
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(testbody)
|
||||
if err != nil {
|
||||
t.Fatal("Test Failed - json marshal error", err)
|
||||
}
|
||||
|
||||
data, err := CheckResponsePayload(payload)
|
||||
if err != nil {
|
||||
t.Error("Test Failed - CheckBody error", err)
|
||||
}
|
||||
|
||||
expected := `{
|
||||
"stuff": "REAAAAHHHHH"
|
||||
}`
|
||||
|
||||
if string(data) != expected {
|
||||
t.Error("unexpected returned data")
|
||||
}
|
||||
}
|
||||
|
||||
type TestStructLevel0 struct {
|
||||
StringVal string `json:"stringVal"`
|
||||
FloatVal float64 `json:"floatVal"`
|
||||
IntVal int64 `json:"intVal"`
|
||||
StructVal TestStructLevel1 `json:"structVal"`
|
||||
}
|
||||
|
||||
type TestStructLevel1 struct {
|
||||
OkayVal string `json:"okayVal"`
|
||||
OkayVal2 float64 `json:"okayVal2"`
|
||||
BadVal string `json:"user"`
|
||||
BadVal2 int `json:"bsb"`
|
||||
OtherData TestStructLevel2 `json:"otherVals"`
|
||||
}
|
||||
|
||||
type TestStructLevel2 struct {
|
||||
OkayVal string `json:"okayVal"`
|
||||
OkayVal2 float64 `json:"okayVal2"`
|
||||
BadVal float32 `json:"name"`
|
||||
BadVal2 int32 `json:"real_name"`
|
||||
OtherData TestStructLevel3 `json:"moreOtherVals"`
|
||||
}
|
||||
|
||||
type TestStructLevel3 struct {
|
||||
OkayVal string `json:"okayVal"`
|
||||
OkayVal2 float64 `json:"okayVal2"`
|
||||
BadVal int64 `json:"receiver_name"`
|
||||
BadVal2 string `json:"account_number"`
|
||||
}
|
||||
|
||||
func TestCheckJSON(t *testing.T) {
|
||||
level3 := TestStructLevel3{
|
||||
OkayVal: "stuff",
|
||||
OkayVal2: 129219,
|
||||
BadVal: 1337,
|
||||
BadVal2: "Super Secret Password",
|
||||
}
|
||||
|
||||
level2 := TestStructLevel2{
|
||||
OkayVal: "stuff",
|
||||
OkayVal2: 129219,
|
||||
BadVal: 0.222,
|
||||
BadVal2: 1337888888,
|
||||
OtherData: level3,
|
||||
}
|
||||
|
||||
level1 := TestStructLevel1{
|
||||
OkayVal: "stuff",
|
||||
OkayVal2: 120938,
|
||||
BadVal: "CritcalBankingStuff",
|
||||
BadVal2: 1337,
|
||||
OtherData: level2,
|
||||
}
|
||||
|
||||
testVal := TestStructLevel0{
|
||||
StringVal: "somestringstuff",
|
||||
FloatVal: 3.14,
|
||||
IntVal: 1337,
|
||||
StructVal: level1,
|
||||
}
|
||||
|
||||
exclusionList, err := GetExcludedItems()
|
||||
if err != nil {
|
||||
t.Error("Test Failed - GetExcludedItems error", err)
|
||||
}
|
||||
|
||||
vals, err := CheckJSON(testVal, &exclusionList)
|
||||
if err != nil {
|
||||
t.Error("Test Failed - Check JSON error", err)
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(vals)
|
||||
if err != nil {
|
||||
t.Fatal("Test Failed - json marshal error", err)
|
||||
}
|
||||
|
||||
newStruct := TestStructLevel0{}
|
||||
err = json.Unmarshal(payload, &newStruct)
|
||||
if err != nil {
|
||||
t.Fatal("Test Failed - Umarshal error", err)
|
||||
}
|
||||
|
||||
if newStruct.StructVal.BadVal != "" {
|
||||
t.Error("Value not wiped correctly")
|
||||
}
|
||||
|
||||
if newStruct.StructVal.BadVal2 != 0 {
|
||||
t.Error("Value not wiped correctly")
|
||||
}
|
||||
|
||||
if newStruct.StructVal.OtherData.BadVal != 0 {
|
||||
t.Error("Value not wiped correctly")
|
||||
}
|
||||
|
||||
if newStruct.StructVal.OtherData.BadVal2 != 0 {
|
||||
t.Error("Value not wiped correctly")
|
||||
}
|
||||
|
||||
if newStruct.StructVal.OtherData.OtherData.BadVal != 0 {
|
||||
t.Error("Value not wiped correctly")
|
||||
}
|
||||
|
||||
if newStruct.StructVal.OtherData.OtherData.BadVal2 != "" {
|
||||
t.Error("Value not wiped correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExcludedItems(t *testing.T) {
|
||||
exclusionList, err := GetExcludedItems()
|
||||
if err != nil {
|
||||
t.Error("Test Failed - GetExcludedItems error", err)
|
||||
}
|
||||
|
||||
if len(exclusionList.Headers) == 0 {
|
||||
t.Error("Test Failed - Header exclusion list not popoulated")
|
||||
}
|
||||
|
||||
if len(exclusionList.Variables) == 0 {
|
||||
t.Error("Test Failed - Variable exclusion list not popoulated")
|
||||
}
|
||||
}
|
||||
282
exchanges/mock/server.go
Normal file
282
exchanges/mock/server.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
)
|
||||
|
||||
// DefaultDirectory defines the main mock directory
|
||||
const DefaultDirectory = "../../testdata/http_mock/"
|
||||
|
||||
const (
|
||||
contentType = "Content-Type"
|
||||
applicationURLEncoded = "application/x-www-form-urlencoded"
|
||||
applicationJSON = "application/json"
|
||||
textPlain = "text/plain"
|
||||
)
|
||||
|
||||
// VCRMock defines the main mock JSON file and attributes
|
||||
type VCRMock struct {
|
||||
Routes map[string]map[string][]HTTPResponse `json:"routes"`
|
||||
}
|
||||
|
||||
// NewVCRServer starts a new VCR server for replaying HTTP requests for testing
|
||||
// purposes and returns the server connection details
|
||||
func NewVCRServer(path string) (string, *http.Client, error) {
|
||||
if path == "" {
|
||||
return "", nil, errors.New("no path to json mock file found")
|
||||
}
|
||||
|
||||
var mockFile VCRMock
|
||||
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
pathing := common.SplitStrings(path, "/")
|
||||
dirPathing := pathing[:len(pathing)-1]
|
||||
dir := common.JoinStrings(dirPathing, "/")
|
||||
err = common.CreateDir(dir)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
data, jErr := json.MarshalIndent(mockFile, "", " ")
|
||||
if jErr != nil {
|
||||
return "", nil, jErr
|
||||
}
|
||||
|
||||
err = common.WriteFile(path, data)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
contents = data
|
||||
}
|
||||
|
||||
if !json.Valid(contents) {
|
||||
return "",
|
||||
nil,
|
||||
fmt.Errorf("contents of file %s are not valid JSON", path)
|
||||
}
|
||||
|
||||
// Get mocking data for the specific service
|
||||
err = json.Unmarshal(contents, &mockFile)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
newMux := http.NewServeMux()
|
||||
// Range over routes and assign responses to explicit paths and http
|
||||
// methods
|
||||
if len(mockFile.Routes) != 0 {
|
||||
for pattern, mockResponses := range mockFile.Routes {
|
||||
RegisterHandler(pattern, mockResponses, newMux)
|
||||
}
|
||||
} else {
|
||||
newMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
err := json.NewEncoder(w).Encode("There is no mock data available in file please record a new HTTP response. Please follow README.md in the mock package.")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
tlsServer := httptest.NewTLSServer(newMux)
|
||||
|
||||
return tlsServer.URL, tlsServer.Client(), nil
|
||||
}
|
||||
|
||||
// RegisterHandler registers a generalised mock response logic for specific
|
||||
// routes
|
||||
func RegisterHandler(pattern string, mock map[string][]HTTPResponse, mux *http.ServeMux) {
|
||||
mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
|
||||
httpResponses, ok := mock[r.Method]
|
||||
if !ok {
|
||||
log.Fatalf("Mock Test Failure - Method %s not present in mock file",
|
||||
r.Method)
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
vals, err := url.ParseRequestURI(r.RequestURI)
|
||||
if err != nil {
|
||||
log.Fatal("Mock Test Failure - Parse request URI error", err)
|
||||
}
|
||||
|
||||
payload, err := MatchAndGetResponse(httpResponses, vals.Query(), true)
|
||||
if err != nil {
|
||||
log.Fatalf("Mock Test Failure - MatchAndGetResponse error %s for %s",
|
||||
err, r.RequestURI)
|
||||
}
|
||||
|
||||
MessageWriteJSON(w, http.StatusOK, payload)
|
||||
return
|
||||
|
||||
case http.MethodPost:
|
||||
switch r.Header.Get(contentType) {
|
||||
case applicationURLEncoded:
|
||||
readBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Fatal("Mock Test Failure - ReadAll error", err)
|
||||
}
|
||||
|
||||
vals, err := url.ParseQuery(string(readBody))
|
||||
if err != nil {
|
||||
log.Fatal("Mock Test Failure - parse query error", err)
|
||||
}
|
||||
|
||||
payload, err := MatchAndGetResponse(httpResponses, vals, false)
|
||||
if err != nil {
|
||||
log.Fatal("Mock Test Failure - MatchAndGetResponse error ", err)
|
||||
}
|
||||
|
||||
MessageWriteJSON(w, http.StatusOK, payload)
|
||||
return
|
||||
|
||||
case "":
|
||||
payload, err := MatchAndGetResponse(httpResponses, r.URL.Query(), true)
|
||||
if err != nil {
|
||||
log.Fatal("Mock Test Failure - MatchAndGetResponse error ", err)
|
||||
}
|
||||
|
||||
MessageWriteJSON(w, http.StatusOK, payload)
|
||||
return
|
||||
|
||||
case applicationJSON:
|
||||
readBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Mock Test Failure - %v", err)
|
||||
}
|
||||
|
||||
reqVals, err := DeriveURLValsFromJSONMap(readBody)
|
||||
if err != nil {
|
||||
log.Fatalf("Mock Test Failure - %v", err)
|
||||
}
|
||||
|
||||
payload, err := MatchAndGetResponse(httpResponses, reqVals, false)
|
||||
if err != nil {
|
||||
log.Fatal("Mock Test Failure - MatchAndGetResponse error ", err)
|
||||
}
|
||||
|
||||
MessageWriteJSON(w, http.StatusOK, payload)
|
||||
return
|
||||
|
||||
case textPlain:
|
||||
headerData, ok := r.Header["X-Gemini-Payload"]
|
||||
if !ok {
|
||||
log.Fatal("Mock Test Failure - Cannot find header in request")
|
||||
}
|
||||
|
||||
base64data := strings.Join(headerData, "")
|
||||
|
||||
jsonThings, err := common.Base64Decode(base64data)
|
||||
if err != nil {
|
||||
log.Fatal("Mock Test Failure - ", err)
|
||||
}
|
||||
|
||||
reqVals, err := DeriveURLValsFromJSONMap(jsonThings)
|
||||
if err != nil {
|
||||
log.Fatalf("Mock Test Failure - %v", err)
|
||||
}
|
||||
|
||||
payload, err := MatchAndGetResponse(httpResponses, reqVals, false)
|
||||
if err != nil {
|
||||
log.Fatal("Mock Test Failure - MatchAndGetResponse error ", err)
|
||||
}
|
||||
|
||||
MessageWriteJSON(w, http.StatusOK, payload)
|
||||
return
|
||||
|
||||
default:
|
||||
log.Fatalf("Mock Test Failure - Unhandled content type %v",
|
||||
r.Header.Get(contentType))
|
||||
}
|
||||
|
||||
case http.MethodDelete:
|
||||
payload, err := MatchAndGetResponse(httpResponses, r.URL.Query(), true)
|
||||
if err != nil {
|
||||
log.Println(r.URL.Query())
|
||||
log.Fatal("Mock Test Failure - MatchAndGetResponse error ", err)
|
||||
}
|
||||
|
||||
MessageWriteJSON(w, http.StatusOK, payload)
|
||||
return
|
||||
|
||||
default:
|
||||
log.Fatal("Mock Test Failure - Unhandled HTTP method:",
|
||||
r.Header.Get(contentType))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MessageWriteJSON writes JSON to a connection
|
||||
func MessageWriteJSON(w http.ResponseWriter, status int, data interface{}) {
|
||||
w.Header().Set(contentType, applicationJSON)
|
||||
w.WriteHeader(status)
|
||||
if data != nil {
|
||||
err := json.NewEncoder(w).Encode(data)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
log.Fatal("Mock Test Failure - JSON encode error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MatchAndGetResponse matches incoming request values with mockdata response
|
||||
// values and returns the payload
|
||||
func MatchAndGetResponse(mockData []HTTPResponse, requestVals url.Values, isQueryData bool) (json.RawMessage, error) {
|
||||
for i := range mockData {
|
||||
var data string
|
||||
if isQueryData {
|
||||
data = mockData[i].QueryString
|
||||
} else {
|
||||
data = mockData[i].BodyParams
|
||||
}
|
||||
|
||||
var mockVals = url.Values{}
|
||||
var err error
|
||||
if json.Valid([]byte(data)) {
|
||||
something := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(data), &something)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range something {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
mockVals.Add(k, val)
|
||||
case bool:
|
||||
mockVals.Add(k, strconv.FormatBool(val))
|
||||
case float64:
|
||||
mockVals.Add(k, strconv.FormatFloat(val, 'f', -1, 64))
|
||||
case map[string]interface{}, []interface{}, nil:
|
||||
mockVals.Add(k, fmt.Sprintf("%v", val))
|
||||
default:
|
||||
log.Println(reflect.TypeOf(val))
|
||||
log.Fatal("unhandled type please add as needed")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mockVals, err = url.ParseQuery(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if MatchURLVals(mockVals, requestVals) {
|
||||
return mockData[i].Data, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("no data could be matched")
|
||||
}
|
||||
117
exchanges/mock/server_test.go
Normal file
117
exchanges/mock/server_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
)
|
||||
|
||||
type responsePayload struct {
|
||||
Price float64 `json:"price"`
|
||||
Amount float64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
}
|
||||
|
||||
const queryString = "currency=btc&command=getprice"
|
||||
const testFile = "test.json"
|
||||
|
||||
func TestNewVCRServer(t *testing.T) {
|
||||
_, _, err := NewVCRServer("")
|
||||
if err == nil {
|
||||
t.Error("Test Failed - NewVCRServer error cannot be nil")
|
||||
}
|
||||
|
||||
// Set up mock data
|
||||
test1 := VCRMock{}
|
||||
test1.Routes = make(map[string]map[string][]HTTPResponse)
|
||||
test1.Routes["/test"] = make(map[string][]HTTPResponse)
|
||||
|
||||
rp, err := json.Marshal(responsePayload{Price: 8000.0,
|
||||
Amount: 1,
|
||||
Currency: "bitcoin"})
|
||||
if err != nil {
|
||||
t.Fatal("Test Failed - marshal error", err)
|
||||
}
|
||||
|
||||
testValue := HTTPResponse{Data: rp, QueryString: queryString, BodyParams: queryString}
|
||||
test1.Routes["/test"][http.MethodGet] = []HTTPResponse{testValue}
|
||||
|
||||
payload, err := json.Marshal(test1)
|
||||
if err != nil {
|
||||
t.Fatal("Test Failed - marshal error", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(testFile, payload, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatal("Test Failed - marshal error", err)
|
||||
}
|
||||
|
||||
deets, client, err := NewVCRServer(testFile)
|
||||
if err != nil {
|
||||
t.Error("Test Failed - NewVCRServer error", err)
|
||||
}
|
||||
|
||||
common.HTTPClient = client // Set common package global HTTP Client
|
||||
|
||||
_, err = common.SendHTTPRequest(http.MethodGet,
|
||||
"http://localhost:300/somethingElse?"+queryString,
|
||||
nil,
|
||||
bytes.NewBufferString(""))
|
||||
if err == nil {
|
||||
t.Error("Test Failed - Sending http request expected an error")
|
||||
}
|
||||
|
||||
// Expected good outcome
|
||||
r, err := common.SendHTTPRequest(http.MethodGet,
|
||||
deets,
|
||||
nil,
|
||||
bytes.NewBufferString(""))
|
||||
if err != nil {
|
||||
t.Error("Test Failed - Sending http request error", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(r, "404 page not found") {
|
||||
t.Error("Test Failed - Was not expecting any value returned:", r)
|
||||
}
|
||||
|
||||
r, err = common.SendHTTPRequest(http.MethodGet,
|
||||
deets+"/test?"+queryString,
|
||||
nil,
|
||||
bytes.NewBufferString(""))
|
||||
if err != nil {
|
||||
t.Error("Test Failed - Sending http request error", err)
|
||||
}
|
||||
|
||||
var res responsePayload
|
||||
err = json.Unmarshal([]byte(r), &res)
|
||||
if err != nil {
|
||||
t.Error("Test Failed - unmarshal error", err)
|
||||
}
|
||||
|
||||
if res.Price != 8000 {
|
||||
t.Error("Test Failed - response error expected 8000 but received:",
|
||||
res.Price)
|
||||
}
|
||||
|
||||
if res.Amount != 1 {
|
||||
t.Error("Test Failed - response error expected 1 but received:",
|
||||
res.Amount)
|
||||
}
|
||||
|
||||
if res.Currency != "bitcoin" {
|
||||
t.Error("Test Failed - response error expected \"bitcoin\" but received:",
|
||||
res.Currency)
|
||||
}
|
||||
|
||||
// clean up test.json file
|
||||
err = os.Remove(testFile)
|
||||
if err != nil {
|
||||
t.Fatal("Test Failed - Remove error", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user