spring-projects / spring-session

Spring Session
https://spring.io/projects/spring-session
Apache License 2.0
1.86k stars 1.12k forks source link

spring-session-jdbc ignores specified MockMvc request session #2037

Open vrish88 opened 2 years ago

vrish88 commented 2 years ago

Describe the bug When using spring-session-jdbc, MockMvc's MockHttpServletRequestBuilder#session is ignored. It seems as though once SessionRepositoryFilter is added to the filter chain, it ignores any session configured by mockMvc

To Reproduce Download the github repo demoing the problem and run ./gradlew test. To fix the test, add the MapSessionRepository bean to the context.

Expected behavior From the demo repo, I would have expected the session that was provided through mockMvc would be the session that was received by the controller.

Sample Github repo and, for posterity, inlining the relevant files.

build.gradle

plugins {
    id 'org.springframework.boot' version '2.6.4'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.session:spring-session-core'
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
    implementation 'org.springframework.session:spring-session-jdbc'
    implementation "com.h2database:h2"

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

DemoApplication.java

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @RestController
    public static class MyController {
        @GetMapping("/")
        public String foo(HttpSession session) {
            return (String) session.getAttribute("foo");
        }
    }
}

DemoApplicationTests.java

@SpringBootTest
@AutoConfigureMockMvc
class DemoApplicationTests {

    @Autowired
    MockMvc mockMvc;

    @TestConfiguration
    public static class FooConfig {
//        Uncomment to make the test pass.
//        @Bean
//        public MapSessionRepository mapSessionRepository() {
//            return new MapSessionRepository(new ConcurrentHashMap<>());
//        }
    }

    @Test
    void contextLoads() throws Exception {
        MockHttpSession session = new MockHttpSession();
        session.setAttribute("foo", "bar");
        mockMvc.perform(MockMvcRequestBuilders.get("/").session(session))
            .andExpect(status().isOk())
            .andExpect(content().string("bar"));
    }
}
eleftherias commented 2 years ago

SessionRepositoryFilter doesn't integrate with MockMvc#session. The reason is because SessionRepositoryFilter uses a CookieHttpSessionIdResolver, which looks for a cookie named "SESSION" and MockMvc#session does not provide that cookie.

At minimum we can update our documentation to explain that.

In your scenario it doesn't seem necessary for the request to pass through the SessionRepositoryFilter.

As you mentioned, one way to get around that is by providing a MapSessionRepository bean. I wouldn't recommend this approach, because it's not obvious why this fixes the problem. For background, this approach works because it deactivates Spring Boot's JdbcSessionConfiguration, which is what creates the SessionRepositoryFilter. Adding the MapSessionRepository bean means the SessionRepositoryFilter won't be created and you won't run into the mapping problem between HttpSession and Session.

A better approach is to explicitly exclude the Spring Boot auto-configuration in your tests. One way to do that is by setting the spring.autoconfigure.exclude property on your test class.

@SpringBootTest(properties = "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.session.SessionAutoConfiguration")

Another option is to exclude your DataSource bean from the tests. In the given example you are using an embedded H2 database, but I presume you will switch to a different database when the code is production ready. With an H2 database Spring Boot will auto-configure the DataSource for you. However, with other types of databases you will register the DataSource bean yourself. If there is no DataSource bean present when you run your tests, then the auto-configuration will not create a SessionRepositoryFilter. You can confirm this in the provided sample by noticing that the tests pass after removing the 'org.springframework.boot:spring-boot-starter-data-jdbc' and 'com.h2database:h2' dependencies.

vrish88 commented 2 years ago

@eleftherias thanks for the response. It's good to know we diagnosed actual behavior.

Is there something that can be done in addition to updating the documentation? Working with devs new to spring, we've lost a lot of time to debugging why this doesn't work. Could there be a warning/error when MockMvc#session and SessionRepositoryFilter are used together?

kunalvarpe commented 1 year ago

I would love to help/contribute here.