0

I wrote a simple spring boot application which uses OAuth2 using Firebase.

Here's the configuration

@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity security) throws Exception {
        security
                .cors()
                .and()
                .csrf().disable()
                .authorizeHttpRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt();

        return security.build();
    }

}

I have a controller which I want to test using MockMvc

Here's the test file

@WebMvcTest(CodeController.class)
@WebAppConfiguration
@ContextConfiguration(classes = SecurityConfig.class)
public class CodeControllerTests {

    @MockBean
    private CodeExecutionService codeExecutionService;
    @MockBean
    private ProblemService problemService;

//    @MockBean
//    private ProblemRepo problemRepo;

    @MockBean
    private TestCaseValidationService validationService;
//    @MockBean
//    private ProblemRepo problemRepo;

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @BeforeEach
    public void setup() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(springSecurity())
                .build();
    }


    @Test

    void runTestCode() throws Exception {

        RunCodeDTO runCodeDTO = new RunCodeDTO("python", "something", "two-sum");
        Problem problem = ProblemUtils.getTwoSum();
        UserCode userCode = new UserCode(runCodeDTO.code(), runCodeDTO.language());
        userCode.mergeWithStub(problem.getCodeRunStub());

        List<TestResult> testResults = problem.getRunTestCases()
                .stream()
                .map(testCase -> new TestResult(testCase, Status.SUCCESS, ""))
                .toList();

        List<TestOutput> testOutputs = testResults
                .stream()
                .map(result -> new TestOutput(result.testCase(), new ValidationResult(Status.SUCCESS, "Test Case Passed")))
                .toList();


        when(problemService.getProblem(runCodeDTO.problemId())).thenReturn(Optional.of(problem));
        when(codeExecutionService.executeAllTestCases(problem.getRunTestCases(), userCode)).thenReturn(testResults);
        when(validationService.validateAllTestResults(testResults, problem.getOutputType(), problem.getValidationType())).thenReturn(testOutputs);

        mockMvc
                .perform(
                    MockMvcRequestBuilders.post("/code/test")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("")
                            .with(SecurityMockMvcRequestPostProcessors.oauth2Login())
                )
                .andExpect(status().isOk());

    }

}

I'm trying to mock authorization using the SecurityMockMvcRequestPostProcessors.oauth2Login() but I get a NoClassDefFoundError - org/springframework/security/oauth2/client/registration/ClientRegistration.

However, the actual application works without any issue. It's only the test where I get this error

Sriram R
  • 2,109
  • 3
  • 23
  • 40
  • 2
    For starters your test is weird (and I would say wrong). Remove the `@WebAppConfiguration` and `@ContextConfiguration`. Remove the `WebApplicationContext` field and ditch the `setup` method. Add `@Autowired` on your `MockMvc` and then check what happens. You are working around Spring Boot here instead of working with it. – M. Deinum Feb 22 '23 at 15:51
  • Thanks @M.Deinum . I added `@WebAppConfiguration` and `@ContextConfiguration` with setup because the documentation said so https://docs.spring.io/spring-security/reference/servlet/test/mockmvc/setup.html Weird that the documentation is wrong. – Sriram R Feb 23 '23 at 04:43
  • This page might not have been updated for a while. Have a look at `@WebMvcTest` javadoc: https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html. Here you are mixing a sample without `@WebMvcTest` with `@WebMvcTest`, so no, this is not optimal... – ch4mp Feb 23 '23 at 05:02
  • The documentation isn't wrong, but tha tis the documentation for use **without Spring Boot testing**. – M. Deinum Feb 23 '23 at 07:13

1 Answers1

-1

@m-denium is right in his comment, plus you are trying to build a client Authentication implementation (oauth2Login() instantiates an OAuth2AuthenticationToken) when testing a resource-server (with resource-server dependencies, reason for classes under org/springframework/security/oauth2/client/ not being found).

As your conf is .oauth2ResourceServer().jwt(), populate your test security context with a JwtAuthenticationToken using SecurityMockMvcRequestPostProcessors.jwt().

Your test should be no more complicated than that:

@WebMvcTest(CodeController.class)
@Import({ SecurityConfig.class })
class CodeControllerTests {

    @MockBean
    private CodeExecutionService codeExecutionService;

    @MockBean
    private ProblemService problemService;

    @MockBean
    private TestCaseValidationService validationService;

    @Autowired
    MockMvc mockMvc;

    @Test
    void runTestCode() throws Exception {
        RunCodeDTO runCodeDTO = new RunCodeDTO("python", "something", "two-sum");
        Problem problem = ProblemUtils.getTwoSum();
        UserCode userCode = new UserCode(runCodeDTO.code(), runCodeDTO.language());
        userCode.mergeWithStub(problem.getCodeRunStub());

        List<TestResult> testResults = problem.getRunTestCases()
                .stream()
                .map(testCase -> new TestResult(testCase, Status.SUCCESS, ""))
                .toList();

        List<TestOutput> testOutputs = testResults
                .stream()
                .map(result -> new TestOutput(result.testCase(), new ValidationResult(Status.SUCCESS, "Test Case Passed")))
                .toList();
        when(problemService.getProblem(runCodeDTO.problemId())).thenReturn(Optional.of(problem));
        when(codeExecutionService.executeAllTestCases(problem.getRunTestCases(), userCode)).thenReturn(testResults);
        when(validationService.validateAllTestResults(testResults, problem.getOutputType(), problem.getValidationType())).thenReturn(testOutputs);

        mockMvc
            .perform(MockMvcRequestBuilders.post("/code/test")
                .contentType(MediaType.APPLICATION_JSON)
                .content("")
                .with(SecurityMockMvcRequestPostProcessors.jwt()
                    .jwt(jwt -> jwt.claim(StandardClaimNames.SUB, "Tonton Pirate"))
                    .authorities(List.of(new SimpleGrantedAuthority("NICE"), new SimpleGrantedAuthority("AUTHOR")))))
            .andExpect(status().isOk());
    }
}

Instead of SecurityMockMvcRequestPostProcessors.jwt(), you could also use @WithMockJwtAuth available from spring-addons-oauth2-test with sources available on this repo of mine (repo which also contains plenty of resource-server samples, each with access-control unit-tests)

ch4mp
  • 6,622
  • 6
  • 29
  • 49
  • Thanks @ch4mp. I added the setup and the annotations as described by the documentation https://docs.spring.io/spring-security/reference/servlet/test/mockmvc/setup.html I guess I'll have to learn oauth2 better to understand the difference betwee OAuth2Login and Resource Server. – Sriram R Feb 23 '23 at 04:42
  • Regarding client VS resource-server, you could read this other answer I wrote: https://stackoverflow.com/a/75504568/619830. Even if the question is completely different from yours, I cover this subject. It is interesting to note it was down voted too :D (I can hardly believe those two answers are usless) – ch4mp Feb 23 '23 at 04:50