Skip to content

Commit fcd63b0

Browse files
committed
test(envvar): cover environment variable exposure
1 parent aac7a6e commit fcd63b0

3 files changed

Lines changed: 346 additions & 0 deletions

File tree

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package com.itasocialacademy.oitassist.envvar.controller;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.mockito.Mockito.verifyNoInteractions;
5+
import static org.mockito.Mockito.when;
6+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
7+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
8+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
9+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
10+
11+
import com.itasocialacademy.oitassist.core.web.AppExceptionHttpStatusMapper;
12+
import com.itasocialacademy.oitassist.envvar.service.interfaces.EnvVariableService;
13+
import com.itasocialacademy.oitassist.security.api.interfaces.SecurityFacade;
14+
import com.itasocialacademy.oitassist.security.jwt.JwtFilter;
15+
import io.swagger.v3.oas.annotations.Hidden;
16+
import java.util.Map;
17+
import java.util.Optional;
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.boot.test.context.TestConfiguration;
21+
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.ComponentScan;
24+
import org.springframework.context.annotation.FilterType;
25+
import org.springframework.context.annotation.Import;
26+
import org.springframework.http.MediaType;
27+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
28+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
29+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
30+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
31+
import org.springframework.security.test.context.support.WithMockUser;
32+
import org.springframework.security.web.SecurityFilterChain;
33+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
34+
import org.springframework.test.web.servlet.MockMvc;
35+
36+
@WebMvcTest(
37+
controllers = EnvVariableController.class,
38+
excludeFilters = @ComponentScan.Filter(
39+
type = FilterType.ASSIGNABLE_TYPE,
40+
classes = JwtFilter.class))
41+
@Import(EnvVariableControllerTest.SecurityTestConfiguration.class)
42+
class EnvVariableControllerTest {
43+
44+
private static final String ENDPOINT = "/api/v1/admin/environment-variables";
45+
private static final String PUBLIC_KEY = "APP_NAME";
46+
private static final String PUBLIC_VALUE = "oit-assist";
47+
private static final Long ADMIN_ID = 42L;
48+
49+
@Autowired
50+
private MockMvc mockMvc;
51+
52+
@MockitoBean
53+
private EnvVariableService envVariableService;
54+
55+
@MockitoBean
56+
private SecurityFacade securityFacade;
57+
58+
@MockitoBean
59+
private AppExceptionHttpStatusMapper appExceptionHttpStatusMapper;
60+
61+
@Test
62+
@WithMockUser(roles = "ADMIN")
63+
void getMap_ShouldReturnVariables_WhenCallerIsAdmin() throws Exception {
64+
when(securityFacade.getCurrentUserId()).thenReturn(Optional.of(ADMIN_ID));
65+
when(envVariableService.getenv()).thenReturn(Map.of(PUBLIC_KEY, PUBLIC_VALUE));
66+
67+
mockMvc.perform(get(ENDPOINT))
68+
.andExpect(status().isOk())
69+
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
70+
.andExpect(jsonPath("$." + PUBLIC_KEY).value(PUBLIC_VALUE));
71+
}
72+
73+
@Test
74+
@WithMockUser(roles = "ADMIN")
75+
void getMap_ShouldReturnEmptyObject_WhenNoVariableIsAllowed() throws Exception {
76+
when(securityFacade.getCurrentUserId()).thenReturn(Optional.of(ADMIN_ID));
77+
when(envVariableService.getenv()).thenReturn(Map.of());
78+
79+
mockMvc.perform(get(ENDPOINT))
80+
.andExpect(status().isOk())
81+
.andExpect(jsonPath("$").isEmpty());
82+
}
83+
84+
@Test
85+
@WithMockUser(roles = "USER")
86+
void getMap_ShouldReturnForbidden_WhenCallerIsNotAdmin() throws Exception {
87+
mockMvc.perform(get(ENDPOINT))
88+
.andExpect(status().isForbidden())
89+
.andExpect(jsonPath("$.code").value("ACCESS_DENIED"))
90+
.andExpect(jsonPath("$.status").value(403));
91+
92+
verifyNoInteractions(envVariableService);
93+
}
94+
95+
@Test
96+
void getMap_ShouldReturnForbidden_WhenCallerIsAnonymous() throws Exception {
97+
mockMvc.perform(get(ENDPOINT))
98+
.andExpect(status().isForbidden())
99+
.andExpect(jsonPath("$.code").value("ACCESS_DENIED"))
100+
.andExpect(jsonPath("$.status").value(403));
101+
102+
verifyNoInteractions(envVariableService);
103+
}
104+
105+
@Test
106+
void controller_ShouldBeExcludedFromApiDocumentation() {
107+
assertThat(EnvVariableController.class.getAnnotation(Hidden.class))
108+
.as("the endpoint must stay out of the generated OpenAPI document")
109+
.isNotNull();
110+
}
111+
112+
@TestConfiguration(proxyBeanMethods = false)
113+
@EnableWebSecurity
114+
@EnableMethodSecurity
115+
static class SecurityTestConfiguration {
116+
@Bean
117+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
118+
return http
119+
.csrf(AbstractHttpConfigurer::disable)
120+
.authorizeHttpRequests(authorization -> authorization
121+
.anyRequest()
122+
.permitAll())
123+
.build();
124+
}
125+
}
126+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.itasocialacademy.oitassist.envvar.properties;
2+
3+
import static com.itasocialacademy.oitassist.envvar.dao.enums.AccessMode.ALL;
4+
import static com.itasocialacademy.oitassist.envvar.dao.enums.AccessMode.BLACKLIST;
5+
import static com.itasocialacademy.oitassist.envvar.dao.enums.AccessMode.WHITELIST;
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
import static org.assertj.core.api.Assertions.assertThatCode;
8+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
9+
10+
import java.util.HashSet;
11+
import java.util.Set;
12+
import org.junit.jupiter.api.Test;
13+
14+
class EnvVariablePropertiesTest {
15+
16+
private static final String RESTRICTED_KEY = "JWT_SIGN_KEY";
17+
private static final String PUBLIC_KEY = "APP_NAME";
18+
19+
@Test
20+
void constructor_ShouldUseEmptySets_WhenListsAreNull() {
21+
EnvVariableProperties properties = new EnvVariableProperties(BLACKLIST, null, null);
22+
23+
assertThat(properties.whitelist()).isEmpty();
24+
assertThat(properties.blacklist()).isEmpty();
25+
}
26+
27+
@Test
28+
void constructor_ShouldKeepKeys_WhenListsAreConfigured() {
29+
EnvVariableProperties properties =
30+
new EnvVariableProperties(WHITELIST, Set.of(PUBLIC_KEY), Set.of(RESTRICTED_KEY));
31+
32+
assertThat(properties.accessMode()).isEqualTo(WHITELIST);
33+
assertThat(properties.whitelist()).containsExactly(PUBLIC_KEY);
34+
assertThat(properties.blacklist()).containsExactly(RESTRICTED_KEY);
35+
}
36+
37+
@Test
38+
void constructor_ShouldCopyList_WhenTheSourceIsMutatedAfterwards() {
39+
Set<String> whitelist = new HashSet<>(Set.of(PUBLIC_KEY));
40+
41+
EnvVariableProperties properties = new EnvVariableProperties(WHITELIST, whitelist, null);
42+
whitelist.add(RESTRICTED_KEY);
43+
44+
assertThat(properties.whitelist()).containsExactly(PUBLIC_KEY);
45+
}
46+
47+
@Test
48+
void whitelist_ShouldBeUnmodifiable_WhenItIsConfigured() {
49+
EnvVariableProperties properties = new EnvVariableProperties(WHITELIST, Set.of(PUBLIC_KEY), null);
50+
51+
assertThatThrownBy(() -> properties.whitelist().add(RESTRICTED_KEY))
52+
.isInstanceOf(UnsupportedOperationException.class);
53+
}
54+
55+
@Test
56+
void blacklist_ShouldBeUnmodifiable_WhenItIsConfigured() {
57+
EnvVariableProperties properties = new EnvVariableProperties(BLACKLIST, null, Set.of(RESTRICTED_KEY));
58+
59+
assertThatThrownBy(() -> properties.blacklist().add(PUBLIC_KEY))
60+
.isInstanceOf(UnsupportedOperationException.class);
61+
}
62+
63+
@Test
64+
void constructor_ShouldThrow_WhenAccessModeIsAllAndWhitelistIsConfigured() {
65+
assertThatThrownBy(() -> new EnvVariableProperties(ALL, Set.of(PUBLIC_KEY), null))
66+
.isInstanceOf(IllegalStateException.class)
67+
.hasMessageContaining("accessMode=ALL");
68+
}
69+
70+
@Test
71+
void constructor_ShouldThrow_WhenAccessModeIsAllAndBlacklistIsConfigured() {
72+
assertThatThrownBy(() -> new EnvVariableProperties(ALL, null, Set.of(RESTRICTED_KEY)))
73+
.isInstanceOf(IllegalStateException.class)
74+
.hasMessageContaining("accessMode=ALL");
75+
}
76+
77+
@Test
78+
void constructor_ShouldNotThrow_WhenAccessModeIsAllAndNoListIsConfigured() {
79+
assertThatCode(() -> new EnvVariableProperties(ALL, null, null))
80+
.doesNotThrowAnyException();
81+
}
82+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.itasocialacademy.oitassist.envvar.service;
2+
3+
import static com.itasocialacademy.oitassist.envvar.dao.enums.AccessMode.ALL;
4+
import static com.itasocialacademy.oitassist.envvar.dao.enums.AccessMode.BLACKLIST;
5+
import static com.itasocialacademy.oitassist.envvar.dao.enums.AccessMode.WHITELIST;
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
8+
import static org.mockito.Mockito.when;
9+
10+
import com.itasocialacademy.oitassist.envvar.dao.enums.AccessMode;
11+
import com.itasocialacademy.oitassist.envvar.properties.EnvVariableProperties;
12+
import com.itasocialacademy.oitassist.envvar.provider.interfaces.EnvVariableProvider;
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
import java.util.Set;
16+
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.api.extension.ExtendWith;
18+
import org.mockito.Mock;
19+
import org.mockito.junit.jupiter.MockitoExtension;
20+
21+
@ExtendWith(MockitoExtension.class)
22+
class EnvVariableServiceImplTest {
23+
24+
private static final String PUBLIC_KEY = "APP_NAME";
25+
private static final String PUBLIC_VALUE = "oit-assist";
26+
private static final String RESTRICTED_KEY = "JWT_SIGN_KEY";
27+
private static final String RESTRICTED_VALUE = "sign-key-value";
28+
private static final String ABSENT_KEY = "NOT_SET_IN_ENVIRONMENT";
29+
30+
@Mock
31+
private EnvVariableProvider envVariableProvider;
32+
33+
@Test
34+
void getenv_ShouldReturnEveryVariable_WhenAccessModeIsAll() {
35+
when(envVariableProvider.getenv()).thenReturn(environment());
36+
37+
Map<String, String> result = createService(ALL, null, null).getenv();
38+
39+
assertThat(result).containsOnlyKeys(PUBLIC_KEY, RESTRICTED_KEY);
40+
assertThat(result).containsEntry(PUBLIC_KEY, PUBLIC_VALUE);
41+
assertThat(result).containsEntry(RESTRICTED_KEY, RESTRICTED_VALUE);
42+
}
43+
44+
@Test
45+
void getenv_ShouldReturnOnlyListedKeys_WhenAccessModeIsWhitelist() {
46+
when(envVariableProvider.getenv()).thenReturn(environment());
47+
48+
Map<String, String> result = createService(WHITELIST, Set.of(PUBLIC_KEY), null).getenv();
49+
50+
assertThat(result).containsOnlyKeys(PUBLIC_KEY);
51+
assertThat(result).containsEntry(PUBLIC_KEY, PUBLIC_VALUE);
52+
}
53+
54+
@Test
55+
void getenv_ShouldIgnoreListedKey_WhenItIsAbsentFromTheEnvironment() {
56+
when(envVariableProvider.getenv()).thenReturn(environment());
57+
58+
Map<String, String> result =
59+
createService(WHITELIST, Set.of(PUBLIC_KEY, ABSENT_KEY), null).getenv();
60+
61+
assertThat(result).containsOnlyKeys(PUBLIC_KEY);
62+
assertThat(result).doesNotContainKey(ABSENT_KEY);
63+
}
64+
65+
@Test
66+
void getenv_ShouldReturnNothing_WhenAccessModeIsWhitelistAndNoKeyIsListed() {
67+
when(envVariableProvider.getenv()).thenReturn(environment());
68+
69+
Map<String, String> result = createService(WHITELIST, null, null).getenv();
70+
71+
assertThat(result).isEmpty();
72+
}
73+
74+
@Test
75+
void getenv_ShouldExcludeListedKeys_WhenAccessModeIsBlacklist() {
76+
when(envVariableProvider.getenv()).thenReturn(environment());
77+
78+
Map<String, String> result = createService(BLACKLIST, null, Set.of(RESTRICTED_KEY)).getenv();
79+
80+
assertThat(result).containsOnlyKeys(PUBLIC_KEY);
81+
assertThat(result).doesNotContainKey(RESTRICTED_KEY);
82+
}
83+
84+
@Test
85+
void getenv_ShouldReturnEveryVariable_WhenAccessModeIsBlacklistAndNoKeyIsListed() {
86+
when(envVariableProvider.getenv()).thenReturn(environment());
87+
88+
Map<String, String> result = createService(BLACKLIST, null, null).getenv();
89+
90+
assertThat(result).containsOnlyKeys(PUBLIC_KEY, RESTRICTED_KEY);
91+
}
92+
93+
@Test
94+
void getenv_ShouldKeepTheKey_WhenItsValueIsNull() {
95+
Map<String, String> environment = new HashMap<>();
96+
environment.put(PUBLIC_KEY, null);
97+
when(envVariableProvider.getenv()).thenReturn(environment);
98+
99+
Map<String, String> result = createService(BLACKLIST, null, null).getenv();
100+
101+
assertThat(result).containsKey(PUBLIC_KEY);
102+
assertThat(result.get(PUBLIC_KEY)).isNull();
103+
}
104+
105+
@Test
106+
void getenv_ShouldReturnUnmodifiableMap_WhenAccessModeIsAll() {
107+
when(envVariableProvider.getenv()).thenReturn(environment());
108+
109+
Map<String, String> result = createService(ALL, null, null).getenv();
110+
111+
assertThatThrownBy(() -> result.put(ABSENT_KEY, PUBLIC_VALUE))
112+
.isInstanceOf(UnsupportedOperationException.class);
113+
}
114+
115+
@Test
116+
void getenv_ShouldReturnUnmodifiableMap_WhenFilteringIsApplied() {
117+
when(envVariableProvider.getenv()).thenReturn(environment());
118+
119+
Map<String, String> result = createService(BLACKLIST, null, Set.of(RESTRICTED_KEY)).getenv();
120+
121+
assertThatThrownBy(() -> result.put(RESTRICTED_KEY, RESTRICTED_VALUE))
122+
.isInstanceOf(UnsupportedOperationException.class);
123+
}
124+
125+
private EnvVariableServiceImpl createService(AccessMode accessMode, Set<String> whitelist,
126+
Set<String> blacklist) {
127+
return new EnvVariableServiceImpl(
128+
envVariableProvider,
129+
new EnvVariableProperties(accessMode, whitelist, blacklist));
130+
}
131+
132+
private Map<String, String> environment() {
133+
Map<String, String> environment = new HashMap<>();
134+
environment.put(PUBLIC_KEY, PUBLIC_VALUE);
135+
environment.put(RESTRICTED_KEY, RESTRICTED_VALUE);
136+
return environment;
137+
}
138+
}

0 commit comments

Comments
 (0)