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