|
|
@@ -0,0 +1,117 @@
|
|
|
+package com.etotem.cfc.controller.admin;
|
|
|
+
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
+import com.etotem.cfc.service.ProductService;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.mockito.ArgumentCaptor;
|
|
|
+import org.springframework.test.util.ReflectionTestUtils;
|
|
|
+
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.Mockito.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 批量商品上下架(/api/admin/product/batch-shelve)类型安全回归测试。
|
|
|
+ *
|
|
|
+ * 故障:批量下架时抛 ClassCastException: Integer cannot be cast to Long。
|
|
|
+ * 根因:@RequestBody Map<String, Object> 使 Jackson 把 JSON 数字数组反序列化为
|
|
|
+ * List<Integer>;控制器旧写法 (List<Long>) params.get("productIds") 为擦除强转,
|
|
|
+ * 编译通过,到服务层 for (Long id : list) 才在运行期对元素做 Integer->Long 转换而失败。
|
|
|
+ *
|
|
|
+ * 约定:productIds 必须是 Number 或数字字符串均可;解析后的 List<Long> 交给服务层。
|
|
|
+ * 用 ObjectMapper 反序列化真实 JSON 复现线上报文(与控制器收到的对象一致)。
|
|
|
+ */
|
|
|
+public class AdminProductControllerBatchShelveTest {
|
|
|
+
|
|
|
+ private static final ObjectMapper OM = new ObjectMapper();
|
|
|
+
|
|
|
+ private AdminProductController controller;
|
|
|
+ private ProductService productService;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ public void setUp() throws Exception {
|
|
|
+ controller = new AdminProductController();
|
|
|
+ productService = mock(ProductService.class);
|
|
|
+ ReflectionTestUtils.setField(controller, "productService", productService);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void batchShelve_jsonIntegerIds_convertedToLong() throws Exception {
|
|
|
+ // 复现线上报文:Jackson 对 JSON 数字产出 Integer
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ Map<String, Object> params = OM.readValue(
|
|
|
+ "{\"productIds\":[1,2,3],\"shelve\":false}", Map.class);
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ List<?> raw = (List<?>) params.get("productIds");
|
|
|
+ assertEquals(Integer.class, raw.get(0).getClass(),
|
|
|
+ "前置条件:Jackson 将 JSON 数字反序列化为 Integer(即本缺陷的诱因)");
|
|
|
+
|
|
|
+ when(productService.batchAdminShelve(any(), eq(false)))
|
|
|
+ .thenReturn(Result.success("批量下架成功"));
|
|
|
+
|
|
|
+ Result<String> result = controller.batchShelve(params);
|
|
|
+
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertEquals("批量下架成功", result.getData());
|
|
|
+
|
|
|
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
|
|
|
+ verify(productService, times(1)).batchAdminShelve(captor.capture(), eq(false));
|
|
|
+ for (Object id : captor.getValue()) {
|
|
|
+ assertEquals(Long.class, id.getClass(),
|
|
|
+ "传给服务层的元素必须是 Long,实际为 " + id.getClass());
|
|
|
+ }
|
|
|
+ assertEquals(3, captor.getValue().size());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void batchShelve_largeValueAndStringNumberAccepted() throws Exception {
|
|
|
+ // 大数值(超出 Integer 范围,Jackson 产出 Long)与字符串数字都应安全解析
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ Map<String, Object> params = OM.readValue(
|
|
|
+ "{\"productIds\":[3000000000,\"42\"],\"shelve\":true}", Map.class);
|
|
|
+
|
|
|
+ when(productService.batchAdminShelve(any(), eq(true)))
|
|
|
+ .thenReturn(Result.success("批量上架成功"));
|
|
|
+
|
|
|
+ Result<String> result = controller.batchShelve(params);
|
|
|
+
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ verify(productService, times(1))
|
|
|
+ .batchAdminShelve(eq(java.util.Arrays.asList(3000000000L, 42L)), eq(true));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void batchShelve_missingOrEmptyIds_returnsCleanError() throws Exception {
|
|
|
+ // Result.error(msg) 约定:body code=500(HTTP 200)。要点是干净的业务错误,
|
|
|
+ // 而不是抛 ClassCastException / 崩溃。
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ Map<String, Object> absent = OM.readValue("{\"shelve\":false}", Map.class);
|
|
|
+ Result<String> r1 = controller.batchShelve(absent);
|
|
|
+ assertEquals(500, r1.getCode());
|
|
|
+ assertEquals("productIds不能为空", r1.getMessage());
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ Map<String, Object> empty = OM.readValue("{\"productIds\":[],\"shelve\":false}", Map.class);
|
|
|
+ Result<String> r2 = controller.batchShelve(empty);
|
|
|
+ assertEquals(500, r2.getCode());
|
|
|
+ assertEquals("productIds不能为空", r2.getMessage());
|
|
|
+
|
|
|
+ verify(productService, never()).batchAdminShelve(any(), any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void batchShelve_missingShelve_returnsCleanError() throws Exception {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ Map<String, Object> params = OM.readValue("{\"productIds\":[1]}", Map.class);
|
|
|
+ Result<String> result = controller.batchShelve(params);
|
|
|
+ assertEquals(500, result.getCode());
|
|
|
+ assertEquals("shelve不能为空", result.getMessage());
|
|
|
+ verify(productService, never()).batchAdminShelve(any(), any());
|
|
|
+ }
|
|
|
+}
|