Skip to content

Commit

Permalink
Improved code coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
KDNeufeld committed Sep 20, 2023
1 parent 46eb02d commit 55c7066
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 24 deletions.
1 change: 1 addition & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ MVN_PROFILE=
SERVER_PORT=8090

# KEYCLOAK CONFIG
KEYCLOAK_REALM_SERVER_URL
KEYCLOAK_SSL_REQUIRED=none
KEYCLOAK_RESOURCE=ai-reviewer-api
KEYCLOAK_AUTH_SERVER_URL=
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/ci-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ jobs:
with:
repository: bcgov/spring-boot-starters
path: spring-boot-starters
ref: v0.1.8
ref: v1.0.0

# Setup Java Environment
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17

# TODO REMOVE AFTER SFTP IS IN MAVEN CENTRAL
- name: Build Spring SFTP Starter
run: mvn install -P all --file ./spring-boot-starters/src/pom.xml
Expand Down Expand Up @@ -73,12 +79,6 @@ jobs:
#echo "::set-env name=GIT_BRANCH::main"
if: env.GIT_BRANCH == 'refs/heads/main'

# Setup Java Environment
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8

# Run Maven Verify to generate all jacoco reports
- name: Build with Maven
run: mvn -B verify -P all --file src/backend/pom.xml
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/cucumber-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
-t ai-reviewer-api:builder
--build-arg SERVICE_NAME=ai-reviewer-api
--build-arg MVN_PROFILE=ai-reviewer
--build-arg STARTERS_V=v0.1.8
--build-arg STARTERS_V=v1.0.0
--cache-from=docker.pkg.github.com/$GITHUB_REPOSITORY/ai-reviewer-api:builder

- name: tag & push ai-reviewer-api to git container registry
Expand Down Expand Up @@ -64,7 +64,7 @@ jobs:
-t ai-reviewer-mock-api:builder
--build-arg SERVICE_NAME=ai-reviewer-mock-api
--build-arg MVN_PROFILE=ai-reviewer-mock
--build-arg STARTERS_V=v0.1.8
--build-arg STARTERS_V=v1.0.0
--cache-from=docker.pkg.github.com/$GITHUB_REPOSITORY/ai-reviewer-mock-api:builder

- name: tag & push ai-reviewer-mock-api to git container registry
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ services:
- CLAMAV_HOST=clamav
- CLAMAV_PORT=3310
- CLAMAV_TIMEOUT=50000
- KEYCLOAK_REALM_SERVER_URL=${KEYCLOAK_REALM_SERVER_URL:-http://keycloak:8080/auth/realms/ai-reviewer}
- KEYCLOAK_AUTH_SERVER_URL=${KEYCLOAK_AUTH_SERVER_URL:-http://keycloak:8080/auth}
- KEYCLOAK_REALM=${KEYCLOAK_REALM:-ai-reviewer}
- KEYCLOAK_RESOURCE=${KEYCLOAK_RESOURCE:-ai-reviewer-api}
Expand Down
1 change: 1 addition & 0 deletions src/backend/ai-reviewer-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ You should use environment variables to configure the jag ai-reviewer-api
| CSO_WEBHOOK_ENDPOINT | String | CSO extract | defaulted to mock service |
| CSO_WEBHOOK_USERNAME | String | CSO username | defaulted to mock service |
| CSO_WEBHOOK_PASSWORD | String | CSO password | defaulted to mock service |
| KEYCLOAK_REALM_SERVER_URL | String | The keycloak JWT issuer uri | not set by default |
| KEYCLOAK_AUTH_SERVER_URL | String | The keycloak auth server URL | not set by default |
| KEYCLOAK_REALM | String | The keycloak realm name | not set by default |
| KEYCLOAK_RESOURCE | String | The keycloak resource name | not set by default |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@
@Component
public class KeycloakJwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {

private static final String KEYCLOAK_PRINCIPLE_ATTRIBUTE = "preferred_username";
private static final String KEYCLOAK_RESOURCE_ATTRIBUTE = "resource_access";
private static final String KEYCLOAK_ROLE_ATTRIBUTE = "roles";
public static final String KEYCLOAK_PRINCIPLE_ATTRIBUTE = "preferred_username";
public static final String KEYCLOAK_RESOURCE_ATTRIBUTE = "resource_access";
public static final String KEYCLOAK_ROLE_ATTRIBUTE = "roles";

@Value("${jwt.auth.converter.resource-id}")
private String resourceId;

public KeycloakJwtAuthConverter(@Value("${jwt.auth.converter.resource-id}") String resourceId) {
this.resourceId = resourceId;
}

@Override
public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {
Expand Down Expand Up @@ -65,6 +68,10 @@ private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) {
}

Collection<String> resourceRoles = (Collection<String>) resource.get(KEYCLOAK_ROLE_ATTRIBUTE);
if (resourceRoles == null) {
return Set.of();
}

return resourceRoles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,18 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

http.cors(cors -> cors.configurationSource(corsConfigurationSource()));

http.csrf(httpSecurityCsrfConfigurer -> {
httpSecurityCsrfConfigurer.disable();
});
http.csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.disable());

http.authorizeHttpRequests(requests -> {
requests.anyRequest().authenticated();
});
http.authorizeHttpRequests(requests -> requests.anyRequest().authenticated());

http.oauth2ResourceServer((oauth2) -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(keycloakJwtAuthConverter)));

http.sessionManagement(session -> {
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
});
http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

return http.build();
}

private CorsConfigurationSource corsConfigurationSource() {
protected CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.applyPermitDefaultValues();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package ca.bc.gov.open.jag.aireviewerapi.config;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;

public class KeycloakJwtAuthConverterTest {

@Test
void testNull() {
KeycloakJwtAuthConverter converter = new KeycloakJwtAuthConverter(null);
assertThrows(NullPointerException.class, () -> converter.convert(null));
}

@Test
void testJwtMissingResourceAccess() throws Exception {
Jwt jwt = Mockito.mock(Jwt.class);

KeycloakJwtAuthConverter converter = new KeycloakJwtAuthConverter(null);
AbstractAuthenticationToken authenticationToken = converter.convert(jwt);
Collection<GrantedAuthority> authorities = authenticationToken.getAuthorities();
assertEquals(0, authorities.size());
}

@Test
void testJwtMissingResource() throws Exception {
Jwt jwt = Mockito.mock(Jwt.class);
Map<String, Object> resourceAccess = new HashMap<String, Object>();
when(jwt.getClaim(KeycloakJwtAuthConverter.KEYCLOAK_RESOURCE_ATTRIBUTE)).thenReturn(resourceAccess);

KeycloakJwtAuthConverter converter = new KeycloakJwtAuthConverter(null);
AbstractAuthenticationToken authenticationToken = converter.convert(jwt);
Collection<GrantedAuthority> authorities = authenticationToken.getAuthorities();
assertEquals(0, authorities.size());
}

@Test
void testJwtMissingRoles() throws Exception {
Jwt jwt = Mockito.mock(Jwt.class);

Map<String, Object> resource = new HashMap<String, Object>();

Map<String, Object> resourceAccess = new HashMap<String, Object>();
resourceAccess.put("ai-reviewer", resource);
when(jwt.getClaim(KeycloakJwtAuthConverter.KEYCLOAK_RESOURCE_ATTRIBUTE)).thenReturn(resourceAccess);

KeycloakJwtAuthConverter converter = new KeycloakJwtAuthConverter("ai-reviewer");
AbstractAuthenticationToken authenticationToken = converter.convert(jwt);
Collection<GrantedAuthority> authorities = authenticationToken.getAuthorities();
assertEquals(0, authorities.size());
}

@Test
void testJwtValid() throws Exception {
Jwt jwt = Mockito.mock(Jwt.class);

Collection<String> resourceRoles = new ArrayList<>();
resourceRoles.add("tester");

Map<String, Object> resource = new HashMap<String, Object>();
resource.put(KeycloakJwtAuthConverter.KEYCLOAK_ROLE_ATTRIBUTE, resourceRoles);

Map<String, Object> resourceAccess = new HashMap<String, Object>();
resourceAccess.put("ai-reviewer", resource);
when(jwt.getClaim(KeycloakJwtAuthConverter.KEYCLOAK_RESOURCE_ATTRIBUTE)).thenReturn(resourceAccess);

KeycloakJwtAuthConverter converter = new KeycloakJwtAuthConverter("ai-reviewer");
AbstractAuthenticationToken authenticationToken = converter.convert(jwt);
Collection<GrantedAuthority> authorities = authenticationToken.getAuthorities();
assertEquals(1, authorities.size());
assertEquals("ROLE_tester", authorities.iterator().next().getAuthority());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ca.bc.gov.open.jag.aireviewerapi.config;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;

class SecurityConfigTest {

@Mock
KeycloakJwtAuthConverter jwtAuthConverter;

@Mock
HttpSecurity http;

@Test
void testFilterChain() throws Exception {
MockitoAnnotations.openMocks(this);
when(http.build()).thenReturn(Mockito.mock(DefaultSecurityFilterChain.class));
SecurityConfig config = new SecurityConfig(jwtAuthConverter);
assertNotNull(config.filterChain(http));
assertNotNull(config.corsConfigurationSource());
}

@Test
void testSessionAuthenticationStrategy() throws Exception {
KeycloakJwtAuthConverter jwtAuthConverter = Mockito.mock(KeycloakJwtAuthConverter.class);
HttpSecurity http = Mockito.mock(HttpSecurity.class);

SecurityConfig config = new SecurityConfig(jwtAuthConverter);
assertNotNull(config.sessionAuthenticationStrategy());
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ca.bc.gov.open.jag.aireviewerapi.core;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Optional;
Expand All @@ -21,6 +22,8 @@
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;

import ca.bc.gov.open.jag.aidiligenclient.api.handler.ApiException;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DisplayName("Security Utils Test Suite")
@ExtendWith(MockitoExtension.class)
Expand All @@ -37,7 +40,6 @@ public class SecurityUtilsTest {

@Test
public void testLoggedInUsername() {

String expectedUsername = RandomStringUtils.randomAlphanumeric(10);

// arrange
Expand All @@ -53,4 +55,15 @@ public void testLoggedInUsername() {
assertTrue(actualUsername.isPresent());
assertEquals(expectedUsername, actualUsername.get());
}

@Test
public void testLoggedInUsernameNoUser() throws ApiException {
Mockito.when(securityContextMock.getAuthentication()).thenReturn(authenticationMock);
Mockito.when(authenticationMock.getPrincipal()).thenReturn(null);
SecurityContextHolder.setContext(securityContextMock);

Optional<String> actualUsername = SecurityUtils.getLoggedInUsername();

assertTrue(actualUsername.isEmpty());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ca.bc.gov.open.jag.jagmailit;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

import ca.bc.gov.open.jag.jagmailit.api.handler.ApiClient;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AutoConfigurationTest {

private ApplicationContextRunner context;

@Test
public void validConfigurationShouldProduceBeans() {

context = new ApplicationContextRunner()
.withUserConfiguration(AutoConfiguration.class)
.withPropertyValues("mailsend.baseUrl=http://test.com")
.withUserConfiguration(MailSendProperties.class);

context.run(it -> {
assertThat(it).hasSingleBean(ApiClient.class);
});

}

}

0 comments on commit 55c7066

Please sign in to comment.