feat(profile): 新增C端个人资料与设备相册功能
- 新增个人资料模块,支持获取、修改昵称和个性签名 - 实现头像上传与校验,支持JPEG/PNG格式及尺寸限制 - 添加app_user表signature字段的可重复执行SQL脚本 - 新增设备相册控制器,支持读取、收藏和删除相册图片 - 编写AppProfileService和DeviceAlbumController单元测试
This commit is contained in:
1 parent
8d61c63c8b
commit
4dd7d98ce9
9 files changed
+382
No files matched your search
@@ -0,0 +1,64 @@
|
||||
package com.youlai.boot.app.profile;
|
||||
|
||||
import com.youlai.boot.app.profile.mapper.AppProfileMapper;
|
||||
import com.youlai.boot.app.profile.model.AppProfileForm;
|
||||
import com.youlai.boot.app.profile.model.AppProfileVO;
|
||||
import com.youlai.boot.app.profile.service.AppProfileService;
|
||||
import com.youlai.boot.common.config.StorageDir;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.support.file.model.FileInfo;
|
||||
import com.youlai.boot.support.file.service.FileService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class AppProfileServiceTest {
|
||||
private final AppProfileMapper mapper = mock(AppProfileMapper.class);
|
||||
private final FileService files = mock(FileService.class);
|
||||
private final AppProfileService service = new AppProfileService(mapper, files);
|
||||
|
||||
@Test
|
||||
void updatesOnlyCurrentUsersTextFields() {
|
||||
AppProfileVO current = new AppProfileVO();
|
||||
current.setId(7L);
|
||||
when(mapper.findCurrent(7L)).thenReturn(current);
|
||||
AppProfileForm form = new AppProfileForm();
|
||||
form.setNickname(" 家人 ");
|
||||
form.setSignature(" 你好 ");
|
||||
|
||||
service.update(7L, form);
|
||||
|
||||
verify(mapper).updateProfile(7L, "家人", "你好");
|
||||
verify(mapper, never()).updateAvatar(anyLong(), anyString());
|
||||
verifyNoInteractions(files);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInvalidAvatarBeforeUploading() {
|
||||
when(mapper.findCurrent(7L)).thenReturn(new AppProfileVO());
|
||||
MockMultipartFile invalid = new MockMultipartFile(
|
||||
"file", "avatar.png", "image/png", "invalid".getBytes());
|
||||
assertThrows(BusinessException.class, () -> service.uploadAvatar(7L, invalid));
|
||||
verifyNoInteractions(files);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removesNewUploadWhenUserCannotBeUpdated() {
|
||||
when(mapper.findCurrent(7L)).thenReturn(new AppProfileVO());
|
||||
byte[] png = Base64.getDecoder().decode(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGP4DwQACfsD/fteaysAAAAASUVORK5CYII=");
|
||||
MockMultipartFile image = new MockMultipartFile("file", "avatar.png", "image/png", png);
|
||||
FileInfo uploaded = new FileInfo();
|
||||
uploaded.setUrl("https://files.example/app/avatar/new.png");
|
||||
when(files.uploadFile(image, StorageDir.AVATAR)).thenReturn(uploaded);
|
||||
when(mapper.updateAvatar(7L, uploaded.getUrl())).thenReturn(0);
|
||||
|
||||
assertThrows(BusinessException.class, () -> service.uploadAvatar(7L, image));
|
||||
|
||||
verify(files).deleteFile(uploaded.getUrl());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.youlai.boot.device;
|
||||
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.device.controller.DeviceAlbumController;
|
||||
import com.youlai.boot.device.model.vo.DevicePictureVO;
|
||||
import com.youlai.boot.device.service.DevicePictureService;
|
||||
import jakarta.validation.Validation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.util.List;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class DeviceAlbumControllerTest {
|
||||
private final DevicePictureService pictures = mock(DevicePictureService.class);
|
||||
private final DeviceAlbumController controller = new DeviceAlbumController(pictures);
|
||||
|
||||
@Test
|
||||
void listUsesAuthenticatedDeviceAndRetainsFavoriteState() {
|
||||
DevicePictureVO photo = new DevicePictureVO();
|
||||
photo.setUrl("https://example.com/photo.jpg");
|
||||
photo.setFavorite(1);
|
||||
when(pictures.listPictures("device-a")).thenReturn(List.of(photo));
|
||||
assertThat(controller.list("device-a").getData()).containsExactly(photo);
|
||||
verify(pictures).listPictures("device-a");
|
||||
verifyNoMoreInteractions(pictures);
|
||||
}
|
||||
|
||||
@Test
|
||||
void favoriteAndDeleteUseAuthenticatedDeviceAndExactUrl() {
|
||||
String url = "https://example.com/photo.jpg?key=a%2Bb";
|
||||
when(pictures.updateFavorite("device-a", url, true)).thenReturn(true);
|
||||
assertThat(controller.favorite("device-a", url, 1).getCode()).isEqualTo(ResultCode.SUCCESS.getCode());
|
||||
controller.delete("device-a", url);
|
||||
verify(pictures).updateFavorite("device-a", url, true);
|
||||
verify(pictures).deletePicture("device-a", url);
|
||||
verifyNoMoreInteractions(pictures);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingPictureDoesNotReportFavoriteSuccess() {
|
||||
assertThat(controller.favorite("device-a", "missing", 0).getCode()).isNotEqualTo(ResultCode.SUCCESS.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void mutationRejectsBlankIdentityAndInvalidFavorite() throws Exception {
|
||||
try (var factory = Validation.buildDefaultValidatorFactory()) {
|
||||
var validator = factory.getValidator().forExecutables();
|
||||
var method = DeviceAlbumController.class.getMethod("favorite", String.class, String.class, Integer.class);
|
||||
assertThat(validator.validateParameters(controller, method, new Object[]{"device-a", "url", 2})).isNotEmpty();
|
||||
assertThat(validator.validateParameters(controller, method, new Object[]{"", " ", 1})).hasSize(2);
|
||||
assertThat(validator.validateParameters(controller, method, new Object[]{"device-a", "url", 0})).isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user