|
|
@@ -0,0 +1,365 @@
|
|
|
+package com.etotem.cfc.unit;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
+import com.etotem.cfc.entity.User;
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
+import com.etotem.cfc.service.RealNameBackfillService;
|
|
|
+import com.etotem.cfc.service.UserService;
|
|
|
+import org.junit.jupiter.api.DisplayName;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.lang.reflect.Method;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
+import static org.junit.jupiter.api.Assertions.fail;
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 契约级单测(TDD-RED,S-cfc-200):能力键 {@code admin.supplier.manage}
|
|
|
+ * (平台运营·供应商管理 SC-A-06 + 商品服务商入驻申请 SC-S-01)。
|
|
|
+ *
|
|
|
+ * <p><b>需求/设计溯源</b>:
|
|
|
+ * <ul>
|
|
|
+ * <li>需求:{@code tests/user-scenarios/应用场景总览.md} <b>SC-A-06</b>(供应商管理:列表/审核/停用,
|
|
|
+ * 边界「有在售商品的供应商停用需先下架」)、<b>SC-S-01</b>(入驻申请:提交资质→平台审核→入驻成功,
|
|
|
+ * 边界「资质不全驳回;重复申请幂等」)、<b>SC-S-02</b>(商品管理上架/编辑)。</li>
|
|
|
+ * <li>设计:{@code docs/superpowers/api/API_REFERENCE.md} <b>§4.13</b> / <b>§4.60.6</b>——
|
|
|
+ * <b>AdminSupplierController</b> {@code /api/admin/supplier}(list/save/update/delete)、
|
|
|
+ * <b>EcomSupplierController</b> {@code /api/admin/ecom-supplier}(list/all/save/update/delete)、
|
|
|
+ * <b>SupplyManageController</b> {@code /api/admin/supply-manage}(list/detail/create/update/delete)。</li>
|
|
|
+ * <li>映射:{@code docs/tdd/orchestration/sc-key-map.md}——SC-A-06 / SC-S-01 → {@code admin.supplier.manage}
|
|
|
+ * (S-cfc-200;AdminSupplier/EcomSupplier/SupplyManageController)。</li>
|
|
|
+ * </ul>
|
|
|
+ * SC-S-02(商品管理上架/编辑)已由合入主链的 {@code VendorProductManageScS02ContractTest}(ProductService 层)
|
|
|
+ * 覆盖,本类不重复,仅登记映射。</p>
|
|
|
+ *
|
|
|
+ * <p><b>实现现状(src/main 逐类核实)</b>:
|
|
|
+ * <ul>
|
|
|
+ * <li>{@code AdminSupplierController} / {@code EcomSupplierController}:<b>410 废弃桩</b>——两个设计载体
|
|
|
+ * 仅返回 {@code Result.error(410, "该接口已废弃,请使用 /api/admin/supply-system/...")},
|
|
|
+ * 契约所需的供应商 CRUD 行为<b>未落地</b>(红)。API_REFERENCE §4.61 载体置换提示:活跃载体为
|
|
|
+ * {@code SupplySystemController}/{@code SupplySystemService}。</li>
|
|
|
+ * <li>{@code SupplyManageController}:已落地(list/detail/create/update/delete + 入驻校验/角色/层级),
|
|
|
+ * 为<b>功能载体</b>——本测试对其已实现行为保留绿证断言(实现现状证据)。</li>
|
|
|
+ * <li>SC-A-06 边界「有在售商品的供应商停用需先下架」:<b>全库未实现</b>——{@code SupplySystemService.toggleStatus}
|
|
|
+ * 直接翻转 active/disabled,{@code SupplyManageController.update} 直接置 vendorStatus,二者均<b>无在售商品守卫</b>
|
|
|
+ * (红)。</li>
|
|
|
+ * </ul></p>
|
|
|
+ *
|
|
|
+ * <p><b>测试风格</b>:与同批 RED 契约测试({@code TeamProfitAdminManageContractTest} 等)一致——纯反射/Mockito
|
|
|
+ * 锚定契约宿主,无 Spring 上下文 / 无 DB / 无外部服务。契约所需行为缺失处断言失败(红);已落地行为保留绿证断言。</p>
|
|
|
+ *
|
|
|
+ * <p><b>契约方法签名(GREEN 阶段据此落地)</b>:
|
|
|
+ * <ul>
|
|
|
+ * <li>{@code AdminSupplierController /api/admin/supplier}:list/save/update/delete 须提供功能化供应商管理(返回 200,
|
|
|
+ * 非 410 废弃桩);</li>
|
|
|
+ * <li>{@code EcomSupplierController /api/admin/ecom-supplier}:list/all/save/update/delete 同上;</li>
|
|
|
+ * <li>SC-A-06 停用边界:停用(vendorStatus→disabled / toggle-status)前须校验供应商在售商品(on_shelf),
|
|
|
+ * 存在则拒绝并提示「请先下架在售商品」。</li>
|
|
|
+ * </ul></p>
|
|
|
+ */
|
|
|
+class AdminSupplierManageContractTest {
|
|
|
+
|
|
|
+ /** 设计载体:管理端供应商主体管理(§4.13 / §4.60.6,SC-A-06) */
|
|
|
+ private static final String ADMIN_SUPPLIER_CTRL =
|
|
|
+ "com.etotem.cfc.controller.admin.AdminSupplierController";
|
|
|
+
|
|
|
+ /** 设计载体:管理端电商供应商(§4.13 / §4.60.6,SC-A-06/SC-S-01) */
|
|
|
+ private static final String ECOM_SUPPLIER_CTRL =
|
|
|
+ "com.etotem.cfc.controller.admin.EcomSupplierController";
|
|
|
+
|
|
|
+ /** 设计载体:管理端供应商管理(§4.60.6,SC-A-06/SC-S-01)——已落地功能载体 */
|
|
|
+ private static final String SUPPLY_MANAGE_CTRL =
|
|
|
+ "com.etotem.cfc.controller.admin.SupplyManageController";
|
|
|
+
|
|
|
+ /** 活跃载体(API_REFERENCE §4.61 载体置换提示):供应体系服务 */
|
|
|
+ private static final String SUPPLY_SYSTEM_SERVICE =
|
|
|
+ "com.etotem.cfc.service.SupplySystemService";
|
|
|
+
|
|
|
+ // ==================== 契约宿主锚定(三设计载体均应存在) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("锚定-1:AdminSupplierController(/api/admin/supplier)应存在(SC-A-06)")
|
|
|
+ void anchor_adminSupplierControllerExists() {
|
|
|
+ assertTrue(loadClass(ADMIN_SUPPLIER_CTRL) != null,
|
|
|
+ "[admin.supplier.manage] AdminSupplierController 不存在:契约要求 /api/admin/supplier 提供"
|
|
|
+ + " list/save/update/delete 供应商主体管理(SC-A-06)。");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("锚定-2:EcomSupplierController(/api/admin/ecom-supplier)应存在(SC-A-06/SC-S-01)")
|
|
|
+ void anchor_ecomSupplierControllerExists() {
|
|
|
+ assertTrue(loadClass(ECOM_SUPPLIER_CTRL) != null,
|
|
|
+ "[admin.supplier.manage] EcomSupplierController 不存在:契约要求 /api/admin/ecom-supplier 提供"
|
|
|
+ + " list/all/save/update/delete 电商供应商管理。");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("锚定-3:SupplyManageController(/api/admin/supply-manage)应存在(SC-A-06/SC-S-01)")
|
|
|
+ void anchor_supplyManageControllerExists() {
|
|
|
+ assertTrue(loadClass(SUPPLY_MANAGE_CTRL) != null,
|
|
|
+ "[admin.supplier.manage] SupplyManageController 不存在:契约要求 /api/admin/supply-manage 提供"
|
|
|
+ + " list/detail/create/update/delete 供应商管理。");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== SC-A-06:AdminSupplierController 应提供功能化供应商 CRUD(红:当前为 410 废弃桩) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-06-R1:AdminSupplierController.list 应返回功能化供应商列表(200),当前为 410 废弃桩")
|
|
|
+ void adminSupplierList_functionalCrud() {
|
|
|
+ Object ctrl = contractHost(ADMIN_SUPPLIER_CTRL);
|
|
|
+ Result<?> r = (Result<?>) call(ctrl, "list", new HashMap<String, Object>());
|
|
|
+ assertEquals(200, r.getCode(),
|
|
|
+ "RED[admin.supplier.manage] AdminSupplierController.list 契约要求返回分页供应商列表(200),"
|
|
|
+ + "当前返回 " + r.getCode() + "/" + r.getMessage()
|
|
|
+ + "(410 废弃桩,SC-A-06 供应商列表未在设计载体落地)。");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-06-R2:AdminSupplierController.save 应创建供应商(200),当前为 410 废弃桩")
|
|
|
+ void adminSupplierSave_functionalCrud() {
|
|
|
+ Object ctrl = contractHost(ADMIN_SUPPLIER_CTRL);
|
|
|
+ Result<?> r = (Result<?>) call(ctrl, "save", new HashMap<String, Object>());
|
|
|
+ assertEquals(200, r.getCode(),
|
|
|
+ "RED[admin.supplier.manage] AdminSupplierController.save 契约要求创建供应商(200),"
|
|
|
+ + "当前返回 " + r.getCode() + "/" + r.getMessage() + "(410 废弃桩)。");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-06-R3:AdminSupplierController.update 应更新供应商(200),当前为 410 废弃桩")
|
|
|
+ void adminSupplierUpdate_functionalCrud() {
|
|
|
+ Object ctrl = contractHost(ADMIN_SUPPLIER_CTRL);
|
|
|
+ Result<?> r = (Result<?>) call(ctrl, "update", new HashMap<String, Object>());
|
|
|
+ assertEquals(200, r.getCode(),
|
|
|
+ "RED[admin.supplier.manage] AdminSupplierController.update 契约要求更新供应商(200),"
|
|
|
+ + "当前返回 " + r.getCode() + "/" + r.getMessage() + "(410 废弃桩)。");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-06-R4:AdminSupplierController.delete 应删除供应商(200),当前为 410 废弃桩")
|
|
|
+ void adminSupplierDelete_functionalCrud() {
|
|
|
+ Object ctrl = contractHost(ADMIN_SUPPLIER_CTRL);
|
|
|
+ Result<?> r = (Result<?>) call(ctrl, "delete", new HashMap<String, Object>());
|
|
|
+ assertEquals(200, r.getCode(),
|
|
|
+ "RED[admin.supplier.manage] AdminSupplierController.delete 契约要求删除供应商(200),"
|
|
|
+ + "当前返回 " + r.getCode() + "/" + r.getMessage() + "(410 废弃桩)。");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== SC-A-06/SC-S-01:EcomSupplierController 应提供功能化供应商 CRUD(红:当前为 410 废弃桩) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-06-R5:EcomSupplierController.list 应返回功能化供应商列表(200),当前为 410 废弃桩")
|
|
|
+ void ecomSupplierList_functionalCrud() {
|
|
|
+ Object ctrl = contractHost(ECOM_SUPPLIER_CTRL);
|
|
|
+ Result<?> r = (Result<?>) call(ctrl, "list", new HashMap<String, Object>());
|
|
|
+ assertEquals(200, r.getCode(),
|
|
|
+ "RED[admin.supplier.manage] EcomSupplierController.list 契约要求返回供应商列表(200),"
|
|
|
+ + "当前返回 " + r.getCode() + "/" + r.getMessage() + "(410 废弃桩)。");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-06-R6:EcomSupplierController.all 应返回全量供应商下拉(200),当前为 410 废弃桩")
|
|
|
+ void ecomSupplierAll_functionalCrud() {
|
|
|
+ Object ctrl = contractHost(ECOM_SUPPLIER_CTRL);
|
|
|
+ Result<?> r = (Result<?>) call(ctrl, "all");
|
|
|
+ assertEquals(200, r.getCode(),
|
|
|
+ "RED[admin.supplier.manage] EcomSupplierController.all 契约要求返回全量供应商(200),"
|
|
|
+ + "当前返回 " + r.getCode() + "/" + r.getMessage() + "(410 废弃桩)。");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-S-01-R1:EcomSupplierController.save 应创建供应商/入驻(200),当前为 410 废弃桩")
|
|
|
+ void ecomSupplierSave_functionalCrud() {
|
|
|
+ Object ctrl = contractHost(ECOM_SUPPLIER_CTRL);
|
|
|
+ Result<?> r = (Result<?>) call(ctrl, "save", (Object) new HashMap<String, Object>());
|
|
|
+ assertEquals(200, r.getCode(),
|
|
|
+ "RED[admin.supplier.manage] EcomSupplierController.save 契约要求创建供应商(200),"
|
|
|
+ + "当前返回 " + r.getCode() + "/" + r.getMessage() + "(410 废弃桩)。");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-06-R7:EcomSupplierController.update 应更新供应商(200),当前为 410 废弃桩")
|
|
|
+ void ecomSupplierUpdate_functionalCrud() {
|
|
|
+ Object ctrl = contractHost(ECOM_SUPPLIER_CTRL);
|
|
|
+ Result<?> r = (Result<?>) call(ctrl, "update", (Object) new HashMap<String, Object>());
|
|
|
+ assertEquals(200, r.getCode(),
|
|
|
+ "RED[admin.supplier.manage] EcomSupplierController.update 契约要求更新供应商(200),"
|
|
|
+ + "当前返回 " + r.getCode() + "/" + r.getMessage() + "(410 废弃桩)。");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-06-R8:EcomSupplierController.delete 应删除供应商(200),当前为 410 废弃桩")
|
|
|
+ void ecomSupplierDelete_functionalCrud() {
|
|
|
+ Object ctrl = contractHost(ECOM_SUPPLIER_CTRL);
|
|
|
+ Result<?> r = (Result<?>) call(ctrl, "delete", new HashMap<String, Object>());
|
|
|
+ assertEquals(200, r.getCode(),
|
|
|
+ "RED[admin.supplier.manage] EcomSupplierController.delete 契约要求删除供应商(200),"
|
|
|
+ + "当前返回 " + r.getCode() + "/" + r.getMessage() + "(410 废弃桩)。");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== SC-A-06 停用边界:有在售商品的供应商停用需先下架(红:全库未实现守卫) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-06-R9:停用供应商前须校验无在售商品(「有在售商品的供应商停用需先下架」守卫缺失)")
|
|
|
+ void supplierDisable_boundary_guardOnSaleProducts() {
|
|
|
+ // 边界(应用场景总览 SC-A-06):有在售商品的供应商停用需先下架。
|
|
|
+ // 停用入口:SupplyManageController.update(vendorStatus→disabled) 与 SupplySystemService.toggleStatus。
|
|
|
+ // 契约:停用前必须校验供应商在售商品(on_shelf),存在则拒绝并提示「请先下架在售商品」。
|
|
|
+ Object manage = contractHost(SUPPLY_MANAGE_CTRL);
|
|
|
+ boolean manageGuard = hasField(manage.getClass(), "productMapper")
|
|
|
+ || hasContractMethod(manage, "hasOnShelfProducts", Long.class);
|
|
|
+ Object sysSvc = contractHost(SUPPLY_SYSTEM_SERVICE);
|
|
|
+ boolean sysGuard = hasContractMethod(sysSvc, "hasOnShelfProducts", Long.class);
|
|
|
+ assertTrue(manageGuard || sysGuard,
|
|
|
+ "RED[admin.supplier.manage] SC-A-06 边界「有在售商品的供应商停用需先下架」未实现:"
|
|
|
+ + "停用入口(SupplyManageController.update / SupplySystemService.toggleStatus)直接翻转状态,"
|
|
|
+ + "无在售商品守卫 —— GREEN 阶段须在停用前校验该供应商 on_shelf 商品,存在则拒绝并提示先下架。");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== SC-S-01 入驻申请:已落地功能载体保留绿证断言(实现现状证据) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-S-01-1:入驻 create 手机号缺失应驳回(资质校验,功能载体已落地)")
|
|
|
+ void supplyManageCreate_phoneRequired_validation() throws Exception {
|
|
|
+ SupplyManageControllerFixture fx = supplyManageFixture(mock(UserMapper.class));
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("nickname", "新供应商");
|
|
|
+ Result<?> r = (Result<?>) call(fx.ctrl, "create", params);
|
|
|
+ assertEquals(500, r.getCode(), "入驻申请手机号缺失须驳回(资质不全驳回边界)");
|
|
|
+ assertTrue(r.getMessage() != null && r.getMessage().contains("手机号不能为空"),
|
|
|
+ "入驻申请手机号缺失应提示「手机号不能为空」,实际: " + r.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-S-01-2:入驻 create 默认审核通过(vendorStatus=approved + supplier_admin 角色,功能载体已落地)")
|
|
|
+ void supplyManageCreate_createsApprovedSupplier() throws Exception {
|
|
|
+ UserMapper userMapper = mock(UserMapper.class);
|
|
|
+ when(userMapper.selectOne(any(Wrapper.class))).thenReturn(null);
|
|
|
+ when(userMapper.insert(any(User.class))).thenAnswer(inv -> {
|
|
|
+ ((User) inv.getArgument(0)).setId(88L);
|
|
|
+ return 1;
|
|
|
+ });
|
|
|
+ SupplyManageControllerFixture fx = supplyManageFixture(userMapper);
|
|
|
+
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("phone", "13800138000");
|
|
|
+ Result<?> r = (Result<?>) call(fx.ctrl, "create", params);
|
|
|
+
|
|
|
+ assertEquals(200, r.getCode(), "入驻 create 应成功建档");
|
|
|
+ User created = (User) r.getData();
|
|
|
+ assertEquals("approved", created.getVendorStatus(), "入驻默认审核通过(SC-S-01:平台审核→入驻成功)");
|
|
|
+ assertEquals("supplier_admin", created.getRole(), "入驻用户应具备供应商管理角色");
|
|
|
+ verify(userMapper).selectOne(any(Wrapper.class));
|
|
|
+ verify(userMapper).insert(any(User.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-06-3:detail 不存在的供应商应返回「供应商不存在」(功能载体已落地)")
|
|
|
+ void supplyManageDetail_supplierNotFound() throws Exception {
|
|
|
+ UserMapper userMapper = mock(UserMapper.class);
|
|
|
+ when(userMapper.selectById(999L)).thenReturn(null);
|
|
|
+ SupplyManageControllerFixture fx = supplyManageFixture(userMapper);
|
|
|
+
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("id", 999L);
|
|
|
+ Result<?> r = (Result<?>) call(fx.ctrl, "detail", params);
|
|
|
+
|
|
|
+ assertEquals(500, r.getCode(), "不存在的供应商须返回错误");
|
|
|
+ assertTrue(r.getMessage() != null && r.getMessage().contains("供应商不存在"),
|
|
|
+ "不存在的供应商应提示「供应商不存在」,实际: " + r.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 反射辅助(锚定契约宿主,避免编译期依赖未落地类) ====================
|
|
|
+
|
|
|
+ private static final class SupplyManageControllerFixture {
|
|
|
+ final Object ctrl;
|
|
|
+ SupplyManageControllerFixture(Object ctrl) { this.ctrl = ctrl; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private SupplyManageControllerFixture supplyManageFixture(UserMapper userMapper) throws Exception {
|
|
|
+ Object ctrl = Class.forName(SUPPLY_MANAGE_CTRL).getDeclaredConstructor().newInstance();
|
|
|
+ inject(ctrl, "userMapper", userMapper);
|
|
|
+ inject(ctrl, "userService", mock(UserService.class));
|
|
|
+ inject(ctrl, "realNameBackfillService", mock(RealNameBackfillService.class));
|
|
|
+ return new SupplyManageControllerFixture(ctrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Class<?> loadClass(String name) {
|
|
|
+ try {
|
|
|
+ return Class.forName(name, false, Thread.currentThread().getContextClassLoader());
|
|
|
+ } catch (ClassNotFoundException e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean hasField(Class<?> clazz, String field) {
|
|
|
+ for (Field f : clazz.getDeclaredFields()) {
|
|
|
+ if (f.getName().equals(field)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean hasContractMethod(Object host, String method, Class<?>... paramTypes) {
|
|
|
+ for (Method m : host.getClass().getMethods()) {
|
|
|
+ if (m.getName().equals(method) && Arrays.equals(m.getParameterTypes(), paramTypes)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 实例化契约宿主;宿主缺失即断言失败(红)。 */
|
|
|
+ private Object contractHost(String fqcn) {
|
|
|
+ try {
|
|
|
+ Class<?> clazz = Class.forName(fqcn);
|
|
|
+ return clazz.getDeclaredConstructor().newInstance();
|
|
|
+ } catch (ClassNotFoundException e) {
|
|
|
+ fail("RED[admin.supplier.manage] 契约宿主 " + simpleName(fqcn) + " 未实现(实现未落地):"
|
|
|
+ + e.getMessage());
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ } catch (Exception e) {
|
|
|
+ fail("契约宿主 " + fqcn + " 实例化失败:" + e.getMessage());
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void inject(Object target, String field, Object value) throws Exception {
|
|
|
+ Field f = target.getClass().getDeclaredField(field);
|
|
|
+ f.setAccessible(true);
|
|
|
+ f.set(target, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 按方法名 + 参数个数调用(宽松适配基本/包装类型);宿主或方法缺失即断言失败。 */
|
|
|
+ private Object call(Object host, String method, Object... args) {
|
|
|
+ for (Method m : host.getClass().getMethods()) {
|
|
|
+ if (m.getName().equals(method) && m.getParameterCount() == args.length) {
|
|
|
+ try {
|
|
|
+ return m.invoke(host, args);
|
|
|
+ } catch (Exception e) {
|
|
|
+ fail("契约方法 " + simpleName(host.getClass().getName()) + "#" + method
|
|
|
+ + " 调用失败:" + e.getMessage());
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ fail("RED[admin.supplier.manage] 契约方法 " + simpleName(host.getClass().getName()) + "#" + method
|
|
|
+ + " 未实现");
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ }
|
|
|
+
|
|
|
+ private String simpleName(String fqcn) {
|
|
|
+ int idx = fqcn.lastIndexOf('.');
|
|
|
+ return idx >= 0 ? fqcn.substring(idx + 1) : fqcn;
|
|
|
+ }
|
|
|
+}
|