Skip to content

Commit fc9b1ae

Browse files
RWA-4723 standardize IDAM mocking in integration/replica tests via Mockito overrides and shared test configs. (#1579)
* RWA-4723 standardize IDAM mocking in integration/replica tests via Mockito overrides and shared test configs. Stub public holiday calendar HTTP calls with WireMock + local fixtures and update calendar tests/URIs. Align supporting integration configs/utilities chore: align calendar support and test config overrides * RWA-4723 standardize IDAM mocking Resolve merge conflict. --------- Co-authored-by: Martin Spasov <martin.y.spasov@gmail.com>
1 parent 613bbda commit fc9b1ae

58 files changed

Lines changed: 648 additions & 285 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/integrationTest/java/uk/gov/hmcts/reform/wataskmanagementapi/GetWelcomeTest.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@
44
import org.junit.jupiter.api.Test;
55
import org.springframework.beans.factory.annotation.Autowired;
66
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
7-
import org.springframework.boot.test.context.SpringBootTest;
8-
import org.springframework.test.context.ActiveProfiles;
97
import org.springframework.test.web.servlet.MockMvc;
108
import org.springframework.test.web.servlet.MvcResult;
9+
import uk.gov.hmcts.reform.wataskmanagementapi.config.IntegrationTest;
1110

1211
import static org.assertj.core.api.Assertions.assertThat;
1312
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
1413
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
1514

16-
@SpringBootTest
17-
@ActiveProfiles({"integration"})
15+
@IntegrationTest
1816
@AutoConfigureMockMvc(addFilters = false)
1917
class GetWelcomeTest {
2018

src/integrationTest/java/uk/gov/hmcts/reform/wataskmanagementapi/auth/IdamTokenGeneratorCacheTest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@
77
import org.junit.jupiter.api.DisplayName;
88
import org.junit.jupiter.api.Nested;
99
import org.junit.jupiter.api.Test;
10-
import org.springframework.boot.test.context.SpringBootTest;
10+
import org.springframework.beans.factory.annotation.Autowired;
1111
import org.springframework.boot.test.context.TestConfiguration;
1212
import org.springframework.context.annotation.Bean;
1313
import org.springframework.test.annotation.DirtiesContext;
14-
import org.springframework.test.context.ActiveProfiles;
1514
import org.springframework.test.context.bean.override.mockito.MockitoBean;
1615
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
1716
import org.springframework.util.LinkedMultiValueMap;
@@ -21,6 +20,7 @@
2120
import uk.gov.hmcts.reform.wataskmanagementapi.auth.idam.entities.UserIdamTokenGeneratorInfo;
2221
import uk.gov.hmcts.reform.wataskmanagementapi.auth.idam.entities.UserInfo;
2322
import uk.gov.hmcts.reform.wataskmanagementapi.clients.IdamWebApi;
23+
import uk.gov.hmcts.reform.wataskmanagementapi.config.IntegrationTest;
2424

2525
import java.util.UUID;
2626
import java.util.concurrent.TimeUnit;
@@ -31,15 +31,14 @@
3131
import static org.mockito.Mockito.verify;
3232
import static org.mockito.Mockito.when;
3333

34-
@SpringBootTest(properties = "IA_IDAM_REDIRECT_URI=http://localhost:3002/oauth2/callback")
35-
@ActiveProfiles("integration")
34+
@IntegrationTest(properties = "IA_IDAM_REDIRECT_URI=http://localhost:3002/oauth2/callback")
3635
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
3736
public class IdamTokenGeneratorCacheTest {
3837

3938
@MockitoBean(name = "systemUserIdamInfo")
4039
UserIdamTokenGeneratorInfo systemUserIdamInfo;
4140

42-
@MockitoBean
41+
@Autowired
4342
private IdamWebApi idamWebApi;
4443

4544
@MockitoSpyBean
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package uk.gov.hmcts.reform.wataskmanagementapi.config;
2+
3+
import jakarta.annotation.PostConstruct;
4+
import uk.gov.hmcts.reform.wataskmanagementapi.auth.idam.entities.Token;
5+
import uk.gov.hmcts.reform.wataskmanagementapi.auth.idam.entities.UserInfo;
6+
import uk.gov.hmcts.reform.wataskmanagementapi.clients.IdamWebApi;
7+
import uk.gov.hmcts.reform.wataskmanagementapi.services.IdamServiceApi;
8+
import uk.gov.hmcts.reform.wataskmanagementapi.utils.ServiceMocks;
9+
10+
import java.util.List;
11+
12+
import static org.mockito.ArgumentMatchers.any;
13+
import static org.mockito.ArgumentMatchers.anyString;
14+
import static org.mockito.Mockito.lenient;
15+
16+
public class IdamMockitoStubber {
17+
18+
private static final String BEARER_PREFIX = "Bearer ";
19+
20+
private final IdamWebApi idamWebApi;
21+
private final IdamServiceApi idamServiceApi;
22+
23+
public IdamMockitoStubber(IdamWebApi idamWebApi, IdamServiceApi idamServiceApi) {
24+
this.idamWebApi = idamWebApi;
25+
this.idamServiceApi = idamServiceApi;
26+
}
27+
28+
@PostConstruct
29+
void stubDefaults() {
30+
String accessToken = stripBearer(ServiceMocks.IDAM_AUTHORIZATION_TOKEN);
31+
UserInfo defaultUserInfo = UserInfo.builder()
32+
.uid(ServiceMocks.IDAM_USER_ID)
33+
.email(ServiceMocks.IDAM_USER_EMAIL)
34+
.roles(List.of())
35+
.build();
36+
37+
lenient().when(idamWebApi.userInfo(anyString())).thenReturn(defaultUserInfo);
38+
lenient().when(idamWebApi.token(any())).thenReturn(new Token(accessToken, "scope"));
39+
40+
// Default no-ops for test support endpoints
41+
lenient().doNothing().when(idamServiceApi).createTestUser(any());
42+
lenient().doNothing().when(idamServiceApi).deleteTestUser(anyString());
43+
}
44+
45+
private static String stripBearer(String token) {
46+
if (token == null) {
47+
return "stub-token";
48+
}
49+
if (token.startsWith(BEARER_PREFIX)) {
50+
return token.substring(BEARER_PREFIX.length());
51+
}
52+
return token;
53+
}
54+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package uk.gov.hmcts.reform.wataskmanagementapi.config;
2+
3+
import org.mockito.Mockito;
4+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
5+
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
6+
import org.springframework.boot.test.context.TestConfiguration;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Primary;
9+
import org.springframework.context.annotation.Profile;
10+
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
11+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
12+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
13+
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
14+
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
15+
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
16+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
17+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
18+
import uk.gov.hmcts.reform.wataskmanagementapi.clients.IdamWebApi;
19+
import uk.gov.hmcts.reform.wataskmanagementapi.services.IdamServiceApi;
20+
21+
@TestConfiguration
22+
@Profile({"integration", "replica"})
23+
public class IntegrationIdamMockitoConfig {
24+
25+
private static final String IDAM_WEB_API_BEAN =
26+
"uk.gov.hmcts.reform.wataskmanagementapi.clients.IdamWebApi";
27+
private static final String IDAM_SERVICE_API_BEAN =
28+
"uk.gov.hmcts.reform.wataskmanagementapi.services.IdamServiceApi";
29+
30+
@Bean
31+
static BeanDefinitionRegistryPostProcessor idamFeignOverridePostProcessor() {
32+
return new BeanDefinitionRegistryPostProcessor() {
33+
@Override
34+
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
35+
if (registry.containsBeanDefinition(IDAM_WEB_API_BEAN)) {
36+
registry.getBeanDefinition(IDAM_WEB_API_BEAN).setPrimary(false);
37+
}
38+
if (registry.containsBeanDefinition(IDAM_SERVICE_API_BEAN)) {
39+
registry.getBeanDefinition(IDAM_SERVICE_API_BEAN).setPrimary(false);
40+
}
41+
}
42+
43+
@Override
44+
public void postProcessBeanFactory(
45+
org.springframework.beans.factory.config.ConfigurableListableBeanFactory beanFactory
46+
) {
47+
// No-op
48+
}
49+
};
50+
}
51+
52+
@Bean
53+
@Primary
54+
public IdamWebApi idamWebApi() {
55+
return Mockito.mock(IdamWebApi.class);
56+
}
57+
58+
@Bean
59+
@Primary
60+
public IdamServiceApi idamServiceApi() {
61+
return Mockito.mock(IdamServiceApi.class);
62+
}
63+
64+
@Bean
65+
public IdamMockitoStubber idamMockitoStubber(IdamWebApi idamWebApi, IdamServiceApi idamServiceApi) {
66+
return new IdamMockitoStubber(idamWebApi, idamServiceApi);
67+
}
68+
69+
@Bean
70+
public ClientRegistrationRepository clientRegistrationRepository() {
71+
ClientRegistration registration = ClientRegistration.withRegistrationId("oidc")
72+
.clientId("integration-test")
73+
.clientSecret("integration-test")
74+
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
75+
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
76+
.scope("openid", "profile")
77+
.authorizationUri("http://localhost/oauth2/authorize")
78+
.tokenUri("http://localhost/oauth2/token")
79+
.userInfoUri("http://localhost/userinfo")
80+
.userNameAttributeName("sub")
81+
.jwkSetUri("http://localhost/jwks")
82+
.clientName("oidc")
83+
.build();
84+
return new InMemoryClientRegistrationRepository(registration);
85+
}
86+
87+
@Bean
88+
public OAuth2AuthorizedClientService oauth2AuthorizedClientService(
89+
ClientRegistrationRepository clientRegistrationRepository) {
90+
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
91+
}
92+
93+
@Bean
94+
public OAuth2AuthorizedClientRepository oauth2AuthorizedClientRepository(
95+
OAuth2AuthorizedClientService authorizedClientService) {
96+
return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
97+
}
98+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package uk.gov.hmcts.reform.wataskmanagementapi.config;
2+
3+
import com.launchdarkly.sdk.LDContext;
4+
import com.launchdarkly.sdk.LDValue;
5+
import com.launchdarkly.sdk.server.interfaces.LDClientInterface;
6+
import org.mockito.Mockito;
7+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
8+
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
9+
import org.springframework.beans.factory.support.RootBeanDefinition;
10+
import org.springframework.boot.test.context.TestConfiguration;
11+
import org.springframework.context.annotation.Bean;
12+
13+
import static org.mockito.ArgumentMatchers.any;
14+
import static org.mockito.ArgumentMatchers.anyBoolean;
15+
import static org.mockito.ArgumentMatchers.anyString;
16+
import static org.mockito.Mockito.lenient;
17+
18+
@TestConfiguration
19+
@SuppressWarnings("HideUtilityClassConstructor") // Spring needs a constructor, its not a utility class
20+
public class IntegrationLaunchDarklyTestConfig {
21+
22+
@Bean
23+
static BeanDefinitionRegistryPostProcessor launchDarklyOverridePostProcessor() {
24+
return new BeanDefinitionRegistryPostProcessor() {
25+
@Override
26+
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
27+
if (registry.containsBeanDefinition("ldClient")) {
28+
registry.removeBeanDefinition("ldClient");
29+
}
30+
RootBeanDefinition definition = new RootBeanDefinition(LDClientInterface.class);
31+
definition.setPrimary(true);
32+
definition.setInstanceSupplier(IntegrationLaunchDarklyTestConfig::createMockClient);
33+
registry.registerBeanDefinition("ldClient", definition);
34+
}
35+
36+
@Override
37+
public void postProcessBeanFactory(
38+
org.springframework.beans.factory.config.ConfigurableListableBeanFactory beanFactory
39+
) {
40+
// No-op
41+
}
42+
43+
};
44+
}
45+
46+
private static LDClientInterface createMockClient() {
47+
LDClientInterface ldClient = Mockito.mock(LDClientInterface.class);
48+
lenient().when(ldClient.boolVariation(anyString(), any(LDContext.class), anyBoolean()))
49+
.thenAnswer(invocation -> invocation.getArgument(2));
50+
lenient().when(ldClient.jsonValueVariation(anyString(), any(LDContext.class), any(LDValue.class)))
51+
.thenAnswer(invocation -> invocation.getArgument(2));
52+
return ldClient;
53+
}
54+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package uk.gov.hmcts.reform.wataskmanagementapi.config;
2+
3+
import org.springframework.beans.factory.config.BeanDefinition;
4+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
5+
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
6+
import org.springframework.boot.test.context.TestConfiguration;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Primary;
9+
import org.springframework.context.annotation.Profile;
10+
import org.springframework.core.Ordered;
11+
import org.springframework.core.annotation.Order;
12+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
13+
import org.springframework.security.oauth2.jwt.Jwt;
14+
import org.springframework.security.oauth2.jwt.JwtDecoder;
15+
import org.springframework.security.web.SecurityFilterChain;
16+
17+
import java.time.Instant;
18+
import java.util.HashMap;
19+
20+
@TestConfiguration
21+
@Order(Ordered.LOWEST_PRECEDENCE)
22+
@Profile({"integration", "replica"})
23+
public class IntegrationSecurityTestConfig {
24+
25+
@Bean
26+
static BeanDefinitionRegistryPostProcessor securityOverridePostProcessor() {
27+
return new BeanDefinitionRegistryPostProcessor() {
28+
@Override
29+
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
30+
removeIfFromSecurityConfiguration(registry, "securityFilterChain");
31+
removeIfFromSecurityConfiguration(registry, "jwtDecoder");
32+
}
33+
34+
@Override
35+
public void postProcessBeanFactory(
36+
org.springframework.beans.factory.config.ConfigurableListableBeanFactory beanFactory
37+
) {
38+
// No-op
39+
}
40+
41+
private void removeIfFromSecurityConfiguration(BeanDefinitionRegistry registry, String beanName) {
42+
if (!registry.containsBeanDefinition(beanName)) {
43+
return;
44+
}
45+
BeanDefinition definition = registry.getBeanDefinition(beanName);
46+
if ("securityConfiguration".equals(definition.getFactoryBeanName())) {
47+
registry.removeBeanDefinition(beanName);
48+
}
49+
}
50+
};
51+
}
52+
53+
@Bean
54+
public JwtDecoder jwtDecoder() {
55+
return token -> Jwt.withTokenValue(token)
56+
.header("alg", "none")
57+
.claim("sub", "integration-test")
58+
.issuedAt(Instant.now())
59+
.expiresAt(Instant.now().plusSeconds(3600))
60+
.claims(claims -> claims.putAll(new HashMap<>()))
61+
.build();
62+
}
63+
64+
@Bean
65+
@Primary
66+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
67+
http
68+
.csrf(csrf -> csrf.disable())
69+
.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
70+
return http.build();
71+
}
72+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package uk.gov.hmcts.reform.wataskmanagementapi.config;
2+
3+
import org.springframework.boot.test.context.SpringBootTest;
4+
import org.springframework.context.annotation.Import;
5+
import org.springframework.core.annotation.AliasFor;
6+
import org.springframework.test.context.ActiveProfiles;
7+
8+
import java.lang.annotation.ElementType;
9+
import java.lang.annotation.Inherited;
10+
import java.lang.annotation.Retention;
11+
import java.lang.annotation.RetentionPolicy;
12+
import java.lang.annotation.Target;
13+
14+
@Target(ElementType.TYPE)
15+
@Retention(RetentionPolicy.RUNTIME)
16+
@Inherited
17+
@SpringBootTest
18+
@ActiveProfiles("integration")
19+
@Import({
20+
IntegrationIdamMockitoConfig.class,
21+
IntegrationSecurityTestConfig.class,
22+
IntegrationLaunchDarklyTestConfig.class
23+
})
24+
public @interface IntegrationTest {
25+
26+
@AliasFor(annotation = SpringBootTest.class, attribute = "classes")
27+
Class<?>[] classes() default {};
28+
29+
@AliasFor(annotation = SpringBootTest.class, attribute = "properties")
30+
String[] properties() default {};
31+
32+
@AliasFor(annotation = SpringBootTest.class, attribute = "webEnvironment")
33+
SpringBootTest.WebEnvironment webEnvironment() default SpringBootTest.WebEnvironment.MOCK;
34+
}

src/integrationTest/java/uk/gov/hmcts/reform/wataskmanagementapi/config/OpenAPIPublisherTest.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
import org.junit.jupiter.api.TestInstance;
66
import org.springframework.beans.factory.annotation.Autowired;
77
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
8-
import org.springframework.boot.test.context.SpringBootTest;
98
import org.springframework.test.annotation.DirtiesContext;
10-
import org.springframework.test.context.ActiveProfiles;
119
import org.springframework.test.web.servlet.MockMvc;
1210

1311
import java.io.OutputStream;
@@ -18,8 +16,7 @@
1816
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
1917
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
2018

21-
@SpringBootTest
22-
@ActiveProfiles({"integration"})
19+
@IntegrationTest
2320
@AutoConfigureMockMvc(addFilters = false)
2421
@TestInstance(PER_CLASS)
2522
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)

0 commit comments

Comments
 (0)