|
|
@@ -0,0 +1,101 @@
|
|
|
+package com.payment.platform.module.payment.har.controller;
|
|
|
+
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.springframework.security.access.prepost.PreAuthorize;
|
|
|
+import org.springframework.web.bind.annotation.PostMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
+
|
|
|
+import java.lang.reflect.Method;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 管理端订单控制器的**接线**断言。
|
|
|
+ *
|
|
|
+ * 只看注解,不碰 Spring,也不调服务:这里钉的是「这个端点存在、挂在哪个 URL、认哪个权限串」。
|
|
|
+ * 服务行为由 {@code HarOrderServiceTest} 覆盖,两者不重叠。
|
|
|
+ *
|
|
|
+ * 为什么值得单独一层:退款是本控制器第一个**动钱**的端点,而权限串是
|
|
|
+ * {@code PermissionCheck.hasAny} 的**全等匹配**(见 LeshouyingAdminWiringTest 的实测记录)
|
|
|
+ * ⇒ 写错的后果是 403(fail-closed,安全但功能全废),而**复用**一个已有的串
|
|
|
+ * (比如图省事写 :sync)后果相反:能查一笔单的人就能把钱退了。这两种都要有测试盯着。
|
|
|
+ * 权限串同时是交付给用户执行的菜单 SQL 里的那一格 —— 两边对不上就是「菜单给了、点进去 403」。
|
|
|
+ */
|
|
|
+class HarOrderControllerTest {
|
|
|
+
|
|
|
+ /** 前端与菜单 SQL 都按这个 URL 走,地址错了两边都白搭 */
|
|
|
+ private static final String BASE = "/payment/har/order";
|
|
|
+
|
|
|
+ private static Method m(String name, Class<?>... args) throws NoSuchMethodException {
|
|
|
+ return HarOrderController.class.getDeclaredMethod(name, args);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String preAuthorize(Method method) {
|
|
|
+ PreAuthorize a = method.getAnnotation(PreAuthorize.class);
|
|
|
+ assertNotNull(a, method.getName() + " 没有 @PreAuthorize —— 该端点不受权限管控");
|
|
|
+ return a.value();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 控制器上每个端点的权限串,按方法名索引 */
|
|
|
+ private static Map<String, String> permissionByMethod() throws Exception {
|
|
|
+ Map<String, String> out = new LinkedHashMap<>();
|
|
|
+ for (Method method : HarOrderController.class.getDeclaredMethods()) {
|
|
|
+ // 只收端点:有 @GetMapping/@PostMapping 这些组合注解的方法
|
|
|
+ if (method.getAnnotation(PostMapping.class) != null
|
|
|
+ || method.getAnnotation(org.springframework.web.bind.annotation.GetMapping.class) != null) {
|
|
|
+ out.put(method.getName(), preAuthorize(method));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void refund_isPostOnIdRefundPathUnderTheOrderBase() throws Exception {
|
|
|
+ assertEquals(BASE, HarOrderController.class.getAnnotation(RequestMapping.class).value()[0],
|
|
|
+ "类上的基路径变了的话,前端与菜单 SQL 都要跟着改");
|
|
|
+
|
|
|
+ PostMapping mapping = m("refund", Long.class,
|
|
|
+ com.payment.platform.module.payment.har.dto.HarOrderRefundDTO.class)
|
|
|
+ .getAnnotation(PostMapping.class);
|
|
|
+
|
|
|
+ assertNotNull(mapping, "退款必须是 POST");
|
|
|
+ assertArrayEquals(new String[]{"/{id}/refund"}, mapping.value(),
|
|
|
+ "实测前端与交付 SQL 都按 /payment/har/order/{id}/refund 写");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 退款**必须有自己的权限串**,不能复用任何一个已有端点的。
|
|
|
+ *
|
|
|
+ * 复用是这里唯一会放大权限的写法:{@code hasAny} 全等匹配,所以「能看列表」
|
|
|
+ * 直接就变成「能退款」。这条断言就是拦它。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void refund_hasItsOwnPermission_notReusedFromAnyOtherEndpoint() throws Exception {
|
|
|
+ Map<String, String> perms = permissionByMethod();
|
|
|
+ String refund = perms.get("refund");
|
|
|
+
|
|
|
+ assertNotNull(refund, "refund 端点没被识别到 —— 注解形状变了?实测已收端点: " + perms.keySet());
|
|
|
+ assertEquals("@perm.hasAny('module_payment:har:order:refund')", refund,
|
|
|
+ "权限串要和交付给用户的菜单 SQL 逐字相同");
|
|
|
+
|
|
|
+ perms.forEach((name, value) -> {
|
|
|
+ if (!"refund".equals(name)) {
|
|
|
+ assertNotEquals(refund, value,
|
|
|
+ "退款复用了 " + name + " 的权限串 ⇒ 拿到那个权限的人就能把钱退了。实测: " + value);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 一个端点都不许漏掉权限注解(将来新增端点时这条会替我们拦一次) */
|
|
|
+ @Test
|
|
|
+ void everyEndpoint_carriesAPermission() throws Exception {
|
|
|
+ Map<String, String> perms = permissionByMethod();
|
|
|
+
|
|
|
+ assertEquals(5, perms.size(), "端点数量变了(新增端点请同时确认它的权限串)实测: " + perms.keySet());
|
|
|
+ perms.forEach((name, value) -> assertTrue(
|
|
|
+ value.startsWith("@perm.hasAny('module_payment:har:order:"),
|
|
|
+ name + " 的权限串不在本模块命名空间下,实测: " + value));
|
|
|
+ }
|
|
|
+}
|