Add unit tests for UpdateUserForm JSON unmarshalling
continuous-integration/drone/push Build is passing

- Introduced `form_test.go` to validate the behavior of the `UpdateUserForm` struct's JSON unmarshalling, specifically for the `profile_image` field.
- Added tests to ensure omitted `profile_image` is not treated as an update, null values clear the image, and valid URLs are preserved.
- Enhanced the `UpdateUserForm` and `UpdateProfileForm` structs to include logic for distinguishing between omitted and explicitly null profile images during JSON unmarshalling.

This update improves the robustness of user profile updates by ensuring correct handling of profile image data in JSON requests, enhancing overall data integrity in the user management system.
This commit is contained in:
Mazyar
2026-06-22 22:09:30 +03:30
parent d486a5e44f
commit ce8a18aa8b
3 changed files with 115 additions and 8 deletions
+61 -1
View File
@@ -1,6 +1,10 @@
package user package user
import "tm/pkg/response" import (
"encoding/json"
"tm/pkg/response"
)
// User Registration DTOs // User Registration DTOs
@@ -27,6 +31,46 @@ type UpdateUserForm struct {
Position *string `json:"position" valid:"optional,length(2|100)" example:"Lead"` // Updated position Position *string `json:"position" valid:"optional,length(2|100)" example:"Lead"` // Updated position
Phone *string `json:"phone" valid:"optional,length(10|20)" example:"+18289784438"` // Updated phone number Phone *string `json:"phone" valid:"optional,length(10|20)" example:"+18289784438"` // Updated phone number
ProfileImage *string `json:"profile_image" valid:"optional,url" example:""` // Updated profile image URL ProfileImage *string `json:"profile_image" valid:"optional,url" example:""` // Updated profile image URL
profileImageSet bool
}
func unmarshalProfileImageField(data []byte, profileImage **string, profileImageSet *bool) ([]byte, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
profileImageRaw, ok := raw["profile_image"]
if ok {
*profileImageSet = true
if string(profileImageRaw) == "null" {
*profileImage = nil
} else {
var image string
if err := json.Unmarshal(profileImageRaw, &image); err != nil {
return nil, err
}
*profileImage = &image
}
delete(raw, "profile_image")
}
return json.Marshal(raw)
}
// UnmarshalJSON distinguishes omitted profile_image from explicit null to allow clearing the image.
func (f *UpdateUserForm) UnmarshalJSON(data []byte) error {
rest, err := unmarshalProfileImageField(data, &f.ProfileImage, &f.profileImageSet)
if err != nil {
return err
}
type alias UpdateUserForm
return json.Unmarshal(rest, (*alias)(f))
}
func (f *UpdateUserForm) isProfileImageSet() bool {
return f.profileImageSet
} }
// User Authentication DTOs // User Authentication DTOs
@@ -56,6 +100,22 @@ type UpdateProfileForm struct {
Position *string `json:"position,omitempty" valid:"optional,length(2|100)" example:"Lead"` // Updated position Position *string `json:"position,omitempty" valid:"optional,length(2|100)" example:"Lead"` // Updated position
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+18289784438"` // Updated phone number Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+18289784438"` // Updated phone number
ProfileImage *string `json:"profile_image,omitempty" valid:"optional,url" example:""` // Updated profile image URL ProfileImage *string `json:"profile_image,omitempty" valid:"optional,url" example:""` // Updated profile image URL
profileImageSet bool
}
// UnmarshalJSON distinguishes omitted profile_image from explicit null to allow clearing the image.
func (f *UpdateProfileForm) UnmarshalJSON(data []byte) error {
rest, err := unmarshalProfileImageField(data, &f.ProfileImage, &f.profileImageSet)
if err != nil {
return err
}
type alias UpdateProfileForm
return json.Unmarshal(rest, (*alias)(f))
}
func (f *UpdateProfileForm) isProfileImageSet() bool {
return f.profileImageSet
} }
// ResetPasswordForm represents the email address for password reset // ResetPasswordForm represents the email address for password reset
+47
View File
@@ -0,0 +1,47 @@
package user
import (
"encoding/json"
"testing"
)
func TestUpdateUserForm_UnmarshalJSON_ProfileImage(t *testing.T) {
t.Run("omitted profile_image is not treated as an update", func(t *testing.T) {
var form UpdateUserForm
err := json.Unmarshal([]byte(`{"full_name":"Jane Doe"}`), &form)
if err != nil {
t.Fatalf("unmarshal failed: %v", err)
}
if form.isProfileImageSet() {
t.Fatal("expected profile image to be unset when omitted")
}
})
t.Run("null profile_image clears the image", func(t *testing.T) {
var form UpdateUserForm
err := json.Unmarshal([]byte(`{"profile_image":null}`), &form)
if err != nil {
t.Fatalf("unmarshal failed: %v", err)
}
if !form.isProfileImageSet() {
t.Fatal("expected profile image to be marked as set")
}
if form.ProfileImage != nil {
t.Fatalf("expected nil profile image, got %#v", form.ProfileImage)
}
})
t.Run("profile image url is preserved", func(t *testing.T) {
var form UpdateUserForm
err := json.Unmarshal([]byte(`{"profile_image":"https://example.com/avatar.png"}`), &form)
if err != nil {
t.Fatalf("unmarshal failed: %v", err)
}
if !form.isProfileImageSet() {
t.Fatal("expected profile image to be marked as set")
}
if form.ProfileImage == nil || *form.ProfileImage != "https://example.com/avatar.png" {
t.Fatalf("unexpected profile image: %#v", form.ProfileImage)
}
})
}
+2 -2
View File
@@ -204,7 +204,7 @@ func (s *userService) Update(ctx context.Context, id string, form *UpdateUserFor
user.Phone = form.Phone user.Phone = form.Phone
} }
if form.ProfileImage != nil { if form.isProfileImageSet() {
user.ProfileImage = form.ProfileImage user.ProfileImage = form.ProfileImage
} }
@@ -601,7 +601,7 @@ func (s *userService) UpdateProfile(ctx context.Context, userID string, form *Up
} }
user.Phone = form.Phone user.Phone = form.Phone
} }
if form.ProfileImage != nil { if form.isProfileImageSet() {
user.ProfileImage = form.ProfileImage user.ProfileImage = form.ProfileImage
} }