package jsonrpc import ( "fmt" "io/ioutil" "net/http" "net/http/httptest" "os" "testing" . "github.com/onsi/gomega" ) // needed to retrieve requests that arrived at httpServer for further investigation var requestChan = make(chan *RequestData, 1) // the request datastructure that can be retrieved for test assertions type RequestData struct { request *http.Request body string } // set the response body the httpServer should return for the next request var responseBody = "" var httpServer *httptest.Server // start the testhttp server and stop it when tests are finished func TestMain(m *testing.M) { httpServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { data, _ := ioutil.ReadAll(r.Body) defer r.Body.Close() // put request and body to channel for the client to investigate them requestChan <- &RequestData{r, string(data)} fmt.Fprintf(w, responseBody) })) defer httpServer.Close() os.Exit(m.Run()) } func TestSimpleRpcCallHeaderCorrect(t *testing.T) { RegisterTestingT(t) rpcClient := NewClient(httpServer.URL) rpcClient.Call("add", 1, 2) req := (<-requestChan).request Expect(req.Method).To(Equal("POST")) Expect(req.Header.Get("Content-Type")).To(Equal("application/json")) Expect(req.Header.Get("Accept")).To(Equal("application/json")) } // test if the structure of an rpc request is built correctly by validating the data that arrived on the test server func TestRpcClient_Call(t *testing.T) { RegisterTestingT(t) rpcClient := NewClient(httpServer.URL) person := Person{ Name: "Alex", Age: 35, Country: "Germany", } drink := Drink{ Name: "Cuba Libre", Ingredients: []string{"rum", "cola"}, } rpcClient.Call("missingParam") Expect((<-requestChan).body).To(Equal(`{"method":"missingParam","id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("nullParam", nil) Expect((<-requestChan).body).To(Equal(`{"method":"nullParam","params":[null],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("nullParams", nil, nil) Expect((<-requestChan).body).To(Equal(`{"method":"nullParams","params":[null,null],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("emptyParams", []interface{}{}) Expect((<-requestChan).body).To(Equal(`{"method":"emptyParams","params":[],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("emptyAnyParams", []string{}) Expect((<-requestChan).body).To(Equal(`{"method":"emptyAnyParams","params":[],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("emptyObject", struct{}{}) Expect((<-requestChan).body).To(Equal(`{"method":"emptyObject","params":{},"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("emptyObjectList", []struct{}{{}, {}}) Expect((<-requestChan).body).To(Equal(`{"method":"emptyObjectList","params":[{},{}],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("boolParam", true) Expect((<-requestChan).body).To(Equal(`{"method":"boolParam","params":[true],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("boolParams", true, false, true) Expect((<-requestChan).body).To(Equal(`{"method":"boolParams","params":[true,false,true],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("stringParam", "Alex") Expect((<-requestChan).body).To(Equal(`{"method":"stringParam","params":["Alex"],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("stringParams", "JSON", "RPC") Expect((<-requestChan).body).To(Equal(`{"method":"stringParams","params":["JSON","RPC"],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("numberParam", 123) Expect((<-requestChan).body).To(Equal(`{"method":"numberParam","params":[123],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("numberParams", 123, 321) Expect((<-requestChan).body).To(Equal(`{"method":"numberParams","params":[123,321],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("floatParam", 1.23) Expect((<-requestChan).body).To(Equal(`{"method":"floatParam","params":[1.23],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("floatParams", 1.23, 3.21) Expect((<-requestChan).body).To(Equal(`{"method":"floatParams","params":[1.23,3.21],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("manyParams", "Alex", 35, true, nil, 2.34) Expect((<-requestChan).body).To(Equal(`{"method":"manyParams","params":["Alex",35,true,null,2.34],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("emptyMissingPublicFieldObject", struct{ name string }{name: "Alex"}) Expect((<-requestChan).body).To(Equal(`{"method":"emptyMissingPublicFieldObject","params":{},"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("singleStruct", person) Expect((<-requestChan).body).To(Equal(`{"method":"singleStruct","params":{"name":"Alex","age":35,"country":"Germany"},"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("singlePointerToStruct", &person) Expect((<-requestChan).body).To(Equal(`{"method":"singlePointerToStruct","params":{"name":"Alex","age":35,"country":"Germany"},"id":0,"jsonrpc":"2.0"}`)) pp := &person rpcClient.Call("doublePointerStruct", &pp) Expect((<-requestChan).body).To(Equal(`{"method":"doublePointerStruct","params":{"name":"Alex","age":35,"country":"Germany"},"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("multipleStructs", person, &drink) Expect((<-requestChan).body).To(Equal(`{"method":"multipleStructs","params":[{"name":"Alex","age":35,"country":"Germany"},{"name":"Cuba Libre","ingredients":["rum","cola"]}],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("singleStructInArray", []interface{}{person}) Expect((<-requestChan).body).To(Equal(`{"method":"singleStructInArray","params":[{"name":"Alex","age":35,"country":"Germany"}],"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("namedParameters", map[string]interface{}{ "name": "Alex", "age": 35, }) Expect((<-requestChan).body).To(Equal(`{"method":"namedParameters","params":{"age":35,"name":"Alex"},"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("anonymousStructNoTags", struct { Name string Age int }{"Alex", 33}) Expect((<-requestChan).body).To(Equal(`{"method":"anonymousStructNoTags","params":{"Name":"Alex","Age":33},"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("anonymousStructWithTags", struct { Name string `json:"name"` Age int `json:"age"` }{"Alex", 33}) Expect((<-requestChan).body).To(Equal(`{"method":"anonymousStructWithTags","params":{"name":"Alex","age":33},"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("structWithNullField", struct { Name string `json:"name"` Address *string `json:"address"` }{"Alex", nil}) Expect((<-requestChan).body).To(Equal(`{"method":"structWithNullField","params":{"name":"Alex","address":null},"id":0,"jsonrpc":"2.0"}`)) rpcClient.Call("nestedStruct", Planet{ Name: "Mars", Properties: Properties{ Distance: 54600000, Color: "red", }, }) Expect((<-requestChan).body).To(Equal(`{"method":"nestedStruct","params":{"name":"Mars","properties":{"distance":54600000,"color":"red"}},"id":0,"jsonrpc":"2.0"}`)) } func TestRpcClient_CallBatch(t *testing.T) { RegisterTestingT(t) rpcClient := NewClient(httpServer.URL) person := Person{ Name: "Alex", Age: 35, Country: "Germany", } drink := Drink{ Name: "Cuba Libre", Ingredients: []string{"rum", "cola"}, } // invalid parameters are possible by manually defining *RPCRequest rpcClient.CallBatch(RPCRequests{ { Method: "singleRequest", Params: 3, // invalid, should be []int{3} }, }) Expect((<-requestChan).body).To(Equal(`[{"method":"singleRequest","params":3,"id":0,"jsonrpc":"2.0"}]`)) // better use Params() unless you know what you are doing rpcClient.CallBatch(RPCRequests{ { Method: "singleRequest", Params: Params(3), // always valid json rpc }, }) Expect((<-requestChan).body).To(Equal(`[{"method":"singleRequest","params":[3],"id":0,"jsonrpc":"2.0"}]`)) // even better, use NewRequest() rpcClient.CallBatch(RPCRequests{ NewRequest("multipleRequests1", 1), NewRequest("multipleRequests2", 2), NewRequest("multipleRequests3", 3), }) Expect((<-requestChan).body).To(Equal(`[{"method":"multipleRequests1","params":[1],"id":0,"jsonrpc":"2.0"},{"method":"multipleRequests2","params":[2],"id":1,"jsonrpc":"2.0"},{"method":"multipleRequests3","params":[3],"id":2,"jsonrpc":"2.0"}]`)) // test a huge batch request requests := RPCRequests{ NewRequest("nullParam", nil), NewRequest("nullParams", nil, nil), NewRequest("emptyParams", []interface{}{}), NewRequest("emptyAnyParams", []string{}), NewRequest("emptyObject", struct{}{}), NewRequest("emptyObjectList", []struct{}{{}, {}}), NewRequest("boolParam", true), NewRequest("boolParams", true, false, true), NewRequest("stringParam", "Alex"), NewRequest("stringParams", "JSON", "RPC"), NewRequest("numberParam", 123), NewRequest("numberParams", 123, 321), NewRequest("floatParam", 1.23), NewRequest("floatParams", 1.23, 3.21), NewRequest("manyParams", "Alex", 35, true, nil, 2.34), NewRequest("emptyMissingPublicFieldObject", struct{ name string }{name: "Alex"}), NewRequest("singleStruct", person), NewRequest("singlePointerToStruct", &person), NewRequest("multipleStructs", person, &drink), NewRequest("singleStructInArray", []interface{}{person}), NewRequest("namedParameters", map[string]interface{}{ "name": "Alex", "age": 35, }), NewRequest("anonymousStructNoTags", struct { Name string Age int }{"Alex", 33}), NewRequest("anonymousStructWithTags", struct { Name string `json:"name"` Age int `json:"age"` }{"Alex", 33}), NewRequest("structWithNullField", struct { Name string `json:"name"` Address *string `json:"address"` }{"Alex", nil}), } rpcClient.CallBatch(requests) Expect((<-requestChan).body).To(Equal(`[{"method":"nullParam","params":[null],"id":0,"jsonrpc":"2.0"},` + `{"method":"nullParams","params":[null,null],"id":1,"jsonrpc":"2.0"},` + `{"method":"emptyParams","params":[],"id":2,"jsonrpc":"2.0"},` + `{"method":"emptyAnyParams","params":[],"id":3,"jsonrpc":"2.0"},` + `{"method":"emptyObject","params":{},"id":4,"jsonrpc":"2.0"},` + `{"method":"emptyObjectList","params":[{},{}],"id":5,"jsonrpc":"2.0"},` + `{"method":"boolParam","params":[true],"id":6,"jsonrpc":"2.0"},` + `{"method":"boolParams","params":[true,false,true],"id":7,"jsonrpc":"2.0"},` + `{"method":"stringParam","params":["Alex"],"id":8,"jsonrpc":"2.0"},` + `{"method":"stringParams","params":["JSON","RPC"],"id":9,"jsonrpc":"2.0"},` + `{"method":"numberParam","params":[123],"id":10,"jsonrpc":"2.0"},` + `{"method":"numberParams","params":[123,321],"id":11,"jsonrpc":"2.0"},` + `{"method":"floatParam","params":[1.23],"id":12,"jsonrpc":"2.0"},` + `{"method":"floatParams","params":[1.23,3.21],"id":13,"jsonrpc":"2.0"},` + `{"method":"manyParams","params":["Alex",35,true,null,2.34],"id":14,"jsonrpc":"2.0"},` + `{"method":"emptyMissingPublicFieldObject","params":{},"id":15,"jsonrpc":"2.0"},` + `{"method":"singleStruct","params":{"name":"Alex","age":35,"country":"Germany"},"id":16,"jsonrpc":"2.0"},` + `{"method":"singlePointerToStruct","params":{"name":"Alex","age":35,"country":"Germany"},"id":17,"jsonrpc":"2.0"},` + `{"method":"multipleStructs","params":[{"name":"Alex","age":35,"country":"Germany"},{"name":"Cuba Libre","ingredients":["rum","cola"]}],"id":18,"jsonrpc":"2.0"},` + `{"method":"singleStructInArray","params":[{"name":"Alex","age":35,"country":"Germany"}],"id":19,"jsonrpc":"2.0"},` + `{"method":"namedParameters","params":{"age":35,"name":"Alex"},"id":20,"jsonrpc":"2.0"},` + `{"method":"anonymousStructNoTags","params":{"Name":"Alex","Age":33},"id":21,"jsonrpc":"2.0"},` + `{"method":"anonymousStructWithTags","params":{"name":"Alex","age":33},"id":22,"jsonrpc":"2.0"},` + `{"method":"structWithNullField","params":{"name":"Alex","address":null},"id":23,"jsonrpc":"2.0"}]`)) // create batch manually requests = []*RPCRequest{ { Method: "myMethod1", Params: []int{1}, ID: 123, // will be forced to requests[i].ID == i unless you use CallBatchRaw JSONRPC: "7.0", // will be forced to "2.0" unless you use CallBatchRaw }, { Method: "myMethod2", Params: &person, ID: 321, // will be forced to requests[i].ID == i unless you use CallBatchRaw JSONRPC: "wrong", // will be forced to "2.0" unless you use CallBatchRaw }, } rpcClient.CallBatch(requests) Expect((<-requestChan).body).To(Equal(`[{"method":"myMethod1","params":[1],"id":0,"jsonrpc":"2.0"},` + `{"method":"myMethod2","params":{"name":"Alex","age":35,"country":"Germany"},"id":1,"jsonrpc":"2.0"}]`)) // use raw batch requests = []*RPCRequest{ { Method: "myMethod1", Params: []int{1}, ID: 123, JSONRPC: "7.0", }, { Method: "myMethod2", Params: &person, ID: 321, JSONRPC: "wrong", }, } rpcClient.CallBatchRaw(requests) Expect((<-requestChan).body).To(Equal(`[{"method":"myMethod1","params":[1],"id":123,"jsonrpc":"7.0"},` + `{"method":"myMethod2","params":{"name":"Alex","age":35,"country":"Germany"},"id":321,"jsonrpc":"wrong"}]`)) } // test if the result of an an rpc request is parsed correctly and if errors are thrown correctly func TestRpcJsonResponseStruct(t *testing.T) { RegisterTestingT(t) rpcClient := NewClient(httpServer.URL) // empty return body is an error responseBody = `` res, err := rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).NotTo(BeNil()) Expect(res).To(BeNil()) // not a json body is an error responseBody = `{ "not": "a", "json": "object"` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).NotTo(BeNil()) Expect(res).To(BeNil()) // field "anotherField" not allowed in rpc response is an error responseBody = `{ "anotherField": "norpc"}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).NotTo(BeNil()) Expect(res).To(BeNil()) // TODO: result must contain one of "result", "error" // TODO: is there an efficient way to do this? /*responseBody = `{}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).NotTo(BeNil()) Expect(res).To(BeNil())*/ // result null is ok responseBody = `{"result": null}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Result).To(BeNil()) Expect(res.Error).To(BeNil()) // error null is ok responseBody = `{"error": null}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Result).To(BeNil()) Expect(res.Error).To(BeNil()) // result and error null is ok responseBody = `{"result": null, "error": null}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Result).To(BeNil()) Expect(res.Error).To(BeNil()) // TODO: result must not contain both of "result", "error" != null // TODO: is there an efficient way to do this? /*responseBody = `{ "result": 123, "error": {"code": 123, "message": "something wrong"}}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).NotTo(BeNil()) Expect(res).To(BeNil())*/ // result string is ok responseBody = `{"result": "ok"}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Result).To(Equal("ok")) // result with error null is ok responseBody = `{"result": "ok", "error": null}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Result).To(Equal("ok")) // error with result null is ok responseBody = `{"error": {"code": 123, "message": "something wrong"}, "result": null}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Result).To(BeNil()) Expect(res.Error.Code).To(Equal(123)) Expect(res.Error.Message).To(Equal("something wrong")) // TODO: empty error is not ok, must at least contain code and message /*responseBody = `{ "error": {}}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Result).To(BeNil()) Expect(res.Error).NotTo(BeNil())*/ // TODO: only code in error is not ok, must at least contain code and message /*responseBody = `{ "error": {"code": 123}}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Result).To(BeNil()) Expect(res.Error).NotTo(BeNil())*/ // TODO: only message in error is not ok, must at least contain code and message /*responseBody = `{ "error": {"message": "something wrong"}}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Result).To(BeNil()) Expect(res.Error).NotTo(BeNil())*/ // error with code and message is ok responseBody = `{ "error": {"code": 123, "message": "something wrong"}}` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Result).To(BeNil()) Expect(res.Error.Code).To(Equal(123)) Expect(res.Error.Message).To(Equal("something wrong")) // check results // should return int correctly responseBody = `{ "result": 1 }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) i, err := res.GetInt() Expect(err).To(BeNil()) Expect(i).To(Equal(int64(1))) // error on wrong type i = 3 responseBody = `{ "result": "notAnInt" }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) i, err = res.GetInt() Expect(err).NotTo(BeNil()) Expect(i).To(Equal(int64(0))) // error on result null i = 3 responseBody = `{ "result": null }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) i, err = res.GetInt() Expect(err).NotTo(BeNil()) Expect(i).To(Equal(int64(0))) b := false responseBody = `{ "result": true }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) b, err = res.GetBool() Expect(err).To(BeNil()) Expect(b).To(Equal(true)) b = true responseBody = `{ "result": 123 }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) b, err = res.GetBool() Expect(err).NotTo(BeNil()) Expect(b).To(Equal(false)) var p *Person responseBody = `{ "result": {"name": "Alex", "age": 35, "anotherField": "something"} }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p) Expect(err).To(BeNil()) Expect(p.Name).To(Equal("Alex")) Expect(p.Age).To(Equal(35)) Expect(p.Country).To(Equal("")) // TODO: How to check if result could be parsed or if it is default? p = nil responseBody = `{ "result": {"anotherField": "something"} }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p) Expect(err).To(BeNil()) Expect(p).NotTo(BeNil()) // TODO: HERE###### var pp *PointerFieldPerson responseBody = `{ "result": {"anotherField": "something", "country": "Germany"} }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&pp) Expect(err).To(BeNil()) Expect(pp.Name).To(BeNil()) Expect(pp.Age).To(BeNil()) Expect(*pp.Country).To(Equal("Germany")) p = nil responseBody = `{ "result": null }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p) Expect(err).To(BeNil()) Expect(p).To(BeNil()) // passing nil is an error p = nil responseBody = `{ "result": null }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(p) Expect(err).NotTo(BeNil()) Expect(p).To(BeNil()) p2 := &Person{ Name: "Alex", } responseBody = `{ "result": null }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p2) Expect(err).To(BeNil()) Expect(p2).To(BeNil()) p2 = &Person{ Name: "Alex", } responseBody = `{ "result": {"age": 35} }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(p2) Expect(err).To(BeNil()) Expect(p2.Name).To(Equal("Alex")) Expect(p2.Age).To(Equal(35)) // prefilled struct is kept on no result p3 := Person{ Name: "Alex", } responseBody = `{ "result": null }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p3) Expect(err).To(BeNil()) Expect(p3.Name).To(Equal("Alex")) // prefilled struct is extended / overwritten p3 = Person{ Name: "Alex", Age: 123, } responseBody = `{ "result": {"age": 35, "country": "Germany"} }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p3) Expect(err).To(BeNil()) Expect(p3.Name).To(Equal("Alex")) Expect(p3.Age).To(Equal(35)) Expect(p3.Country).To(Equal("Germany")) // nil is an error responseBody = `{ "result": {"age": 35} }` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(nil) Expect(err).NotTo(BeNil()) } func TestRpcBatchJsonResponseStruct(t *testing.T) { RegisterTestingT(t) rpcClient := NewClient(httpServer.URL) // empty return body is an error responseBody = `` res, err := rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).NotTo(BeNil()) Expect(res).To(BeNil()) // not a json body is an error responseBody = `{ "not": "a", "json": "object"` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).NotTo(BeNil()) Expect(res).To(BeNil()) // field "anotherField" not allowed in rpc response is an error responseBody = `{ "anotherField": "norpc"}` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).NotTo(BeNil()) Expect(res).To(BeNil()) // TODO: result must contain one of "result", "error" // TODO: is there an efficient way to do this? /*responseBody = `[{}]` res, err = rpcClient.Call("something", 1, 2, 3) <-requestChan Expect(err).NotTo(BeNil()) Expect(res).To(BeNil())*/ // result must be wrapped in array on batch request responseBody = `{"result": null}` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err.Error()).NotTo(BeNil()) // result ok since in array responseBody = `[{"result": null}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(len(res)).To(Equal(1)) Expect(res[0].Result).To(BeNil()) // error null is ok responseBody = `[{"error": null}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Result).To(BeNil()) Expect(res[0].Error).To(BeNil()) // result and error null is ok responseBody = `[{"result": null, "error": null}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Result).To(BeNil()) Expect(res[0].Error).To(BeNil()) // TODO: result must not contain both of "result", "error" != null // TODO: is there an efficient way to do this? /*responseBody = `[{ "result": 123, "error": {"code": 123, "message": "something wrong"}}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something",1, 2, 3), }) <-requestChan Expect(err).NotTo(BeNil()) Expect(res).To(BeNil())*/ // result string is ok responseBody = `[{"result": "ok","id":0}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Result).To(Equal("ok")) Expect(res[0].ID).To(Equal(0)) // result with error null is ok responseBody = `[{"result": "ok", "error": null}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Result).To(Equal("ok")) // error with result null is ok responseBody = `[{"error": {"code": 123, "message": "something wrong"}, "result": null}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Result).To(BeNil()) Expect(res[0].Error.Code).To(Equal(123)) Expect(res[0].Error.Message).To(Equal("something wrong")) // TODO: empty error is not ok, must at least contain code and message /*responseBody = `[{ "error": {}}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something",1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Result).To(BeNil()) Expect(res[0].Error).NotTo(BeNil())*/ /* // TODO: only code in error is not ok, must at least contain code and message */ /*responseBody = `[{ "error": {"code": 123}}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something",1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Result).To(BeNil()) Expect(res[0].Error).NotTo(BeNil())*/ /* // TODO: only message in error is not ok, must at least contain code and message */ /*responseBody = `[{ "error": {"message": "something wrong"}}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something",1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Result).To(BeNil()) Expect(res[0].Error).NotTo(BeNil())*/ // error with code and message is ok responseBody = `[{ "error": {"code": 123, "message": "something wrong"}}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Result).To(BeNil()) Expect(res[0].Error.Code).To(Equal(123)) Expect(res[0].Error.Message).To(Equal("something wrong")) // check results // should return int correctly responseBody = `[{ "result": 1 }]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Error).To(BeNil()) i, err := res[0].GetInt() Expect(err).To(BeNil()) Expect(i).To(Equal(int64(1))) // error on wrong type i = 3 responseBody = `[{ "result": "notAnInt" }]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Error).To(BeNil()) i, err = res[0].GetInt() Expect(err).NotTo(BeNil()) Expect(i).To(Equal(int64(0))) var p *Person responseBody = `[{"id":0, "result": {"name": "Alex", "age": 35}}, {"id":2, "result": {"name": "Lena", "age": 2}}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res[0].Error).To(BeNil()) Expect(res[0].ID).To(Equal(0)) Expect(res[1].Error).To(BeNil()) Expect(res[1].ID).To(Equal(2)) err = res[0].GetObject(&p) Expect(p.Name).To(Equal("Alex")) Expect(p.Age).To(Equal(35)) err = res[1].GetObject(&p) Expect(p.Name).To(Equal("Lena")) Expect(p.Age).To(Equal(2)) // check if error occurred responseBody = `[{ "result": "someresult", "error": null}, { "result": null, "error": {"code": 123, "message": "something wrong"}}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res.HasError()).To(BeTrue()) // check if error occurred responseBody = `[{ "result": null, "error": {"code": 123, "message": "something wrong"}}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res.HasError()).To(BeTrue()) // check if error occurred responseBody = `[{ "result": null, "error": {"code": 123, "message": "something wrong"}}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res.HasError()).To(BeTrue()) // check if response mapping works responseBody = `[{ "id":123,"result": 123},{ "id":1,"result": 1}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res.HasError()).To(BeFalse()) resMap := res.AsMap() int1, _ := resMap[1].GetInt() int123, _ := resMap[123].GetInt() Expect(int1).To(Equal(int64(1))) Expect(int123).To(Equal(int64(123))) // check if getByID works int123, _ = res.GetByID(123).GetInt() Expect(int123).To(Equal(int64(123))) // check if error occurred responseBody = `[{ "result": null, "error": {"code": 123, "message": "something wrong"}}]` res, err = rpcClient.CallBatch(RPCRequests{ NewRequest("something", 1, 2, 3), }) <-requestChan Expect(err).To(BeNil()) Expect(res.HasError()).To(BeTrue()) /* // TODO: How to check if result could be parsed or if it is default? p = nil responseBody = `{ "result": {"anotherField": "something"} }` res, err = rpcClient.CallBatch(RPCRequests{ {"something", Params(1, 2, 3)}, }) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p) Expect(err).To(BeNil()) Expect(p).NotTo(BeNil()) // TODO: HERE###### var pp *PointerFieldPerson responseBody = `{ "result": {"anotherField": "something", "country": "Germany"} }` res, err = rpcClient.CallBatch(RPCRequests{ {"something", Params(1, 2, 3)}, }) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&pp) Expect(err).To(BeNil()) Expect(pp.Name).To(BeNil()) Expect(pp.Age).To(BeNil()) Expect(*pp.Country).To(Equal("Germany")) p = nil responseBody = `{ "result": null }` res, err = rpcClient.CallBatch(RPCRequests{ {"something", Params(1, 2, 3)}, }) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p) Expect(err).To(BeNil()) Expect(p).To(BeNil()) // passing nil is an error p = nil responseBody = `{ "result": null }` res, err = rpcClient.CallBatch(RPCRequests{ {"something", Params(1, 2, 3)}, }) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(p) Expect(err).NotTo(BeNil()) Expect(p).To(BeNil()) p2 := &Person{ Name: "Alex", } responseBody = `{ "result": null }` res, err = rpcClient.CallBatch(RPCRequests{ {"something", Params(1, 2, 3)}, }) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p2) Expect(err).To(BeNil()) Expect(p2).To(BeNil()) p2 = &Person{ Name: "Alex", } responseBody = `{ "result": {"age": 35} }` res, err = rpcClient.CallBatch(RPCRequests{ {"something", Params(1, 2, 3)}, }) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(p2) Expect(err).To(BeNil()) Expect(p2.Name).To(Equal("Alex")) Expect(p2.Age).To(Equal(35)) // prefilled struct is kept on no result p3 := Person{ Name: "Alex", } responseBody = `{ "result": null }` res, err = rpcClient.CallBatch(RPCRequests{ {"something", Params(1, 2, 3)}, }) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p3) Expect(err).To(BeNil()) Expect(p3.Name).To(Equal("Alex")) // prefilled struct is extended / overwritten p3 = Person{ Name: "Alex", Age: 123, } responseBody = `{ "result": {"age": 35, "country": "Germany"} }` res, err = rpcClient.CallBatch(RPCRequests{ {"something", Params(1, 2, 3)}, }) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(&p3) Expect(err).To(BeNil()) Expect(p3.Name).To(Equal("Alex")) Expect(p3.Age).To(Equal(35)) Expect(p3.Country).To(Equal("Germany")) // nil is an error responseBody = `{ "result": {"age": 35} }` res, err = rpcClient.CallBatch(RPCRequests{ {"something", Params(1, 2, 3)}, }) <-requestChan Expect(err).To(BeNil()) Expect(res.Error).To(BeNil()) err = res.GetObject(nil) Expect(err).NotTo(BeNil()) */ } func TestRpcClient_CallFor(t *testing.T) { RegisterTestingT(t) rpcClient := NewClient(httpServer.URL) i := 0 responseBody = `{"result":3,"id":0,"jsonrpc":"2.0"}` err := rpcClient.CallFor(&i, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(i).To(Equal(3)) /* i = 3 responseBody = `{"result":null,"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(&i, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) // i is not modified when result is empty since null (nil) value cannot be stored in int Expect(i).To(Equal(3)) var pi *int responseBody = `{"result":4,"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(pi, "something", 1, 2, 3) <-requestChan Expect(err).NotTo(BeNil()) Expect(pi).To(BeNil()) responseBody = `{"result":4,"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(&pi, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) Expect(*pi).To(Equal(4)) *pi = 3 responseBody = `{"result":null,"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(&pi, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) // since pi has a value it is not overwritten by null result Expect(pi).To(BeNil()) p := &Person{} responseBody = `{"result":null,"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(p, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) // p is not changed since it has a value and result is null Expect(p).NotTo(BeNil()) var p2 *Person responseBody = `{"result":null,"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(p2, "something", 1, 2, 3) <-requestChan Expect(err).NotTo(BeNil()) // p is not changed since it has a value and result is null Expect(p2).To(BeNil()) p3 := Person{} responseBody = `{"result":null,"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(&p3, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) // p is not changed since it has a value and result is null Expect(p).NotTo(BeNil()) p = &Person{Age: 35} responseBody = `{"result":{"name":"Alex"},"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(p, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) // p is not changed since it has a value and result is null Expect(p.Name).To(Equal("Alex")) Expect(p.Age).To(Equal(35)) p2 = nil responseBody = `{"result":{"name":"Alex"},"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(p2, "something", 1, 2, 3) <-requestChan Expect(err).NotTo(BeNil()) // p is not changed since it has a value and result is null Expect(p2).To(BeNil()) p2 = nil responseBody = `{"result":{"name":"Alex"},"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(&p2, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) // p is not changed since it has a value and result is null Expect(p2).NotTo(BeNil()) Expect(p2.Name).To(Equal("Alex")) p3 = Person{Age: 35} responseBody = `{"result":{"name":"Alex"},"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(&p3, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) // p is not changed since it has a value and result is null Expect(p.Name).To(Equal("Alex")) Expect(p.Age).To(Equal(35)) p3 = Person{Age: 35} responseBody = `{"result":{"name":"Alex"},"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(&p3, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) // p is not changed since it has a value and result is null Expect(p.Name).To(Equal("Alex")) Expect(p.Age).To(Equal(35)) var intArray []int responseBody = `{"result":[1, 2, 3],"id":0,"jsonrpc":"2.0"}` err = rpcClient.CallFor(&intArray, "something", 1, 2, 3) <-requestChan Expect(err).To(BeNil()) // p is not changed since it has a value and result is null Expect(intArray).To(ContainElement(1)) Expect(intArray).To(ContainElement(2)) Expect(intArray).To(ContainElement(3))*/ } type Person struct { Name string `json:"name"` Age int `json:"age"` Country string `json:"country"` } type PointerFieldPerson struct { Name *string `json:"name"` Age *int `json:"age"` Country *string `json:"country"` } type Drink struct { Name string `json:"name"` Ingredients []string `json:"ingredients"` } type Planet struct { Name string `json:"name"` Properties Properties `json:"properties"` } type Properties struct { Distance int `json:"distance"` Color string `json:"color"` }