I'm writing my first API endpoint in GoLang using GRPC/proto-buffers. I'm rather new to GoLang. Below is the API in action in the happy case:
$ grpcurl -d '{
"field1": "A",
}' -plaintext localhost:11000 myteam.myprject.v1.MyProjectAPI/Endpoint
Response is Success:
{
"message": "success"
}
Below is the API in action in the unhappy case:
$ grpcurl -d '{}' -plaintext localhost:11000 myteam.myprject.v1.MyProjectAPI/Endpoint
Response is Failure:
ERROR:
Code: InvalidArgument
Message: Required parameter 'field1' not provided
This is exactly correct behavior based on my application function shown below:
func (a *APIv1) Endpoint(ctx context.Context, msg *myprojectv1.EndpointRequest) (*myprojectv1.EndpointResponse, error) {
if msg.Field1 == "" {
return nil, status.Error(codes.InvalidArgument, "Required parameter 'field1' not provided")
}
return &myprojectv1.EndpointResponse{Message: "success"}, nil
}
I have the following two test-cases to test the happy path and unhappy path:
func TestEndpoint(t *testing.T) {
myApiv1 := myprojecthandlersv1.New()
t.Run("Success", func(t *testing.T) {
res, err := myApiv1.Endpoint(context.Background(), &myprojectv1.EndpointRequest{
Id: "A",
})
require.Nil(t, err)
require.Equal(t, "success", res.Message)
})
t.Run("Missing argument id", func(t *testing.T) {
_, err := myApiv1.Endpoint(context.Background(), &myprojectv1.EndpointRequest{
})
require.NotNil(t, err)
require.Equal(t, codes.InvalidArgument, /* WHAT DO I PUT HERE?? */)
require.Equal(t, "Required parameter 'field1' not provided", /* WHAT DO I PUT HERE?? */)
})
}
But I do not know how to test the value of the error in the Test case.
How can I test that the Code == InvalidArgument and Message == Required parameter 'field1' not provided?
You can forge the same error as you expect and then compare err you've got with it like this:
expectedErr := status.Error(codes.InvalidArgument, "Required parameter 'field1' not provided")
_, err := myApiv1.Endpoint(context.Background(), &myprojectv1.EndpointRequest{})
require.NotNil(t, err)
require.Equal(t, expectedErr, err)
To get error message and code from err you'll probably need to use reflection on err as if I remember correctly gprc status wraps them into own private struct that is then used to concatenate into single string of format code: FOO desc: BAR and that is obtainable through err.Error().
Related
I am trying to test the following line of code:
httpReq.Header.Set("Content-Type", "application/json")
I am mocking the request to an external api in this way:
httpmock.RegisterResponder(http.MethodPost, "do-not-exist.com",
httpmock.NewStringResponder(http.StatusOK, `{
"data":{"Random": "Stuff"}}`),
)
And want to test if the request to the api has the header that I assigned. Is there a way I could achieve this?
With the help of the comment by #Kelsnare I was able to solve this issue in the following way:
httpmock.RegisterResponder(http.MethodPost, "do-not-exist.com",
func(req *http.Request) (*http.Response, error) {
require.Equal(t, req.Header.Get("Content-Type"), "application/json")
resp, _ := httpmock.NewStringResponder(http.StatusOK, `{
"data":{"Random": "Stuff"}}`)(req)
return resp, nil},
)
I wrote my own func of http.Responder type and used httpmock.NewStringResponder inside that func.
response_test.go illustrates how the header is tested:
response, err := NewJsonResponse(200, test.body)
if err != nil {
t.Errorf("#%d NewJsonResponse failed: %s", i, err)
continue
}
if response.StatusCode != 200 {
t.Errorf("#%d response status mismatch: %d ≠ 200", i, response.StatusCode)
continue
}
if response.Header.Get("Content-Type") != "application/json" {
t.Errorf("#%d response Content-Type mismatch: %s ≠ application/json",
i, response.Header.Get("Content-Type"))
continue
You can see an example of table-driven test with httpmock.RegisterResponder here.
I have this json that I convert to:
var leerCHAT []interface{}
but I am going through crazy hoops to get to any point on that map inside map and inside map crazyness, specially because some results are different content.
this is the Json
[
null,
null,
"hub:zWXroom",
"presence_diff",
{
"joins":{
"f718a187-6e96-4d62-9c2d-67aedea00000":{
"metas":[
{
"context":{},
"permissions":{},
"phx_ref":"zNDwmfsome=",
"phx_ref_prev":"zDMbRTmsome=",
"presence":"lobby",
"profile":{},
"roles":{}
}
]
}
},
"leaves":{}
}
]
I need to get to profile then inside there is a "DisplayName" field.
so I been doing crazy hacks.. and even like this I got stuck half way...
First is an array so I can just do something[elementnumber]
then is when the tricky mapping starts...
SORRY about all the prints etc is to debug and see the number of elements I am getting back.
if leerCHAT[3] == "presence_diff" {
var id string
presence := leerCHAT[4].(map[string]interface{})
log.Printf("algo: %v", len(presence))
log.Printf("algo: %s", presence["joins"])
vamos := presence["joins"].(map[string]interface{})
for i := range vamos {
log.Println(i)
id = i
}
log.Println(len(vamos))
vamonos := vamos[id].(map[string]interface{})
log.Println(vamonos)
log.Println(len(vamonos))
metas := vamonos["profile"].(map[string]interface{}) \\\ I get error here..
log.Println(len(metas))
}
so far I can see all the way to the meta:{...} but can't continue with my hacky code into what I need.
NOTICE: that since the id after Joins: and before metas: is dynamic I have to get it somehow since is always just one element I did the for range loop to grab it.
The array element at index 3 describes the type of the variant JSON at index 4.
Here's how to decode the JSON to Go values. First, declare Go types for each of the variant parts of the JSON:
type PrescenceDiff struct {
Joins map[string]*Presence // declaration of Presence type to be supplied
Leaves map[string]*Presence
}
type Message struct {
Body string
}
Declare a map associating the type string to the Go type:
var messageTypes = map[string]reflect.Type{
"presence_diff": reflect.TypeOf(&PresenceDiff{}),
"message": reflect.TypeOf(&Message{}),
// add more types here as needed
}
Decode the variant part to a raw message. Use use the name in the element at index 3 to create a value of the appropriate Go type and decode to that value:
func decode(data []byte) (interface{}, error) {
var messageType string
var raw json.RawMessage
v := []interface{}{nil, nil, nil, &messageType, &raw}
err := json.Unmarshal(data, &v)
if err != nil {
return nil, err
}
if len(raw) == 0 {
return nil, errors.New("no message")
}
t := messageTypes[messageType]
if t == nil {
return nil, fmt.Errorf("unknown message type: %q", messageType)
}
result := reflect.New(t.Elem()).Interface()
err = json.Unmarshal(raw, result)
return result, err
}
Use type switches to access the variant part of the message:
defer ws.Close()
for {
_, data, err := ws.ReadMessage()
if err != nil {
log.Printf("Read error: %v", err)
break
}
v, err := decode(data)
if err != nil {
log.Printf("Decode error: %v", err)
continue
}
switch v := v.(type) {
case *PresenceDiff:
fmt.Println(v.Joins, v.Leaves)
case *Message:
fmt.Println(v.Body)
default:
fmt.Printf("type %T not handled\n", v)
}
}
Run it on the playground.
Im using postman to post data and in the body Im putting some simple json
Request Body
{
"order":"1",
"Name":"ts1"
}
I need to transfer the data to json and I try like following,
and I wasnt able to get json, any idea what is missing
router.POST("/user", func(c *gin.Context) {
var f interface{}
//value, _ := c.Request.GetBody()
//fmt.Print(value)
err2 := c.ShouldBindJSON(&f)
if err2 == nil {
err = client.Set("id", f, 0).Err()
if err != nil {
panic(err)
}
}
The f is not a json and Im getting an error, any idea how to make it work?
The error is:
redis: can't marshal map[string]interface {} (implement encoding.BinaryMarshaler)
I use https://github.com/go-redis/redis#quickstart
If I remove the the body and use hard-coded code like this I was able to set the data, it works
json, err := json.Marshal(Orders{
order: "1",
Name: "tst",
})
client.Set("id", json, 0).Err()
If you only want to pass the request body JSON to Redis as a value, then you do not need to bind the JSON to a value. Read the raw JSON from the request body directly and just pass it through:
jsonData, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
// Handle error
}
err = client.Set("id", jsonData, 0).Err()
let's do it with an example. Assume your request body has a user email like this:
{ email: "test#test.com" }
and now you want to get this email on the back-end. first, define a struct like the following:
type EmailRequestBody struct {
Email string
}
Now you can easily bind the email value in your request body to the struct you defined: first, define a variable for your struct and then bind the value:
func ExampleFunction(c *gin.Context) {
var requestBody EmailRequestBody
if err := c.BindJSON(&requestBody); err != nil {
// DO SOMETHING WITH THE ERROR
}
fmt.Println(requestBody.Email)
}
you can easily access the email value and print it out or do whatever you need :
fmt.Println(requestBody.Email)
Or you can use GetRawData() function as:
jsonData, err := c.GetRawData()
if err != nil{
//Handle Error
}
err = client.Set("id", jsonData, 0).Err()
If you want to get json body like other frameworks like express(Nodejs), you can do the following
bodyAsByteArray, _ := ioutil.ReadAll(c.Request.Body)
jsonBody := string(bodyAsByteArray)
I have a function named LoginUser(c *gin.Context) with argument c *gin.Context. I want to call LoginUser from another function CreateBlogsWithUser. But LoginUser requires username and password. I tried to pass in c.Request.Body but it is not working.
func (server *Server) CreateBlogsWithUser() {
resp := httptest.NewRecorder()
gin.SetMode(gin.TestMode)
c, _ := gin.CreateTestContext(resp)
c.Request.Header.Add("Content-Type","application/json")
c.Request.Body.Add("uname","test") //this line is not working
c.Request.Body.Add("password","test#123") //this line is not working
LoginUser(c)
}
func LoginUser(c *gin.Context) {
requestData := models.CCPADefaultData{}
err := json.NewDecoder(c.Request.Body).Decode(&requestData)
if err != nil {
errList["Invalid_body"] = "Missing request parameters."
c.JSON(http.StatusUnprocessableEntity, gin.H{
"status": http.StatusUnprocessableEntity,
"error": "Always come in this if condition.",
})
return
}
//XXXXXXXXX
//Code
}
I tried many things but none is working for me.
Please help how to pass parameter if argument is c *gin.Context
I am trying to test my UserRegister functionality, it takes http request.
If user enters already existing email, UserRegister returns an error log (using logrus).
logs "github.com/sirupsen/logrus"
func UserRegister(res http.ResponseWriter, req *http.Request) {
requestID := req.FormValue("uid")
email := req.FormValue("email")
logs.WithFields(logs.Fields{
"Service": "User Service",
"package": "register",
"function": "UserRegister",
"uuid": requestID,
"email": email,
}).Info("Received data to insert to users table")
// check user entered new email address
hasAccount := checkemail.Checkmail(email, requestID) // returns true/false
if hasAccount != true { // User doesn't have an account
db := dbConn()
// Inserting token to login_token table
insertUser, err := db.Prepare("INSERT INTO users (email) VALUES(?)")
if err != nil {
logs.WithFields(logs.Fields{
"Service": "User Service",
"package": "register",
"function": "UserRegister",
"uuid": requestID,
"Error": err,
}).Error("Couldnt prepare insert statement for users table")
}
insertUser.Exec(email)
defer db.Close()
return
} // user account created
logs.WithFields(logs.Fields{
"Service": "User Service",
"package": "register",
"function": "UserRegister",
"uuid": requestID,
"email": email,
}).Error("User has an account for this email")
}
In my test module, I used following.
func TestUserRegister(t *testing.T) {
rec := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "http://localhost:7071/register?email=sachit45345h#gmail.com&uid=sjfkjsdkf9w89w83490w", nil)
UserRegister(rec, req)
expected := "User has an account for this email"
res := rec.Result()
content, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error("Couldnt read body", err)
}
val, err := strconv.Atoi(string(bytes.TrimSpace(content)))
if err != nil {
log.Println("Error parsing response", err)
}
if string(val) != expected {
t.Errorf("Expected %s, got %s", expected, string(content))
}
}
Result : Error parsing response strconv.Atoi: parsing "": invalid syntax
Why response can not be converted?
Checked threads:
Why is this Golang code to convert a string to an integer failing.
Edit : after #chmike answer.
This is a part of microservice. All the responses are written to API-Gateway. Using a function.
But here I just want to perform unit test and check whether my UserRegister works as expected.
The function UserRegister never writes to res or sets the status. As a consequence you get an empty string from res in TestUserRegister. content is an empty string and the conversion of an empty string to an integer with Atoi fails since there is no integer to convert.
I can only explain what happens. I can’t tell you what to do to fix the problem because you don’t explain what you want to do or get in the question.
The http response you are reading is not the response to your request. You create a response and expect it to have something in it. it will not. So you end up trying to create an integer from an empty string.
Look at some examples to see where the real response would come from. https://golang.org/pkg/net/http