From ce8a18aa8b55e283187a96256216b0fef06c3319 Mon Sep 17 00:00:00 2001 From: Mazyar Date: Mon, 22 Jun 2026 22:09:30 +0330 Subject: [PATCH] Add unit tests for UpdateUserForm JSON unmarshalling - 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. --- internal/user/form.go | 72 ++++++++++++++++++++++++++++++++++---- internal/user/form_test.go | 47 +++++++++++++++++++++++++ internal/user/service.go | 4 +-- 3 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 internal/user/form_test.go diff --git a/internal/user/form.go b/internal/user/form.go index 395897e..80c23e3 100644 --- a/internal/user/form.go +++ b/internal/user/form.go @@ -1,6 +1,10 @@ package user -import "tm/pkg/response" +import ( + "encoding/json" + + "tm/pkg/response" +) // User Registration DTOs @@ -27,6 +31,46 @@ type UpdateUserForm struct { 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 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 @@ -51,11 +95,27 @@ type ChangePasswordForm struct { // UpdateProfileForm represents the data for updating user profile information type UpdateProfileForm struct { - FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)" example:"Admin User"` // Updated full name - Department *string `json:"department,omitempty" valid:"optional,length(2|100)" example:"Information Technology"` // Updated department - 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 - ProfileImage *string `json:"profile_image,omitempty" valid:"optional,url" example:""` // Updated profile image URL + FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)" example:"Admin User"` // Updated full name + Department *string `json:"department,omitempty" valid:"optional,length(2|100)" example:"Information Technology"` // Updated department + 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 + 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 diff --git a/internal/user/form_test.go b/internal/user/form_test.go new file mode 100644 index 0000000..ecc3a84 --- /dev/null +++ b/internal/user/form_test.go @@ -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) + } + }) +} diff --git a/internal/user/service.go b/internal/user/service.go index b852df9..e3d3b92 100644 --- a/internal/user/service.go +++ b/internal/user/service.go @@ -204,7 +204,7 @@ func (s *userService) Update(ctx context.Context, id string, form *UpdateUserFor user.Phone = form.Phone } - if form.ProfileImage != nil { + if form.isProfileImageSet() { user.ProfileImage = form.ProfileImage } @@ -601,7 +601,7 @@ func (s *userService) UpdateProfile(ctx context.Context, userID string, form *Up } user.Phone = form.Phone } - if form.ProfileImage != nil { + if form.isProfileImageSet() { user.ProfileImage = form.ProfileImage }