Skip to content

Commit

Permalink
MAT-7060 API for delete library with all its versions
Browse files Browse the repository at this point in the history
  • Loading branch information
adongare committed Aug 6, 2024
1 parent 4b899e2 commit 172c123
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ public class EnvironmentConfig {

@Value("${madie.cql-elm.service.elm-json-uri}")
private String cqlElmServiceElmJsonUri;

@Value("${madie.measure-service.base-url}")
private String measureServiceBaseUrl;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import gov.cms.madie.cqllibraryservice.dto.LibraryUsage;
import gov.cms.madie.cqllibraryservice.exceptions.InvalidIdException;
import gov.cms.madie.cqllibraryservice.exceptions.InvalidResourceStateException;
import gov.cms.madie.cqllibraryservice.exceptions.UnauthorizedException;
import gov.cms.madie.cqllibraryservice.repositories.LibrarySetRepository;
import gov.cms.madie.cqllibraryservice.services.ActionLogService;
import gov.cms.madie.cqllibraryservice.services.LibrarySetService;
Expand Down Expand Up @@ -181,8 +182,7 @@ public ResponseEntity<CqlLibrary> createDraft(
@GetMapping(
value = "/usage",
produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<List<LibraryUsage>> getLibraryUsage(
@RequestParam("libraryName") String libraryName) {
public ResponseEntity<List<LibraryUsage>> getLibraryUsage(@RequestParam String libraryName) {
return ResponseEntity.ok().body(cqlLibraryService.findLibraryUsage(libraryName));
}

Expand Down Expand Up @@ -236,4 +236,18 @@ public ResponseEntity<CqlLibrary> hardDeleteLibrary(
@PathVariable("id") String id, Principal principal) {
return ResponseEntity.ok(cqlLibraryService.deleteDraftLibrary(id, principal.getName()));
}

@DeleteMapping("/{libraryName}/delete-all-versions")
public ResponseEntity<String> deleteLibraryAlongWithVersions(
HttpServletRequest request,
@PathVariable String libraryName,
@RequestHeader("Authorization") String accessToken,
@Value("${admin-api-key}") String apiKey) {
if (StringUtils.equals(request.getHeader("api-key"), apiKey)) {
cqlLibraryService.deleteLibraryAlongWithVersions(libraryName, accessToken, apiKey);
return ResponseEntity.ok()
.body("The library and all its associated versions have been removed successfully.");
}
throw new UnauthorizedException("Unauthorized");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ Map<String, Object> onResourceCannotBeVersionedException(WebRequest request) {
InternalServerErrorException.class,
PersistHapiFhirCqlLibraryException.class,
CqlElmTranslationErrorException.class,
CqlElmTranslationServiceException.class
CqlElmTranslationServiceException.class,
MeasureServiceException.class
})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gov.cms.madie.cqllibraryservice.exceptions;

public class MeasureServiceException extends RuntimeException {
public MeasureServiceException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public interface CqlLibraryRepository

boolean existsByLibrarySetIdAndDraft(String librarySetId, boolean draft);

List<CqlLibrary> findAllByCqlLibraryName(String libraryName);

List<CqlLibrary> findAllByCqlLibraryNameAndDraftAndVersion(
String cqlLibraryName, boolean draft, Version version);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;

@Slf4j
@Service
Expand All @@ -26,6 +26,7 @@ public class CqlLibraryService {
private final ElmTranslatorClient elmTranslatorClient;
private CqlLibraryRepository cqlLibraryRepository;
private LibrarySetService librarySetService;
private MeasureServiceClient measureServiceClient;

public void checkDuplicateCqlLibraryName(String cqlLibraryName) {
if (StringUtils.isNotEmpty(cqlLibraryName)
Expand Down Expand Up @@ -133,4 +134,47 @@ public List<LibraryUsage> findLibraryUsage(String libraryName) {
}
return cqlLibraryRepository.findLibraryUsageByLibraryName(libraryName);
}

/**
* Library is being used if any of its version is either included in other library or measure
*
* @param name - library name
* @param accessToken
* @param apiKey
* @return true/false
*/
public boolean isLibraryBeinUsed(String name, String accessToken, String apiKey) {
// check usage in libraries
List<LibraryUsage> usageInLibraries = findLibraryUsage(name);
if (CollectionUtils.isNotEmpty(usageInLibraries)) {
return true;
}
// check usage in measures
List<LibraryUsage> usageInMeasures =
measureServiceClient.getLibraryUsageInMeasures(name, accessToken, apiKey);
if (CollectionUtils.isNotEmpty(usageInMeasures)) {
return true;
}
return false;
}

/**
* This method deletes cql library and its versions permanently, if none of the versions is being
* used either in measure or another library
*
* @param name
* @param apiKey
*/
public void deleteLibraryAlongWithVersions(String name, String accessToken, String apiKey) {
// check if library exists before finding usage and delete
if (!cqlLibraryRepository.existsByCqlLibraryName(name)) {
throw new ResourceNotFoundException("Library", "name", name);
}
if (isLibraryBeinUsed(name, accessToken, apiKey)) {
throw new GeneralConflictException(
"Library is being used actively, hence can not be deleted.");
}
List<CqlLibrary> libraries = cqlLibraryRepository.findAllByCqlLibraryName(name);
cqlLibraryRepository.deleteAll(libraries);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package gov.cms.madie.cqllibraryservice.services;

import gov.cms.madie.cqllibraryservice.config.EnvironmentConfig;
import gov.cms.madie.cqllibraryservice.dto.LibraryUsage;
import gov.cms.madie.cqllibraryservice.exceptions.MeasureServiceException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.List;

@Slf4j
@Service
@AllArgsConstructor
public class MeasureServiceClient {
private EnvironmentConfig environmentConfig;
private RestTemplate restTemplate;

public List<LibraryUsage> getLibraryUsageInMeasures(
String libraryName, String accessToken, String apiKey) {
try {
URI uri =
URI.create(
environmentConfig.getMeasureServiceBaseUrl()
+ "/measures/library/usage?libraryName="
+ libraryName);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
headers.set(HttpHeaders.AUTHORIZATION, accessToken);
headers.set("api-key", apiKey);
ResponseEntity<List<LibraryUsage>> responseEntity =
restTemplate.exchange(
new RequestEntity<>(headers, HttpMethod.GET, uri),
new ParameterizedTypeReference<>() {});
return responseEntity.getBody();
} catch (Exception ex) {
String message = "An error occurred while fetching library usage in measures";
log.error(message, ex);
throw new MeasureServiceException(message);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletRequest;

@ExtendWith(MockitoExtension.class)
class CqlLibraryControllerTest {
Expand Down Expand Up @@ -513,4 +514,19 @@ void testGetLibraryUsage() {
assertThat(usage.get(0).getName(), is(equalTo(libraryName)));
assertThat(usage.get(0).getOwner(), is(equalTo(owner)));
}

@Test
void testDeleteLibraryAlongWithVersions() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("api-key", "key");
String libraryName = "Helper";
doNothing()
.when(cqlLibraryService)
.deleteLibraryAlongWithVersions(anyString(), anyString(), anyString());
ResponseEntity<String> response =
cqlLibraryController.deleteLibraryAlongWithVersions(request, libraryName, "token", "key");
assertThat(
response.getBody(),
is(equalTo("The library and all its associated versions have been removed successfully.")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class CqlLibraryServiceTest {
@InjectMocks private CqlLibraryService cqlLibraryService;
@Mock private CqlLibraryRepository cqlLibraryRepository;
@Mock private LibrarySetService librarySetService;
@Mock private MeasureServiceClient measureServiceClient;

@Mock private ElmTranslatorClient elmTranslatorClient;

Expand Down Expand Up @@ -308,4 +309,63 @@ void testFindLibraryUsageWhenLibraryNameBlank() {
BadRequestObjectException.class, () -> cqlLibraryService.findLibraryUsage(null));
assertThat(ex.getMessage(), is(equalTo("Please provide library name.")));
}

@Test
void testDeleteLibraryAlongWithVersionsSuccess() {
String libraryName = "test";
CqlLibrary cqlLibrary = CqlLibrary.builder().cqlLibraryName(libraryName).build();
when(cqlLibraryRepository.existsByCqlLibraryName(anyString())).thenReturn(true);
when(cqlLibraryRepository.findLibraryUsageByLibraryName(anyString())).thenReturn(List.of());
when(measureServiceClient.getLibraryUsageInMeasures(anyString(), anyString(), anyString()))
.thenReturn(List.of());
when(cqlLibraryRepository.findAllByCqlLibraryName(anyString())).thenReturn(List.of(cqlLibrary));

cqlLibraryService.deleteLibraryAlongWithVersions(libraryName, "token", "key");
verify(cqlLibraryRepository, times(1)).deleteAll(List.of(cqlLibrary));
}

@Test
void testDeleteLibraryAlongWithVersionsIfUsedInLibrary() {
String libraryName = "test";
String owner = "john";
LibraryUsage usage = LibraryUsage.builder().name(libraryName).owner(owner).build();
when(cqlLibraryRepository.existsByCqlLibraryName(anyString())).thenReturn(true);
when(cqlLibraryRepository.findLibraryUsageByLibraryName(anyString()))
.thenReturn(List.of(usage));
Exception ex =
assertThrows(
GeneralConflictException.class,
() -> cqlLibraryService.deleteLibraryAlongWithVersions(libraryName, "token", "key"));
assertThat(
ex.getMessage(), is(equalTo("Library is being used actively, hence can not be deleted.")));
}

@Test
void testDeleteLibraryAlongWithVersionsIfUsedInMeasure() {
String libraryName = "test";
String owner = "john";
LibraryUsage usage = LibraryUsage.builder().name(libraryName).owner(owner).build();
when(cqlLibraryRepository.existsByCqlLibraryName(anyString())).thenReturn(true);
when(cqlLibraryRepository.findLibraryUsageByLibraryName(anyString())).thenReturn(List.of());
when(measureServiceClient.getLibraryUsageInMeasures(anyString(), anyString(), anyString()))
.thenReturn(List.of(usage));
Exception ex =
assertThrows(
GeneralConflictException.class,
() -> cqlLibraryService.deleteLibraryAlongWithVersions(libraryName, "token", "key"));
assertThat(
ex.getMessage(), is(equalTo("Library is being used actively, hence can not be deleted.")));
}

@Test
void testDeleteLibraryAlongWithVersionsIfOneNotExists() {
String libraryName = "test";
when(cqlLibraryRepository.existsByCqlLibraryName(anyString())).thenReturn(false);
Exception ex =
assertThrows(
ResourceNotFoundException.class,
() -> cqlLibraryService.deleteLibraryAlongWithVersions(libraryName, "token", "key"));
assertThat(
ex.getMessage(), is(equalTo("Could not find resource Library with name: " + libraryName)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gov.cms.madie.cqllibraryservice.services;

import gov.cms.madie.cqllibraryservice.config.EnvironmentConfig;
import gov.cms.madie.cqllibraryservice.dto.LibraryUsage;
import gov.cms.madie.cqllibraryservice.exceptions.MeasureServiceException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.util.List;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class MeasureServiceClientTest {
@Mock private EnvironmentConfig environmentConfig;
@Mock private RestTemplate restTemplate;

@InjectMocks private MeasureServiceClient measureServiceClient;

private final String token = "124";
private final String apiKey = "key";
private final String libraryName = "Test";

@Test
void testGetLibraryUsageInMeasures() {
String owner = "john";
LibraryUsage libraryUsage = LibraryUsage.builder().name(libraryName).owner(owner).build();
when(environmentConfig.getMeasureServiceBaseUrl()).thenReturn("url");
when(restTemplate.exchange(any(RequestEntity.class), any(ParameterizedTypeReference.class)))
.thenReturn(ResponseEntity.ok(List.of(libraryUsage)));
List<LibraryUsage> libraryUsages =
measureServiceClient.getLibraryUsageInMeasures(libraryName, token, apiKey);
assertThat(libraryUsages.size(), is(equalTo(1)));
assertThat(libraryUsages.get(0).getName(), is(equalTo(libraryName)));
assertThat(libraryUsages.get(0).getOwner(), is(equalTo(owner)));
}

@Test
void testGetLibraryUsageInMeasuresThrowsException() {
String message = "An error occurred while fetching library usage in measures";
when(environmentConfig.getMeasureServiceBaseUrl()).thenReturn("url");
doThrow(new RestClientException(message))
.when(restTemplate)
.exchange(any(RequestEntity.class), any(ParameterizedTypeReference.class));
Exception ex =
assertThrows(
MeasureServiceException.class,
() -> measureServiceClient.getLibraryUsageInMeasures(libraryName, token, apiKey));
assertThat(ex.getMessage(), is(equalTo(message)));
}
}

0 comments on commit 172c123

Please sign in to comment.