Test fails to capture logging output - go

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

Related

Probleme with httptest postForm in golang

i receive a response body "bad request" with "httptest.Client().Postform"
type testServer struct {
*httptest.Server
}
func newTestServer(t *testing.T, h http.Handler) *testServer {
ts := httptest.NewTLSServer(h)
jar, err := cookiejar.New(nil)
if err != nil {
t.Fatal(err)
}
ts.Client().Jar = jar
ts.Client().CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
return &testServer{ts}
}
func (ts *testServer) postForm(t *testing.T, urlPath string, form url.Values) (int, http.Header, string) {
rs, err := ts.Client().PostForm(ts.URL+urlPath, form)
if err != nil {
t.Fatal(err)
}
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
return rs.StatusCode, rs.Header, string(body)
}
I don't know where is the problem, i have also verified the url it's correct.
Always badrequest with POST but with GET request it's works fine.
this is the handler object :
func (app *application) routes() http.Handler {
router := httprouter.New()
router.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
app.notFound(w)
})
dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.Authenticated)
router.Handler(http.MethodGet, "/", dynamic.ThenFunc(app.home))
router.Handler(http.MethodGet, "/user/signup", dynamic.ThenFunc(app.userSignup))
router.Handler(http.MethodPost, "/user/signup", dynamic.ThenFunc(app.userSignupPost))
standart := alice.New(app.recoverPanic, app.logRequest, securityHeaders)
return standart.Then(router)
}
the test function :https://go.dev/play/p/k45-JYTYCOS
the app.userSignupPost:
func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) {
var form userSignupForm
err := app.decodPostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form.CheckField(validator.NotBlank(form.Name), "name", "this field must not be blank")
form.CheckField(validator.NotBlank(form.Email), "email", "this field must not be blank")
form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "this field must be a valid email address")
form.CheckField(validator.NotBlank(form.Password), "password", "this field must not be blank")
form.CheckField(validator.MinChars(form.Password, 8), "password", "password must bee at least 8 caracter long")
if !form.Valid() {
data := app.newTemplateData(r)
data.Form = form
app.render(w, http.StatusUnprocessableEntity, "signup.tmpl.html", data)
return
}
err = app.users.Insert(form.Name, form.Email, form.Password)
if err != nil {
if errors.Is(err, models.ErrDuplicateEmail) {
form.AddFieldError("email", "Email already exist")
data := app.newTemplateData(r)
data.Form = form
app.render(w, http.StatusUnprocessableEntity, "signup.tmpl.html", data)
} else {
fmt.Println("error user postform")
app.serverError(w, err)
}
return
}
app.sessionManager.Put(r.Context(), "flash", "Signup Successful. Please log in")
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
}
It appears that you're using https://github.com/justinas/alice to register handlers - you don't want to do this. That package is for middleware chaining - e.g. "before all requests to this URL, first authenticate the request" - you'd put the authentication into a middleware function and then add it to the chain.
So every POST /user/signup request is getting passed first to app.userSignup() (what you are using to handle GET requests). This is because calling alice.ThenFunc() appends the passed handler to the chain and then returns the entire chain as a handler - you need to read this part of the Alice docs carefully before using it.
Replace this line:
router.Handler(http.MethodPost, "/user/signup", dynamic.ThenFunc(app.userSignupPost))
with
router.Handler(http.MethodPost, "/user/signup", http.HandlerFunc(app.userSignupPost))
You may not need the additional decoration of http.HandlerFunc() - try it with and without to see what works. I cannot say for sure without knowing what the body of app.userSignupPost() looks like (same for the other handler functions as well).
You'll then need to do the same for the other handler registration lines - you shouldn't be using middleware chaining for your end handlers. An http.Handler is used for saying, "send any request to path /PP/ppp with method XXXX to this function." Middleware chaining is for preprocessing (authentication, authorization, etc.) - a whole host of things can be done there, but end request handling shouldn't be one of them.
I'm still curious if your use of PostForm() is going to cause you issues for the reason I cited in my comment on your question - try a raw Post() and see if the behavior differs, but after refactoring to take out the alice goop (at least temporarily). When testing a handler, I'd start off with a much more minimal approach - test that the handler itself works before muddying the waters with both alice and what looks like this package.
I think i found the problem , the session cookie are not the same for get and post request. i don't know why it has changed.They use the same http.Client()

How to sanitize strings in Golang striping html, javascript, sql etc

I'm writing a REST API, through which users can register/login and a number of other things that would require them to send JSON in the request body. The JSON sent in the request body will look something like this for registering:
{
"name": "luke",
"email": "l#g.com",
"date_of_birth": "2012-04-23T18:25:43.511Z",
"location": "Galway",
"profile_image_id": 1,
"password": "password",
"is_subscribee_account": false,
"broker_id": 1
}
This data will then be decoded into a struct and the individual items inserted into the database. I'm not familiar with how I should sanitize the data to remove any Javascript, HTML, SQL etc that may interfere with the correct running of the application and possibly result in a compromised system.
The below code is what I have currently to read in and validate the data but it doesn't sanitize the data for html, javascript, sql etc.
Any input would be appreciated on how I should go about sanitizing it. The REST API is written in Go.
//The below code decodes the JSON into the dst variable.
func (app *application) readJSON(w http.ResponseWriter, r *http.Request, dst interface{}) error {
maxBytes := 1_048_576
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
err := dec.Decode(dst)
if err != nil {
var syntaxError *json.SyntaxError
var unmarshalTypeError *json.UnmarshalTypeError
var invalidUnmarshalError *json.InvalidUnmarshalError
switch {
case errors.As(err, &syntaxError):
return fmt.Errorf("body contains badly-formed JSON (at character %d)", syntaxError.Offset)
case errors.Is(err, io.ErrUnexpectedEOF):
return errors.New("body contains badly-formed JSON")
case errors.As(err, &unmarshalTypeError):
if unmarshalTypeError.Field != "" {
return fmt.Errorf("body contains incorrect JSON type for field %q", unmarshalTypeError.Field)
}
return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshalTypeError.Offset)
case errors.Is(err, io.EOF):
return errors.New("body must not be empty")
case strings.HasPrefix(err.Error(), "json: unknown field "):
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
return fmt.Errorf("body contains unknown key %s", fieldName)
case err.Error() == "http: request body too large":
return fmt.Errorf("body must not be larger than %d bytes", maxBytes)
case errors.As(err, &invalidUnmarshalError):
panic(err)
default:
return err
}
}
err = dec.Decode(&struct{}{})
if err != io.EOF {
return errors.New("body must only contain a single JSON value")
}
return nil
}
//The below code validates the email, password and user. If the first parameter in V.Check() is false the following two parameter are returned in the response to the user.
func ValidateEmail(v *validator.Validator, email string) {
v.Check(email != "", "email", "must be provided")
v.Check(validator.Matches(email, validator.EmailRX), "email", "must be a valid email address")
}
func ValidatePasswordPlaintext(v *validator.Validator, password string) {
v.Check(password != "", "password", "must be provided")
v.Check(len(password) >= 8, "password", "must be at least 8 bytes long")
v.Check(len(password) <= 72, "password", "must not be more than 72 bytes long")
}
func ValidateUser(v *validator.Validator, user *User) {
v.Check(user.Name != "", "name", "must be provided")
v.Check(len(user.Name) <= 500, "name", "must not be more than 500 bytes long")
ValidateEmail(v, user.Email)
if user.Password.plaintext != nil {
ValidatePasswordPlaintext(v, *user.Password.plaintext)
}
if user.Password.hash == nil {
panic("missing password hash for user")
}
}
How do I serialize the struct data with this in mind? So it wont cause issues if later inserted in a webpage. Is there a package function that I can pass the string data to that will return safe data?

How to interrogate an error object inside a GoLang Test case?

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().

How to add json data as parameter to c.Body.Request?

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

How can I remove a property from a object

I have a User struct:
type User struct {
gorm.Model
Email string
Password string
AccountType int
CompanyId int
FirstName string
LastName string
PhoneNumber string
RecoveryEmail string
Contractor bool `gorm:"sql:'not null' default:'false'"`
}
I'm using this struct to get a row from the database using gorm:
// Get a specific user from the database.
func getUser(id uint) (*User, error) {
var user User
if err := database.Connection.Select("id, created_at, email, account_type, company_id, first_name, last_name").Where("id = ? ", id).First(&user).Error; err != nil {
return nil, err
}
fmt.Println(&user)
return &user, nil
}
My Gin hanlder:
// #Summary Attempts to get a existing user by id
// #tags users
// #Router /api/users/getUserById [get]
func HandleGetUserById(c *gin.Context) {
// Were using delete params as it shares the same interface.
var json deleteParams
if err := c.Bind(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "No user ID found, please try again."})
return
}
outcome, err := getUser(json.Id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Something went wrong while trying to process that, please try again.", "error": err.Error()})
log.Println(err)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Successfully found user",
"user": outcome,
})
}
It returns back everything fine, but when I return &user the fields not selected are returned back with default values:
{
"message": "Successfully found user",
"user": {
"ID": 53,
"CreatedAt": "2018-06-24T00:05:49.761736+01:00",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Email": "jack#jackner.com",
"Password": "",
"AccountType": 0,
"CompanyId": 2,
"FirstName": "",
"LastName": "",
"PhoneNumber": "",
"RecoveryEmail": "",
"Contractor": false
}
}
Is there a way in go to remove empty or null properties from an object? Or will I have to send back an object instead with the values mapped to said new object? If there's a simple way of doing the former with a helper function I'd like to know how.
You can specify the omitempty tag in your object's fields definitions.
Example:
Email string `json:",omitempty"`
If you define the fields that way, empty values will not be present in the JSON output:
https://golang.org/pkg/encoding/json/#Marshal
The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

Resources