Skip to content

Commit

Permalink
GRAD2-3032 - Adds new V2 endpoint for getting school by schoolId (#369)
Browse files Browse the repository at this point in the history
* GRAD2-3032 - Adds new V2 endpoint for getting school by schoolId

* GRAD2-3032 - Fixes UT
  • Loading branch information
mightycox authored Nov 12, 2024
1 parent 9329c19 commit 494a072
Show file tree
Hide file tree
Showing 14 changed files with 453 additions and 90 deletions.
6 changes: 6 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.jdbc.core.JdbcTemplate;
Expand All @@ -26,6 +27,7 @@
import reactor.netty.http.client.HttpClient;

@Configuration
@Profile("!test")
public class GradTraxConfig {

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
Expand All @@ -18,6 +19,7 @@

@Configuration
@EnableRedisRepositories("ca.bc.gov.educ.api.trax.repository.redis")
@Profile("!redisTest")
public class RedisConfig {
@Autowired
private EducGradTraxApiConstants constants;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.HashMap;
Expand Down Expand Up @@ -59,6 +60,17 @@ protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotV
return buildResponseEntity(apiError);
}

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<Object> handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException ex) {
String requiredType = (ex.getRequiredType() != null) ? ex.getRequiredType().getSimpleName() : "unknown";
String message = String.format("Parameter '%s' with value '%s' could not be converted to type '%s'.",
ex.getName(), ex.getValue(), requiredType);
log.error("Method argument type mismatch: {}", message, ex);
ApiError apiError = new ApiError(BAD_REQUEST);
apiError.setMessage(message);
return buildResponseEntity(apiError);
}

@ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class })
protected ResponseEntity<Object> handleConflict(RuntimeException ex) {
log.error("Illegal argument ERROR IS: {}", ex.getClass().getName(), ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

@CrossOrigin
@RestController("schoolControllerV2")
Expand Down Expand Up @@ -48,16 +50,16 @@ public List<School> getAllSchools() {
return schoolService.getSchoolsFromRedisCache();
}

@GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_URL_MAPPING_V2 + EducGradTraxApiConstants.GET_SCHOOL_BY_CODE_MAPPING)
@GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_URL_MAPPING_V2 + EducGradTraxApiConstants.GET_SCHOOL_BY_SCHOOL_ID)
@PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA)
@Operation(summary = "Find a School by Mincode from cache", description = "Get a School by Mincode from cache", tags = { "School" })
@Operation(summary = "Find a School by schoolId from cache", description = "Get a School by schoolId from cache", tags = { "School" })
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "204", description = "NO CONTENT")})
public ResponseEntity<School> getSchoolByMincode(@PathVariable String minCode) {
log.debug("getSchoolByMincode V2 : ");
School schoolResponse = schoolService.getSchoolByMinCodeFromRedisCache(minCode);
if(schoolResponse != null) {
return response.GET(schoolResponse);
public ResponseEntity<School> getSchoolBySchoolId(@PathVariable UUID schoolId) {
log.debug("getSchoolBySchoolId V2 : ");
Optional<School> schoolResponse = schoolService.getSchoolBySchoolId(schoolId);
if(schoolResponse.isPresent()) {
return response.GET(schoolResponse.get());
}else {
return response.NOT_FOUND();
}
Expand Down Expand Up @@ -104,6 +106,15 @@ public ResponseEntity<SchoolDetail> getSchoolDetailsByMincode(@PathVariable Stri
return response.NOT_FOUND();
}
}

@GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_URL_MAPPING_V2 + EducGradTraxApiConstants.GET_SCHOOL_SEARCH_MAPPING)
@PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA)
@Operation(summary = "Search for a school", description = "Search for a School", tags = { "School" })
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "400", description = "BAD REQUEST")})
public ResponseEntity<List<School>> getSchoolsByParams(
@RequestParam(value = "districtId", required = false) UUID districtId,
@RequestParam(value = "mincode", required = false) String mincode) {
return response.GET(schoolService.getSchoolsByParams(districtId, mincode));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface SchoolRedisRepository extends CrudRepository<SchoolEntity, String> {
String HASH_KEY = "School";

SchoolEntity findByMincode(String mincode);
List<SchoolEntity> findAllByDistrictIdAndMincode(String districtId, String mincode);
List<SchoolEntity> findAllByDistrictId(String districtId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@
import ca.bc.gov.educ.api.trax.repository.redis.SchoolRedisRepository;
import ca.bc.gov.educ.api.trax.service.RESTService;
import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;

import java.util.ArrayList;
import java.util.List;
import java.util.*;

@Slf4j
@RequiredArgsConstructor
@Service("InstituteSchoolService")
public class SchoolService {

Expand Down Expand Up @@ -171,4 +172,21 @@ public void updateSchoolCache(List<String> schoolIds) throws ServiceException {
updateSchoolCache(schoolId);
}
}

public Optional<School> getSchoolBySchoolId(UUID schoolId) {
return schoolRedisRepository.findById(String.valueOf(schoolId)).map(schoolTransformer::transformToDTO);
}

public List<School> getSchoolsByParams(UUID districtId, String mincode) {
if(districtId == null && mincode == null) {
return schoolTransformer.transformToDTO(schoolRedisRepository.findAll());
} else if (mincode == null) {
return schoolTransformer.transformToDTO(schoolRedisRepository.findAllByDistrictId(String.valueOf(districtId)));
} else if(districtId == null) {
SchoolEntity schoolEntity = schoolRedisRepository.findByMincode(mincode);
return schoolEntity != null ? List.of(schoolTransformer.transformToDTO(schoolEntity)) : Collections.emptyList();
} else {
return schoolTransformer.transformToDTO(schoolRedisRepository.findAllByDistrictIdAndMincode(String.valueOf(districtId), mincode));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public class EducGradTraxApiConstants {

public static final String CHECK_SCHOOL_BY_CODE_MAPPING = "/check/{minCode}";
public static final String GET_SCHOOL_BY_CODE_MAPPING = "/{minCode}";
public static final String GET_SCHOOL_BY_SCHOOL_ID = "/{schoolId}";

public static final String GET_COMMON_SCHOOL_BY_CODE_MAPPING = GET_COMMON_SCHOOLS + GET_SCHOOL_BY_CODE_MAPPING;
public static final String GET_SCHOOL_SEARCH_MAPPING = "/search";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,33 +181,6 @@ public void whenGetAllSchools_ReturnsListOfSchools() {
Mockito.verify(schoolServiceV2).getSchoolsFromRedisCache();
}

@Test
public void whenGetSchoolByMincode_ReturnsSchool() {
String mincode = "12345678";
ca.bc.gov.educ.api.trax.model.dto.institute.School school = new ca.bc.gov.educ.api.trax.model.dto.institute.School();
school.setSchoolId("1234567");
school.setDistrictId("9876543");
school.setMincode(mincode);

Mockito.when(schoolServiceV2.getSchoolByMinCodeFromRedisCache(mincode)).thenReturn(school);
schoolControllerV2.getSchoolByMincode(mincode);
Mockito.verify(schoolServiceV2).getSchoolByMinCodeFromRedisCache(mincode);
}

@Test
public void whenGetSchoolByMincode_Return_NOT_FOUND() {
String mincode = "12345678";
ca.bc.gov.educ.api.trax.model.dto.institute.School school = new ca.bc.gov.educ.api.trax.model.dto.institute.School();
school.setSchoolId("1234567");
school.setDistrictId("9876543");
school.setMincode(mincode);

Mockito.when(schoolServiceV2.getSchoolByMinCodeFromRedisCache(mincode)).thenReturn(null);
schoolControllerV2.getSchoolByMincode(mincode);
Mockito.verify(schoolServiceV2).getSchoolByMinCodeFromRedisCache(mincode);
assertEquals(responseHelper.NOT_FOUND(), schoolControllerV2.getSchoolByMincode(mincode));
}

@Test
public void whenGetAllSchoolDetails_ReturnsListOfSchoolDetails() {
final List<SchoolDetail> schoolDetails = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package ca.bc.gov.educ.api.trax.controller.v2;

import ca.bc.gov.educ.api.trax.EducGradTraxApiApplication;
import ca.bc.gov.educ.api.trax.model.dto.institute.School;
import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail;
import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolTransformer;
import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolDetailTransformer;
import ca.bc.gov.educ.api.trax.repository.redis.SchoolRedisRepository;
import ca.bc.gov.educ.api.trax.repository.redis.SchoolDetailRedisRepository;
import ca.bc.gov.educ.api.trax.support.MockConfiguration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import ca.bc.gov.educ.api.trax.support.TestRedisConfiguration;

import java.util.UUID;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {EducGradTraxApiApplication.class})
@AutoConfigureMockMvc
@ActiveProfiles({"test", "redisTest"})
@ContextConfiguration(classes = {TestRedisConfiguration.class, MockConfiguration.class})
class SchoolControllerIntegrationTest {

@Autowired
private MockMvc mockMvc;

@Autowired
private SchoolRedisRepository schoolRedisRepository;

@Autowired
private SchoolDetailRedisRepository schoolDetailRedisRepository;

@Autowired
private SchoolTransformer schoolTransformer;

@Autowired
private SchoolDetailTransformer schoolDetailTransformer;

private final String schoolId = UUID.randomUUID().toString();
private final String districtId = UUID.randomUUID().toString();

@BeforeEach
void setup() {
schoolRedisRepository.deleteAll();
schoolDetailRedisRepository.deleteAll();

School school = new School();
school.setSchoolId(schoolId);
school.setMincode("1234567");
school.setDistrictId(districtId);
schoolRedisRepository.save(schoolTransformer.transformToEntity(school));

SchoolDetail schoolDetail = new SchoolDetail();
schoolDetail.setSchoolId(schoolId);
schoolDetail.setDistrictId(districtId);
schoolDetailRedisRepository.save(schoolDetailTransformer.transformToEntity(schoolDetail));
}

@Test
void testGetSchoolDetails_givenValidPayload_shouldReturnOk() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/v2/trax/school/{schoolId}", schoolId)
.with(jwt().jwt(jwt -> jwt.claim("scope", "READ_GRAD_SCHOOL_DATA")))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.schoolId").value(schoolId))
.andExpect(jsonPath("$.mincode").value("1234567"));
}

@Test
void testGetSchoolDetails_givenInvalidSchoolId_shouldReturnNotFound() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/v2/trax/school/{schoolId}", "invalid_id")
.with(jwt().jwt(jwt -> jwt.claim("scope", "READ_GRAD_SCHOOL_DATA")))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.message").value("Parameter 'schoolId' with value 'invalid_id' could not be converted to type 'UUID'."));
}

@Test
void testGetSchoolDetails_givenInvalidScope_shouldReturnUnauthorized() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/v2/trax/school/{schoolId}", schoolId)
.with(jwt().jwt(jwt -> jwt.claim("scope", "BAD_SCOPE")))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());
}

@Test
void testGetSchoolsByParams_givenValidPayload_shouldReturnOk() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/v2/trax/school/search")
.param("districtId", districtId)
.with(jwt().jwt(jwt -> jwt.claim("scope", "READ_GRAD_SCHOOL_DATA")))
.param("mincode", "1234567")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().json("[{\"schoolId\":\""+schoolId+"\",\"districtId\":\""+districtId+"\"}]"));
}

@Test
void testGetSchoolsByParams_givenNoResult_shouldReturnOk() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/v2/trax/school/search")
.param("districtId", UUID.randomUUID().toString())
.with(jwt().jwt(jwt -> jwt.claim("scope", "READ_GRAD_SCHOOL_DATA")))
.param("mincode", "1234567")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().json("[]"));
}

@Test
void testGetSchoolsByParams_givenBadDistrictId_shouldReturnBadRequest() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/v2/trax/school/search")
.param("districtId", "BAD_ID123")
.with(jwt().jwt(jwt -> jwt.claim("scope", "READ_GRAD_SCHOOL_DATA")))
.param("mincode", "1234567")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.message").value("Parameter 'districtId' with value 'BAD_ID123' could not be converted to type 'UUID'."));
}

@Test
void testGetSchoolsByParams_givenScope_shouldReturnForbidden() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/v2/trax/school/search")
.param("districtId", districtId)
.with(jwt().jwt(jwt -> jwt.claim("scope", "BAD_SCOPE")))
.param("mincode", "1234567")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());
}
}
Loading

0 comments on commit 494a072

Please sign in to comment.