This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5631-chaining-require-annotations in repository https://gitbox.apache.org/repos/asf/struts.git
commit 6970a3b2a58b5bd59dc22514fe18cab983b28141 Author: Lukasz Lenart <[email protected]> AuthorDate: Wed May 27 08:17:36 2026 +0200 WW-5631 test(chaining): add failing @StrutsParameter enforcement tests Co-Authored-By: Claude Opus 4.7 <[email protected]> --- .../interceptor/ChainingInterceptorTest.java | 91 ++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/core/src/test/java/org/apache/struts2/interceptor/ChainingInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/ChainingInterceptorTest.java index 85df164cc..e59780a93 100644 --- a/core/src/test/java/org/apache/struts2/interceptor/ChainingInterceptorTest.java +++ b/core/src/test/java/org/apache/struts2/interceptor/ChainingInterceptorTest.java @@ -27,6 +27,9 @@ import org.apache.struts2.SimpleAction; import org.apache.struts2.TestBean; import org.apache.struts2.XWorkTestCase; import org.apache.struts2.util.ValueStack; +import org.apache.struts2.interceptor.parameter.StrutsParameterAuthorizer; +import org.apache.struts2.ognl.OgnlUtil; +import org.apache.struts2.util.ProxyService; import java.util.*; @@ -149,6 +152,94 @@ public class ChainingInterceptorTest extends XWorkTestCase { } + private StrutsParameterAuthorizer buildAuthorizer(boolean requireAnnotations, boolean transitionMode) { + StrutsParameterAuthorizer authorizer = new StrutsParameterAuthorizer(); + authorizer.setOgnlUtil(container.getInstance(OgnlUtil.class)); + authorizer.setProxyService(container.getInstance(ProxyService.class)); + authorizer.setRequireAnnotations(String.valueOf(requireAnnotations)); + authorizer.setRequireAnnotationsTransitionMode(String.valueOf(transitionMode)); + return authorizer; + } + + private void enableChainingEnforcement(boolean requireAnnotations, boolean transitionMode) { + interceptor.setParameterAuthorizer(buildAuthorizer(requireAnnotations, transitionMode)); + interceptor.setRequireAnnotations("true"); + } + + public void testFlagOffCopiesUnannotatedProperty() throws Exception { + AnnotatedChainingAction source = new AnnotatedChainingAction(); + source.setManagerApproved(true); + UnannotatedChainingAction target = new UnannotatedChainingAction(); + mockInvocation.matchAndReturn("getAction", target); + stack.push(source); + stack.push(target); + + interceptor.intercept(invocation); + + assertTrue("legacy chaining should copy the property when flag is off", target.getManagerApproved()); + } + + public void testFlagOnSkipsUnannotatedProperty() throws Exception { + AnnotatedChainingAction source = new AnnotatedChainingAction(); + source.setManagerApproved(true); + UnannotatedChainingAction target = new UnannotatedChainingAction(); + mockInvocation.matchAndReturn("getAction", target); + stack.push(source); + stack.push(target); + + enableChainingEnforcement(true, false); + interceptor.intercept(invocation); + + assertFalse("unannotated target property must NOT be copied when enforcement is on", + target.getManagerApproved()); + } + + public void testFlagOnCopiesAnnotatedProperty() throws Exception { + AnnotatedChainingAction source = new AnnotatedChainingAction(); + source.setManagerApproved(true); + AnnotatedChainingAction target = new AnnotatedChainingAction(); + mockInvocation.matchAndReturn("getAction", target); + stack.push(source); + stack.push(target); + + enableChainingEnforcement(true, false); + interceptor.intercept(invocation); + + assertTrue("annotated target property should be copied when enforcement is on", + target.getManagerApproved()); + } + + public void testTransitionModeCopiesNonNestedUnannotatedProperty() throws Exception { + AnnotatedChainingAction source = new AnnotatedChainingAction(); + source.setManagerApproved(true); + UnannotatedChainingAction target = new UnannotatedChainingAction(); + mockInvocation.matchAndReturn("getAction", target); + stack.push(source); + stack.push(target); + + enableChainingEnforcement(true, true); + interceptor.intercept(invocation); + + assertTrue("transition mode should copy depth-0 property without annotation", + target.getManagerApproved()); + } + + public void testRequireAnnotationsFalseIsNoOp() throws Exception { + AnnotatedChainingAction source = new AnnotatedChainingAction(); + source.setManagerApproved(true); + UnannotatedChainingAction target = new UnannotatedChainingAction(); + mockInvocation.matchAndReturn("getAction", target); + stack.push(source); + stack.push(target); + + interceptor.setParameterAuthorizer(buildAuthorizer(false, false)); + interceptor.setRequireAnnotations("true"); + interceptor.intercept(invocation); + + assertTrue("when global requireAnnotations is off, enforcement is a no-op", + target.getManagerApproved()); + } + @Override protected void setUp() throws Exception { super.setUp();
