66
77import java .lang .reflect .Method ;
88import java .util .List ;
9+ import java .util .Optional ;
910
1011import static org .junit .jupiter .api .Assertions .assertEquals ;
12+ import static org .junit .jupiter .api .Assertions .assertTrue ;
1113import static org .mockito .Mockito .mock ;
1214import static org .mockito .Mockito .when ;
1315
@@ -30,85 +32,107 @@ public void create(Long articleId, Long uid, TestReq req) {
3032 }
3133 }
3234
33- @ Test
34- void resolvesSpelKeysFromMethodArgs () throws Exception {
35+ private ProceedingJoinPoint pjpWith (Object [] args ) throws Exception {
3536 Method method = Target .class .getMethod ("create" , Long .class , Long .class , TestReq .class );
3637 MethodSignature sig = mock (MethodSignature .class );
3738 when (sig .getMethod ()).thenReturn (method );
3839
3940 ProceedingJoinPoint pjp = mock (ProceedingJoinPoint .class );
4041 when (pjp .getSignature ()).thenReturn (sig );
42+ when (pjp .getArgs ()).thenReturn (args );
43+ return pjp ;
44+ }
4145
46+ private TestReq reqWith (String requestId ) {
4247 TestReq req = new TestReq ();
43- req .setRequestId ("req-123" );
44- when (pjp .getArgs ()).thenReturn (new Object []{42L , 7L , req });
48+ req .setRequestId (requestId );
49+ return req ;
50+ }
51+
52+ @ Test
53+ void allKeysResolve_returnsValuesInOrder () throws Exception {
54+ ProceedingJoinPoint pjp = pjpWith (new Object []{42L , 7L , reqWith ("req-123" )});
4555
4656 RepeatExecuteKeyResolver resolver = new RepeatExecuteKeyResolver ();
47- List <String > keys = resolver .resolve (pjp , new String []{"#articleId" , "#uid" , "#req.requestId" });
57+ Optional < List <String > > keys = resolver .resolve (pjp , new String []{"#articleId" , "#uid" , "#req.requestId" });
4858
49- assertEquals (List .of ("42" , "7" , "req-123" ), keys );
59+ assertEquals (Optional . of ( List .of ("42" , "7" , "req-123" ) ), keys );
5060 }
5161
5262 @ Test
53- void blankExpressionsAreSkipped () throws Exception {
54- Method method = Target .class .getMethod ("create" , Long .class , Long .class , TestReq .class );
55- MethodSignature sig = mock (MethodSignature .class );
56- when (sig .getMethod ()).thenReturn (method );
57-
58- ProceedingJoinPoint pjp = mock (ProceedingJoinPoint .class );
59- when (pjp .getSignature ()).thenReturn (sig );
60- when (pjp .getArgs ()).thenReturn (new Object []{42L , 7L , new TestReq ()});
63+ void nullIntermediateProperty_makesWholeKeySetUnusable () throws Exception {
64+ // req 为 null → #req.requestId 抛 EvaluationException。
65+ // 绝不能"丢掉这一项、只留 #uid":那样 Key 会退化成 (articleId, uid),
66+ // 把同一用户 30s 内的第二条正常评论误判成重复。
67+ ProceedingJoinPoint pjp = pjpWith (new Object []{42L , 7L , null });
6168
6269 RepeatExecuteKeyResolver resolver = new RepeatExecuteKeyResolver ();
63- List <String > keys = resolver .resolve (pjp , new String []{"" , "#uid" });
70+ Optional < List <String >> keys = resolver .resolve (pjp , new String []{"#req.requestId " , "#uid" });
6471
65- assertEquals ( List . of ( "7" ), keys );
72+ assertTrue ( keys . isEmpty () );
6673 }
6774
6875 @ Test
69- void nullIntermediateProperty_skipsThatKey () throws Exception {
70- Method method = Target .class .getMethod ("create" , Long .class , Long .class , TestReq .class );
71- MethodSignature sig = mock (MethodSignature .class );
72- when (sig .getMethod ()).thenReturn (method );
73-
74- ProceedingJoinPoint pjp = mock (ProceedingJoinPoint .class );
75- when (pjp .getSignature ()).thenReturn (sig );
76- when (pjp .getArgs ()).thenReturn (new Object []{42L , 7L , null });
76+ void misspelledProperty_makesWholeKeySetUnusable () throws Exception {
77+ // 对象上的属性名写错(#req.artcleId)→ EvaluationException。
78+ // 绝不能"丢掉这一项、只留 #uid":那样 Key 会静默退化成更粗的粒度,误伤正常请求。
79+ ProceedingJoinPoint pjp = pjpWith (new Object []{42L , 7L , reqWith ("req-123" )});
7780
7881 RepeatExecuteKeyResolver resolver = new RepeatExecuteKeyResolver ();
79- List <String > keys = resolver .resolve (pjp , new String []{"#req.requestId " , "#uid" });
82+ Optional < List <String >> keys = resolver .resolve (pjp , new String []{"#req.artcleId " , "#uid" });
8083
81- assertEquals ( List . of ( "7" ), keys );
84+ assertTrue ( keys . isEmpty () );
8285 }
8386
8487 @ Test
85- void nullOrEmptyKeysArray_returnsEmptyList () throws Exception {
86- Method method = Target .class .getMethod ("create" , Long .class , Long .class , TestReq .class );
87- MethodSignature sig = mock (MethodSignature .class );
88- when (sig .getMethod ()).thenReturn (method );
88+ void unknownVariable_makesWholeKeySetUnusable () throws Exception {
89+ // 变量名写错(#artcleId):SpEL 解析为 null,同样必须整组作废
90+ ProceedingJoinPoint pjp = pjpWith (new Object []{42L , 7L , reqWith ("req-123" )});
8991
90- ProceedingJoinPoint pjp = mock (ProceedingJoinPoint .class );
91- when (pjp .getSignature ()).thenReturn (sig );
92- when (pjp .getArgs ()).thenReturn (new Object []{42L , 7L , new TestReq ()});
92+ RepeatExecuteKeyResolver resolver = new RepeatExecuteKeyResolver ();
93+ Optional <List <String >> keys = resolver .resolve (pjp , new String []{"#artcleId" , "#uid" });
94+
95+ assertTrue (keys .isEmpty ());
96+ }
97+
98+ @ Test
99+ void malformedExpression_doesNotThrow_makesKeySetUnusable () throws Exception {
100+ // 语法错误抛的是 ParseException(不是 EvaluationException):
101+ // 必须兜住,否则异常穿透切面 → 业务请求 500,违反幂等组件 fail-open 契约
102+ ProceedingJoinPoint pjp = pjpWith (new Object []{42L , 7L , reqWith ("req-123" )});
93103
94104 RepeatExecuteKeyResolver resolver = new RepeatExecuteKeyResolver ();
95- assertEquals (List .of (), resolver .resolve (pjp , null ));
96- assertEquals (List .of (), resolver .resolve (pjp , new String []{}));
105+ Optional <List <String >> keys = resolver .resolve (pjp , new String []{"#articleId +" , "#uid" });
106+
107+ assertTrue (keys .isEmpty ());
97108 }
98109
99110 @ Test
100- void nullPropertyValue_resolvesToEmptyString () throws Exception {
101- Method method = Target .class .getMethod ("create" , Long .class , Long .class , TestReq .class );
102- MethodSignature sig = mock (MethodSignature .class );
103- when (sig .getMethod ()).thenReturn (method );
111+ void blankExpression_makesKeySetUnusable () throws Exception {
112+ ProceedingJoinPoint pjp = pjpWith (new Object []{42L , 7L , reqWith ("req-123" )});
104113
105- ProceedingJoinPoint pjp = mock (ProceedingJoinPoint .class );
106- when (pjp .getSignature ()).thenReturn (sig );
107- when (pjp .getArgs ()).thenReturn (new Object []{42L , 7L , new TestReq ()});
114+ RepeatExecuteKeyResolver resolver = new RepeatExecuteKeyResolver ();
115+ Optional <List <String >> keys = resolver .resolve (pjp , new String []{"" , "#uid" });
116+
117+ assertTrue (keys .isEmpty ());
118+ }
119+
120+ @ Test
121+ void nullPropertyValue_makesKeySetUnusable () throws Exception {
122+ ProceedingJoinPoint pjp = pjpWith (new Object []{42L , 7L , reqWith (null )});
108123
109124 RepeatExecuteKeyResolver resolver = new RepeatExecuteKeyResolver ();
110- List <String > keys = resolver .resolve (pjp , new String []{"#req.requestId" });
125+ Optional < List <String > > keys = resolver .resolve (pjp , new String []{"#req.requestId" });
111126
112- assertEquals (List .of ("" ), keys );
127+ assertTrue (keys .isEmpty ());
128+ }
129+
130+ @ Test
131+ void nullOrEmptyKeysArray_isUnusable () throws Exception {
132+ ProceedingJoinPoint pjp = pjpWith (new Object []{42L , 7L , reqWith ("req-123" )});
133+
134+ RepeatExecuteKeyResolver resolver = new RepeatExecuteKeyResolver ();
135+ assertTrue (resolver .resolve (pjp , null ).isEmpty ());
136+ assertTrue (resolver .resolve (pjp , new String []{}).isEmpty ());
113137 }
114138}
0 commit comments