Skip to content

Commit fe35662

Browse files
committed
refactor(tests): replace AntPathMatcher as path matching strategy instead of default PathPatternParser and refactor spring security from 5.x to 6.x during upgrade of spring boot 3.0.x
As of Spring Boot 2.6, the PathPatternParser is used by default. However, for pattern like /abc/**/xyz, PathPattenParser does not resolve the path. https://spring.io/blog/2022/05/24/preparing-for-spring-boot-3-0#use-spring-mvcs-pathpatternparser spring-projects/spring-framework#24952 Due to this change, encountered below errors in igor-web module: ``` BuildControllerSpec > get the status of a build FAILED java.lang.IllegalStateException: Invalid mapping on handler class [com.netflix.spinnaker.igor.build.BuildController]: public void com.netflix.spinnaker.igor.build.BuildController.update(java.lang.String,java.lang.Integer,com.netflix.spinnaker.igor.build.model.UpdatedBuild,jakarta.servlet.http.HttpServletRequest) at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lambda$detectHandlerMethods$1(AbstractHandlerMethodMapping.java:287) at org.springframework.core.MethodIntrospector.lambda$selectMethods$0(MethodIntrospector.java:74) at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:366) at org.springframework.core.MethodIntrospector.selectMethods(MethodIntrospector.java:72) at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.detectHandlerMethods(AbstractHandlerMethodMapping.java:280) at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.processCandidateBean(AbstractHandlerMethodMapping.java:265) at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.initHandlerMethods(AbstractHandlerMethodMapping.java:224) at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.afterPropertiesSet(AbstractHandlerMethodMapping.java:212) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.afterPropertiesSet(RequestMappingHandlerMapping.java:225) at org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder.registerMvcSingletons(StandaloneMockMvcBuilder.java:419) at org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder.initWebAppContext(StandaloneMockMvcBuilder.java:391) at org.springframework.test.web.servlet.setup.AbstractMockMvcBuilder.build(AbstractMockMvcBuilder.java:157) at com.netflix.spinnaker.igor.build.BuildControllerSpec.setup(BuildControllerSpec.groovy:116) Caused by: org.springframework.web.util.pattern.PatternParseException: No more pattern data allowed after {*...} or ** pattern element at app//org.springframework.web.util.pattern.InternalPathPatternParser.peekDoubleWildcard(InternalPathPatternParser.java:250) at app//org.springframework.web.util.pattern.InternalPathPatternParser.parse(InternalPathPatternParser.java:113) at app//org.springframework.web.util.pattern.PathPatternParser.parse(PathPatternParser.java:129) at app//org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition.parse(PathPatternsRequestCondition.java:84) at app//org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition.<init>(PathPatternsRequestCondition.java:74) at app//org.springframework.web.servlet.mvc.method.RequestMappingInfo$DefaultBuilder.build(RequestMappingInfo.java:714) at app//org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.createRequestMappingInfo(RequestMappingHandlerMapping.java:400) at app//org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.createRequestMappingInfo(RequestMappingHandlerMapping.java:345) at app//org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.getMappingForMethod(RequestMappingHandlerMapping.java:302) at app//org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.getMappingForMethod(RequestMappingHandlerMapping.java:76) at app//org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lambda$detectHandlerMethods$1(AbstractHandlerMethodMapping.java:283) ... 12 more ``` ``` GoogleCloudBuildTest > missingAccountTest() FAILED java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:143) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:127) at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:191) at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:130) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:241) at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138) Caused by: org.springframework.web.util.pattern.PatternParseException: No more pattern data allowed after {*...} or ** pattern element at app//org.springframework.web.util.pattern.InternalPathPatternParser.peekDoubleWildcard(InternalPathPatternParser.java:250) at app//org.springframework.web.util.pattern.InternalPathPatternParser.parse(InternalPathPatternParser.java:113) at app//org.springframework.web.util.pattern.PathPatternParser.parse(PathPatternParser.java:129) at app//org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition.parse(PathPatternsRequestCondition.java:84) at app//org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition.<init>(PathPatternsRequestCondition.java:74) at app//org.springframework.web.servlet.mvc.method.RequestMappingInfo$DefaultBuilder.build(RequestMappingInfo.java:714) at app//org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.createRequestMappingInfo(RequestMappingHandlerMapping.java:400) at app//org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.createRequestMappingInfo(RequestMappingHandlerMapping.java:345) at app//org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.getMappingForMethod(RequestMappingHandlerMapping.java:302) at app//org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.getMappingForMethod(RequestMappingHandlerMapping.java:76) at app//org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lambda$detectHandlerMethods$1(AbstractHandlerMethodMapping.java:283) ... 118 more ``` So, refactoring the tests to replace AntPathMatcher instead of PathPatternParser by adding the property `spring.mvc.pathmatch.matching-strategy = ANT_PATH_MATCHER` Ref: spinnaker#1211 ======================================================== With spring boot upgrade, spring security also upgrades from 5.x to 6.x. As per the migration [steps](https://www.baeldung.com/spring-security-migrate-5-to-6), `WebSecurityConfigurerAdapter` has been removed. So, it is not required to be extended, instead bean can be registered.
1 parent 0a0c5e8 commit fe35662

File tree

2 files changed

+40
-60
lines changed

2 files changed

+40
-60
lines changed

igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/BuildControllerSpec.groovy

Lines changed: 31 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.netflix.spinnaker.igor.build
1818

19+
import com.netflix.spinnaker.config.PluginsAutoConfiguration
1920
import com.netflix.spinnaker.igor.PendingOperationsCache
2021
import com.netflix.spinnaker.igor.build.model.GenericBuild
2122
import com.netflix.spinnaker.igor.build.model.UpdatedBuild
@@ -33,15 +34,18 @@ import com.netflix.spinnaker.igor.travis.service.TravisService
3334
import com.netflix.spinnaker.kork.web.exceptions.ExceptionMessageDecorator
3435
import com.netflix.spinnaker.kork.web.exceptions.GenericExceptionHandlers
3536
import com.netflix.spinnaker.kork.web.exceptions.NotFoundException
36-
import okhttp3.mockwebserver.MockWebServer
37+
import org.spockframework.spring.SpringBean
38+
import org.springframework.beans.factory.annotation.Autowired
39+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
40+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
3741
import org.springframework.http.HttpStatus
3842
import org.springframework.http.MediaType
3943
import org.springframework.mock.web.MockHttpServletResponse
44+
import org.springframework.test.context.ContextConfiguration
45+
import org.springframework.test.context.TestPropertySource
4046
import org.springframework.test.web.servlet.MockMvc
41-
import org.springframework.test.web.servlet.setup.MockMvcBuilders
4247
import retrofit.client.Header
4348
import retrofit.client.Response
44-
import spock.lang.Shared
4549
import spock.lang.Specification
4650

4751
import static com.netflix.spinnaker.igor.build.BuildController.InvalidJobParameterException
@@ -55,19 +59,24 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
5559
* Tests for BuildController
5660
*/
5761
@SuppressWarnings(['DuplicateNumberLiteral', 'PropertyName'])
62+
@AutoConfigureMockMvc(addFilters = false)
63+
@WebMvcTest(controllers = [BuildController])
64+
@ContextConfiguration(classes = [PluginsAutoConfiguration, BuildController, GenericExceptionHandlers])
65+
@TestPropertySource(
66+
properties = [
67+
"spring.application.name = igor",
68+
"spring.mvc.pathmatch.matching-strategy = ANT_PATH_MATCHER"
69+
])
5870
class BuildControllerSpec extends Specification {
5971

72+
@Autowired
6073
MockMvc mockMvc
61-
BuildServices buildServices
62-
BuildCache cache
63-
JenkinsService jenkinsService
64-
BuildOperations service
65-
TravisService travisService
66-
PendingOperationsCache pendingOperationService
67-
ExceptionMessageDecorator exceptionMessageDecorator
68-
69-
@Shared
70-
MockWebServer server
74+
@SpringBean BuildServices buildServices = Mock()
75+
@SpringBean JenkinsService jenkinsService = Mock()
76+
@SpringBean BuildOperations service = Mock()
77+
@SpringBean TravisService travisService = Mock()
78+
@SpringBean PendingOperationsCache pendingOperationService = Mock()
79+
@SpringBean ExceptionMessageDecorator exceptionMessageDecorator = Mock()
7180

7281
def SERVICE = 'SERVICE'
7382
def JENKINS_SERVICE = 'JENKINS_SERVICE'
@@ -83,38 +92,16 @@ class BuildControllerSpec extends Specification {
8392

8493
GenericBuild genericBuild
8594

86-
void cleanup() {
87-
server.shutdown()
88-
}
89-
9095
void setup() {
91-
exceptionMessageDecorator = Mock(ExceptionMessageDecorator)
92-
service = Mock(BuildOperations)
93-
jenkinsService = Mock(JenkinsService)
9496
jenkinsService.getBuildServiceProvider() >> BuildServiceProvider.JENKINS
95-
travisService = Mock(TravisService)
9697
travisService.getBuildServiceProvider() >> BuildServiceProvider.TRAVIS
97-
buildServices = new BuildServices()
98-
buildServices.addServices([
99-
(SERVICE) : service,
100-
(JENKINS_SERVICE): jenkinsService,
101-
(TRAVIS_SERVICE) : travisService,
102-
])
98+
buildServices.getService(SERVICE) >> service
99+
buildServices.getService(JENKINS_SERVICE) >> jenkinsService
100+
buildServices.getService(TRAVIS_SERVICE) >> travisService
101+
103102
genericBuild = new GenericBuild()
104103
genericBuild.number = BUILD_NUMBER
105104
genericBuild.id = BUILD_ID
106-
107-
cache = Mock(BuildCache)
108-
server = new MockWebServer()
109-
pendingOperationService = Mock(PendingOperationsCache)
110-
pendingOperationService.getAndSetOperationStatus(_, _, _) >> {
111-
return new PendingOperationsCache.OperationState()
112-
}
113-
114-
mockMvc = MockMvcBuilders
115-
.standaloneSetup(new BuildController(buildServices, pendingOperationService, Optional.empty(), Optional.empty(), Optional.empty()))
116-
.setControllerAdvice(new GenericExceptionHandlers(exceptionMessageDecorator))
117-
.build()
118105
}
119106

120107
void 'get the status of a build'() {
@@ -285,6 +272,7 @@ class BuildControllerSpec extends Specification {
285272

286273
void 'trigger a build with parameters to a job with parameters'() {
287274
given:
275+
pendingOperationService.getAndSetOperationStatus(_, _, _) >> new PendingOperationsCache.OperationState()
288276
1 * jenkinsService.getJobConfig(JOB_NAME) >> new JobConfig(buildable: true, parameterDefinitionList: [new ParameterDefinition(defaultParameterValue: [name: "name", value: null], description: "description")])
289277
1 * jenkinsService.buildWithParameters(JOB_NAME, [name: "myName"]) >> new Response("http://test.com", HTTP_201, "", [new Header("Location", "foo/${BUILD_NUMBER}")], null)
290278

@@ -298,6 +286,7 @@ class BuildControllerSpec extends Specification {
298286

299287
void 'trigger a build without parameters to a job with parameters with default values'() {
300288
given:
289+
pendingOperationService.getAndSetOperationStatus(_, _, _) >> new PendingOperationsCache.OperationState()
301290
1 * jenkinsService.getJobConfig(JOB_NAME) >> new JobConfig(buildable: true, parameterDefinitionList: [new ParameterDefinition(defaultParameterValue: [name: "name", value: "value"], description: "description")])
302291
1 * jenkinsService.buildWithParameters(JOB_NAME, ['startedBy': "igor"]) >> new Response("http://test.com", HTTP_201, "", [new Header("Location", "foo/${BUILD_NUMBER}")], null)
303292

@@ -311,6 +300,7 @@ class BuildControllerSpec extends Specification {
311300

312301
void 'trigger a build with parameters to a job without parameters'() {
313302
given:
303+
pendingOperationService.getAndSetOperationStatus(_, _, _) >> new PendingOperationsCache.OperationState()
314304
1 * jenkinsService.getJobConfig(JOB_NAME) >> new JobConfig(buildable: true)
315305

316306
when:
@@ -323,6 +313,7 @@ class BuildControllerSpec extends Specification {
323313

324314
void 'trigger a build with an invalid choice'() {
325315
given:
316+
pendingOperationService.getAndSetOperationStatus(_, _, _) >> new PendingOperationsCache.OperationState()
326317
JobConfig config = new JobConfig(buildable: true)
327318
config.parameterDefinitionList = [
328319
new ParameterDefinition(type: "ChoiceParameterDefinition", name: "foo", choices: ["bar", "baz"])
@@ -342,6 +333,7 @@ class BuildControllerSpec extends Specification {
342333

343334
void 'trigger a disabled build'() {
344335
given:
336+
pendingOperationService.getAndSetOperationStatus(_, _, _) >> new PendingOperationsCache.OperationState()
345337
JobConfig config = new JobConfig()
346338
1 * jenkinsService.getJobConfig(JOB_NAME) >> config
347339
1 * exceptionMessageDecorator.decorate(_, _) >> "Job '${JOB_NAME}' is not buildable. It may be disabled."
@@ -392,16 +384,10 @@ class BuildControllerSpec extends Specification {
392384

393385
void "doesn't trigger a build when previous request is still in progress"() {
394386
given:
395-
pendingOperationService = Stub(PendingOperationsCache)
396387
pendingOperationService.getAndSetOperationStatus("${JENKINS_SERVICE}:${PENDING_JOB_NAME}:NO_EXECUTION_ID:foo=bat", _, _) >> {
397388
return new PendingOperationsCache.OperationState(PendingOperationsCache.OperationStatus.PENDING)
398389
}
399390

400-
mockMvc = MockMvcBuilders
401-
.standaloneSetup(new BuildController(buildServices, pendingOperationService, Optional.empty(), Optional.empty(), Optional.empty()))
402-
.setControllerAdvice(new GenericExceptionHandlers())
403-
.build()
404-
405391
when:
406392
MockHttpServletResponse response = mockMvc.perform(put("/masters/${JENKINS_SERVICE}/jobs/${PENDING_JOB_NAME}")
407393
.contentType(MediaType.APPLICATION_JSON).param("foo", "bat")).andReturn().response
@@ -412,18 +398,12 @@ class BuildControllerSpec extends Specification {
412398

413399
void "resets the cache once the build status has been retrieved"() {
414400
given:
415-
pendingOperationService = Mock(PendingOperationsCache)
416401
pendingOperationService.getAndSetOperationStatus("${JENKINS_SERVICE}:${JOB_NAME}:NO_EXECUTION_ID:foo=bat", _, _) >> {
417402
PendingOperationsCache.OperationState state = new PendingOperationsCache.OperationState()
418403
state.load(PendingOperationsCache.OperationStatus.COMPLETED.toString() + ":" + BUILD_NUMBER)
419404
return state
420405
}
421406

422-
mockMvc = MockMvcBuilders
423-
.standaloneSetup(new BuildController(buildServices, pendingOperationService, Optional.empty(), Optional.empty(), Optional.empty()))
424-
.setControllerAdvice(new GenericExceptionHandlers())
425-
.build()
426-
427407
when:
428408
MockHttpServletResponse response = mockMvc.perform(put("/masters/${JENKINS_SERVICE}/jobs/${JOB_NAME}")
429409
.contentType(MediaType.APPLICATION_JSON).param("foo", "bat")).andReturn().response
@@ -448,7 +428,6 @@ class BuildControllerSpec extends Specification {
448428

449429
then:
450430
1 * jenkinsService.updateBuild(jobName, BUILD_NUMBER, new UpdatedBuild("this is my new description"))
451-
0 * _
452431
response.status == 200
453432

454433
where:

igor-web/src/test/groovy/com/netflix/spinnaker/igor/gcb/GoogleCloudBuildTest.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,19 @@
5353
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
5454
import org.springframework.boot.test.context.SpringBootTest;
5555
import org.springframework.boot.test.context.TestConfiguration;
56+
import org.springframework.context.annotation.Bean;
5657
import org.springframework.context.annotation.ComponentScan;
5758
import org.springframework.core.annotation.Order;
5859
import org.springframework.http.MediaType;
5960
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6061
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
61-
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
62+
import org.springframework.security.web.SecurityFilterChain;
6263
import org.springframework.test.context.TestPropertySource;
6364
import org.springframework.test.context.junit.jupiter.SpringExtension;
6465
import org.springframework.test.web.servlet.MockMvc;
65-
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
6666

6767
@ExtendWith(SpringExtension.class)
6868
@AutoConfigureMockMvc
69-
@EnableWebMvc
7069
@ComponentScan({"com.netflix.spinnaker.config", "com.netflix.spinnaker.igor"})
7170
@SpringBootTest(
7271
classes = {
@@ -79,7 +78,8 @@
7978
@TestPropertySource(
8079
properties = {
8180
"spring.config.location=classpath:gcb/gcb-test.yml",
82-
"spring.application.name = igor"
81+
"spring.application.name = igor",
82+
"spring.mvc.pathmatch.matching-strategy = ANT_PATH_MATCHER"
8383
})
8484
public class GoogleCloudBuildTest {
8585
@Autowired private MockMvc mockMvc;
@@ -93,10 +93,11 @@ public class GoogleCloudBuildTest {
9393
@TestConfiguration
9494
@EnableWebSecurity
9595
@Order(1)
96-
static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
97-
@Override
98-
protected void configure(HttpSecurity httpSecurity) throws Exception {
99-
httpSecurity.authorizeRequests().anyRequest().permitAll().and().csrf().disable();
96+
static class WebSecurityConfig {
97+
@Bean
98+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
99+
http.authorizeHttpRequests().anyRequest().permitAll().and().csrf().disable();
100+
return http.build();
100101
}
101102
}
102103

0 commit comments

Comments
 (0)