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
commit e824a9c176cf6e81d98203d5a1e25ac289ea1247 Author: Lukasz Lenart <[email protected]> AuthorDate: Mon Nov 2 08:19:16 2026 +0100 WW-4164 test(core): pin extension behaviour with one declaring package Co-Authored-By: Claude Opus 5.5 <[email protected]> --- .../dispatcher/ActionExtensionEndToEndTest.java | 159 +++++++++++++++++++++ .../dispatcher/struts-extension-one-package.xml | 33 +++++ 2 files changed, 192 insertions(+) diff --git a/core/src/test/java/org/apache/struts2/dispatcher/ActionExtensionEndToEndTest.java b/core/src/test/java/org/apache/struts2/dispatcher/ActionExtensionEndToEndTest.java new file mode 100644 index 000000000..6039d2222 --- /dev/null +++ b/core/src/test/java/org/apache/struts2/dispatcher/ActionExtensionEndToEndTest.java @@ -0,0 +1,159 @@ +/* + * 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.dispatcher; + +import jakarta.servlet.ServletException; +import org.apache.struts2.ActionContext; +import org.apache.struts2.ServletActionContext; +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.StrutsInternalTestCase; +import org.apache.struts2.action.Action; +import org.apache.struts2.components.URL; +import org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter; +import org.apache.struts2.dispatcher.mapper.ActionMapping; +import org.apache.struts2.util.ValueStack; +import org.apache.struts2.util.ValueStackFactory; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ActionExtensionEndToEndTest extends StrutsInternalTestCase { + + private static final String EXECUTED = "actionExtensionEndToEndTest.executed"; + + private StrutsPrepareAndExecuteFilter filter; + private MockHttpServletRequest request; + private MockHttpServletResponse response; + private MockFilterChain filterChain; + + public static class RecordingAction implements Action { + @Override + public String execute() { + ServletActionContext.getRequest().setAttribute(EXECUTED, Boolean.TRUE); + return NONE; + } + } + + @Override + protected void setUp() throws Exception { + PrepareOperations.clearDevModeOverride(); + Map<String, String> params = new HashMap<>(); + params.put("config", "struts-default.xml,org/apache/struts2/dispatcher/struts-extension-one-package.xml"); + params.put(StrutsConstants.STRUTS_ACTION_EXTENSION, "action,,"); + initDispatcher(params); + + filter = new StrutsPrepareAndExecuteFilter() { + @Override + protected InitOperations createInitOperations() { + return new InitOperations() { + @Override + public Dispatcher initDispatcher(HostConfig filterConfig) { + return dispatcher; + } + }; + } + }; + filter.init(new MockFilterConfig(servletContext)); + } + + public void testStaticHtmlFallsThrough() throws Exception { + doFilter("/index.html"); + + assertThat(filterChain.getRequest()).isNotNull(); + assertThat(request.getAttribute(EXECUTED)).isNull(); + } + + public void testDeclaredActionServedOnItsExtension() throws Exception { + doFilter("/shop/cart.html"); + + assertThat(filterChain.getRequest()).isNull(); + assertThat(request.getAttribute(EXECUTED)).isEqualTo(Boolean.TRUE); + assertThat(response.getStatus()).isEqualTo(200); + } + + public void testDeclaredActionRejectedOnGlobalExtension() throws Exception { + doFilter("/shop/cart.action"); + + assertThat(response.getStatus()).isEqualTo(404); + assertThat(request.getAttribute(EXECUTED)).isNull(); + assertThat(filterChain.getRequest()).isNull(); + } + + public void testUndeclaredActionUnchanged() throws Exception { + doFilter("/home.action"); + + assertThat(filterChain.getRequest()).isNull(); + assertThat(request.getAttribute(EXECUTED)).isEqualTo(Boolean.TRUE); + assertThat(response.getStatus()).isEqualTo(200); + + doFilter("/home"); + + assertThat(filterChain.getRequest()).isNull(); + assertThat(request.getAttribute(EXECUTED)).isEqualTo(Boolean.TRUE); + assertThat(response.getStatus()).isEqualTo(200); + } + + public void testUndeclaredActionNotServedOnDeclaredOnlyExtension() throws Exception { + doFilter("/home.html"); + + assertThat(filterChain.getRequest()).isNotNull(); + assertThat(request.getAttribute(EXECUTED)).isNull(); + } + + public void testLinkFromHtmlPageToUndeclaredAction() { + MockHttpServletRequest pageRequest = new MockHttpServletRequest(); + MockHttpServletResponse pageResponse = new MockHttpServletResponse(); + ActionMapping current = new ActionMapping("cart", "/shop", null, null); + current.setExtension("html"); + ValueStack stack = container.getInstance(ValueStackFactory.class).createValueStack(); + ActionContext context = stack.getActionContext() + .withContainer(container) + .withValueStack(stack) + .withActionMapping(current) + .withServletRequest(pageRequest) + .withServletResponse(pageResponse) + .bind(); + + URL url = new URL(context.getValueStack(), pageRequest, pageResponse); + container.inject(url); + url.setAction("home"); + url.setNamespace("/"); + StringWriter writer = new StringWriter(); + url.start(writer); + url.end(writer, ""); + + assertThat(writer.toString()).endsWith("/home.action"); + } + + private void doFilter(String uri) throws ServletException, IOException { + request = new MockHttpServletRequest(servletContext); + request.setRequestURI(uri); + response = new MockHttpServletResponse(); + filterChain = new MockFilterChain(); + filter.doFilter(request, response, filterChain); + } +} diff --git a/core/src/test/resources/org/apache/struts2/dispatcher/struts-extension-one-package.xml b/core/src/test/resources/org/apache/struts2/dispatcher/struts-extension-one-package.xml new file mode 100644 index 000000000..54346be82 --- /dev/null +++ b/core/src/test/resources/org/apache/struts2/dispatcher/struts-extension-one-package.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/* + * 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. + */ +--> +<!DOCTYPE struts PUBLIC + "-//Apache Software Foundation//DTD Struts Configuration 7.5//EN" + "https://struts.apache.org/dtds/struts-7.5.dtd"> +<struts> + <package name="shop" extends="struts-default" namespace="/shop" extension="html"> + <action name="cart" class="org.apache.struts2.dispatcher.ActionExtensionEndToEndTest$RecordingAction"/> + </package> + + <package name="root" extends="struts-default" namespace="/"> + <action name="home" class="org.apache.struts2.dispatcher.ActionExtensionEndToEndTest$RecordingAction"/> + </package> +</struts>
