This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git


The following commit(s) were added to refs/heads/main by this push:
     new fda6ca9f2 WW-5786 test(convention): pin HTTP method enforcement on 
Convention actions
fda6ca9f2 is described below

commit fda6ca9f24e33ffcf2ce024b901abf8545d74dae
Author: Lukasz Lenart <[email protected]>
AuthorDate: Fri Oct 23 10:25:03 2026 +0200

    WW-5786 test(convention): pin HTTP method enforcement on Convention actions
    
    Convention actions have no XML declaration and so no http-method
    attribute; the HttpGet/HttpPost/... annotations on the class or method
    are what HttpMethodInterceptor enforces for them. Drive four annotated
    Convention actions through the real dispatcher and assert: an allowed
    method executes, a disallowed one gets 405 plus Allow when nothing maps
    bad-request, and the bad-request result is used when Convention
    discovers a template for it or @Result declares one. Covers class-level
    and method-level annotations, the latter on an @Action-mapped method
    whose name differs from the action name, and pins that the scanned
    ActionConfigs carry no http-method set of their own.
    
    The fixtures sit in actions.httpmethod under a package-level @Namespace,
    so their templates and bad-request lookups cannot collide with other
    fixtures in the shared root namespace. A root-level package locator
    ("actions") only yields a namespace for sub-packages when a dot precedes
    it, hence the explicit annotation.
    
    Verified by mutating isAllowed() to always return true: the four
    rejection tests fail, the five others stay green.
    
    Co-Authored-By: Claude Mythos 5.1 <[email protected]>
---
 .../test/java/actions/httpmethod/OrdersAction.java |  36 ++++++
 .../java/actions/httpmethod/PostOnlyAction.java    |  35 +++++
 .../java/actions/httpmethod/ProfileAction.java     |  36 ++++++
 .../test/java/actions/httpmethod/UploadAction.java |  35 +++++
 .../test/java/actions/httpmethod/package-info.java |  22 ++++
 .../convention/ConventionHttpMethodTest.java       | 141 +++++++++++++++++++++
 .../resources/httpmethod/create-order-success.ftl  |  19 +++
 .../resources/httpmethod/post-only-success.ftl     |  19 +++
 .../test/resources/httpmethod/profile-rejected.ftl |  19 +++
 .../test/resources/httpmethod/profile-success.ftl  |  19 +++
 .../resources/httpmethod/upload-bad-request.ftl    |  19 +++
 .../test/resources/httpmethod/upload-success.ftl   |  19 +++
 12 files changed, 419 insertions(+)

diff --git 
a/plugins/convention/src/test/java/actions/httpmethod/OrdersAction.java 
b/plugins/convention/src/test/java/actions/httpmethod/OrdersAction.java
new file mode 100644
index 000000000..a75ba9db8
--- /dev/null
+++ b/plugins/convention/src/test/java/actions/httpmethod/OrdersAction.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package actions.httpmethod;
+
+import org.apache.struts2.ActionSupport;
+import org.apache.struts2.convention.annotation.Action;
+import org.apache.struts2.interceptor.httpmethod.HttpPost;
+
+/**
+ * Method-level restriction on an {@code @Action}-mapped method whose name 
differs from the action name.
+ * Rendered by create-order-success.ftl.
+ */
+public class OrdersAction extends ActionSupport {
+
+    @Action("create-order")
+    @HttpPost
+    public String create() {
+        return SUCCESS;
+    }
+}
diff --git 
a/plugins/convention/src/test/java/actions/httpmethod/PostOnlyAction.java 
b/plugins/convention/src/test/java/actions/httpmethod/PostOnlyAction.java
new file mode 100644
index 000000000..496a4e7af
--- /dev/null
+++ b/plugins/convention/src/test/java/actions/httpmethod/PostOnlyAction.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package actions.httpmethod;
+
+import org.apache.struts2.ActionSupport;
+import org.apache.struts2.interceptor.httpmethod.HttpPost;
+
+/**
+ * Class-level restriction on a Convention action with no {@code bad-request} 
result anywhere.
+ * Rendered by post-only-success.ftl.
+ */
+@HttpPost
+public class PostOnlyAction extends ActionSupport {
+
+    @Override
+    public String execute() {
+        return SUCCESS;
+    }
+}
diff --git 
a/plugins/convention/src/test/java/actions/httpmethod/ProfileAction.java 
b/plugins/convention/src/test/java/actions/httpmethod/ProfileAction.java
new file mode 100644
index 000000000..f79e93093
--- /dev/null
+++ b/plugins/convention/src/test/java/actions/httpmethod/ProfileAction.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package actions.httpmethod;
+
+import org.apache.struts2.ActionSupport;
+import org.apache.struts2.convention.annotation.Result;
+import org.apache.struts2.interceptor.httpmethod.HttpPut;
+
+/**
+ * Class-level restriction whose {@code bad-request} result is declared with 
{@code @Result}.
+ */
+@HttpPut
+@Result(name = "bad-request", location = "/httpmethod/profile-rejected.ftl", 
type = "freemarker")
+public class ProfileAction extends ActionSupport {
+
+    @Override
+    public String execute() {
+        return SUCCESS;
+    }
+}
diff --git 
a/plugins/convention/src/test/java/actions/httpmethod/UploadAction.java 
b/plugins/convention/src/test/java/actions/httpmethod/UploadAction.java
new file mode 100644
index 000000000..09288a2a8
--- /dev/null
+++ b/plugins/convention/src/test/java/actions/httpmethod/UploadAction.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package actions.httpmethod;
+
+import org.apache.struts2.ActionSupport;
+import org.apache.struts2.interceptor.httpmethod.HttpPost;
+
+/**
+ * Class-level restriction whose {@code bad-request} result is discovered by 
Convention
+ * from upload-bad-request.ftl, with no result annotation.
+ */
+@HttpPost
+public class UploadAction extends ActionSupport {
+
+    @Override
+    public String execute() {
+        return SUCCESS;
+    }
+}
diff --git 
a/plugins/convention/src/test/java/actions/httpmethod/package-info.java 
b/plugins/convention/src/test/java/actions/httpmethod/package-info.java
new file mode 100644
index 000000000..2b67b85e9
--- /dev/null
+++ b/plugins/convention/src/test/java/actions/httpmethod/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+@Namespace("/httpmethod")
+package actions.httpmethod;
+
+import org.apache.struts2.convention.annotation.Namespace;
diff --git 
a/plugins/convention/src/test/java/org/apache/struts2/convention/ConventionHttpMethodTest.java
 
b/plugins/convention/src/test/java/org/apache/struts2/convention/ConventionHttpMethodTest.java
new file mode 100644
index 000000000..9abac90db
--- /dev/null
+++ 
b/plugins/convention/src/test/java/org/apache/struts2/convention/ConventionHttpMethodTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.convention;
+
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.struts2.config.entities.ActionConfig;
+import org.apache.struts2.dispatcher.Dispatcher;
+import org.apache.struts2.dispatcher.mapper.ActionMapping;
+import org.apache.struts2.junit.StrutsJUnit4TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Convention actions carry no {@code http-method} attribute, so the HTTP 
method annotations on the
+ * action class or method are what {@code HttpMethodInterceptor} enforces for 
them (WW-5786).
+ * Fixtures live in {@code actions.httpmethod}, whose package-level {@code 
@Namespace} gives them the
+ * {@code /httpmethod} namespace and result path of their own.
+ */
+public class ConventionHttpMethodTest extends StrutsJUnit4TestCase<Object> {
+
+    private static final String NAMESPACE = "/httpmethod";
+
+    @Test
+    public void conventionActionsDeclareNoHttpMethodAttribute() {
+        for (String actionName : new String[]{"post-only", "create-order", 
"upload", "profile"}) {
+            ActionConfig config = 
configuration.getRuntimeConfiguration().getActionConfig(NAMESPACE, actionName);
+            assertNotNull(actionName + " must be mapped in " + NAMESPACE, 
config);
+            assertTrue(actionName + " must rely on annotations alone", 
config.getHttpMethods().isEmpty());
+        }
+    }
+
+    @Test
+    public void classLevelAnnotationAllowsListedMethod() throws Exception {
+        request.setMethod("POST");
+
+        String output = executeAction(NAMESPACE + "/post-only.action");
+
+        assertTrue(output.contains("post-only executed"));
+    }
+
+    @Test
+    public void classLevelAnnotationRejectsWith405WhenNoBadRequestResult() 
throws Exception {
+        dispatch("GET", NAMESPACE + "/post-only.action");
+
+        assertEquals(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 
response.getStatus());
+        assertEquals("POST", response.getHeader("Allow"));
+        assertEquals("", response.getContentAsString());
+    }
+
+    @Test
+    public void methodLevelAnnotationOnActionMappedMethodAllowsListedMethod() 
throws Exception {
+        request.setMethod("POST");
+
+        String output = executeAction(NAMESPACE + "/create-order.action");
+
+        assertTrue(output.contains("create-order executed"));
+    }
+
+    @Test
+    public void methodLevelAnnotationOnActionMappedMethodRejectsWith405() 
throws Exception {
+        dispatch("GET", NAMESPACE + "/create-order.action");
+
+        assertEquals(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 
response.getStatus());
+        assertEquals("POST", response.getHeader("Allow"));
+        assertEquals("", response.getContentAsString());
+    }
+
+    @Test
+    public void actionWithConventionBadRequestTemplateAllowsListedMethod() 
throws Exception {
+        request.setMethod("POST");
+
+        String output = executeAction(NAMESPACE + "/upload.action");
+
+        assertTrue(output.contains("upload executed"));
+    }
+
+    @Test
+    public void rejectionRendersBadRequestTemplateDiscoveredByConvention() 
throws Exception {
+        dispatch("GET", NAMESPACE + "/upload.action");
+
+        assertNotEquals(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 
response.getStatus());
+        assertNull(response.getHeader("Allow"));
+        assertTrue(response.getContentAsString().contains("upload rejected by 
convention template"));
+    }
+
+    @Test
+    public void actionWithDeclaredBadRequestResultAllowsListedMethod() throws 
Exception {
+        request.setMethod("PUT");
+
+        String output = executeAction(NAMESPACE + "/profile.action");
+
+        assertTrue(output.contains("profile executed"));
+    }
+
+    @Test
+    public void rejectionRendersBadRequestResultDeclaredByAnnotation() throws 
Exception {
+        dispatch("GET", NAMESPACE + "/profile.action");
+
+        assertNotEquals(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 
response.getStatus());
+        assertNull(response.getHeader("Allow"));
+        assertTrue(response.getContentAsString().contains("profile rejected by 
declared result"));
+    }
+
+    /**
+     * {@link #executeAction(String)} turns any non-200 into an exception, so 
rejected requests go
+     * straight to the dispatcher and the test reads the response itself.
+     */
+    private void dispatch(String httpMethod, String uri) throws Exception {
+        request.setMethod(httpMethod);
+        request.setRequestURI(uri);
+        ActionMapping mapping = getActionMapping(request);
+        assertNotNull(mapping);
+        Dispatcher.getInstance().serviceAction(request, response, mapping);
+    }
+
+    @Override
+    protected String getConfigPath() {
+        return "struts-plugin.xml,struts-convention-configuration.xml";
+    }
+}
diff --git 
a/plugins/convention/src/test/resources/httpmethod/create-order-success.ftl 
b/plugins/convention/src/test/resources/httpmethod/create-order-success.ftl
new file mode 100644
index 000000000..61fdf0384
--- /dev/null
+++ b/plugins/convention/src/test/resources/httpmethod/create-order-success.ftl
@@ -0,0 +1,19 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+create-order executed
diff --git 
a/plugins/convention/src/test/resources/httpmethod/post-only-success.ftl 
b/plugins/convention/src/test/resources/httpmethod/post-only-success.ftl
new file mode 100644
index 000000000..c2143c949
--- /dev/null
+++ b/plugins/convention/src/test/resources/httpmethod/post-only-success.ftl
@@ -0,0 +1,19 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+post-only executed
diff --git 
a/plugins/convention/src/test/resources/httpmethod/profile-rejected.ftl 
b/plugins/convention/src/test/resources/httpmethod/profile-rejected.ftl
new file mode 100644
index 000000000..517322882
--- /dev/null
+++ b/plugins/convention/src/test/resources/httpmethod/profile-rejected.ftl
@@ -0,0 +1,19 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+profile rejected by declared result
diff --git 
a/plugins/convention/src/test/resources/httpmethod/profile-success.ftl 
b/plugins/convention/src/test/resources/httpmethod/profile-success.ftl
new file mode 100644
index 000000000..0ed6017a4
--- /dev/null
+++ b/plugins/convention/src/test/resources/httpmethod/profile-success.ftl
@@ -0,0 +1,19 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+profile executed
diff --git 
a/plugins/convention/src/test/resources/httpmethod/upload-bad-request.ftl 
b/plugins/convention/src/test/resources/httpmethod/upload-bad-request.ftl
new file mode 100644
index 000000000..1073e06e7
--- /dev/null
+++ b/plugins/convention/src/test/resources/httpmethod/upload-bad-request.ftl
@@ -0,0 +1,19 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+upload rejected by convention template
diff --git 
a/plugins/convention/src/test/resources/httpmethod/upload-success.ftl 
b/plugins/convention/src/test/resources/httpmethod/upload-success.ftl
new file mode 100644
index 000000000..eb6bddf4d
--- /dev/null
+++ b/plugins/convention/src/test/resources/httpmethod/upload-success.ftl
@@ -0,0 +1,19 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+upload executed

Reply via email to