Skip to content

Commit

Permalink
Merge pull request #516 from CMSgov/story-503
Browse files Browse the repository at this point in the history
Story 503
  • Loading branch information
saquino0827 committed Dec 22, 2017
2 parents 0874b3b + 400ac83 commit 301fa3d
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
Expand All @@ -26,6 +27,7 @@
*/
@RestController
@RequestMapping("/cpc")
@CrossOrigin
public class CpcFileControllerV1 {

private static final Logger API_LOG = LoggerFactory.getLogger(Constants.API_LOG);
Expand Down Expand Up @@ -69,7 +71,7 @@ public ResponseEntity<List<UnprocessedCpcFileData>> getUnprocessedCpcPlusFiles()
headers = {"Accept=" + Constants.V1_API_ACCEPT})
public ResponseEntity<InputStreamResource> getFileById(@PathVariable("fileId") String fileId)
throws IOException {
API_LOG.info("CPC+ file request received");
API_LOG.info("CPC+ file retrieval request received");

if (blockCpcPlusApi()) {
API_LOG.info("CPC+ file request blocked by feature flag");
Expand All @@ -78,20 +80,44 @@ public ResponseEntity<InputStreamResource> getFileById(@PathVariable("fileId") S

InputStreamResource content = cpcFileService.getFileById(fileId);

API_LOG.info("CPC+ file request succeeded");
API_LOG.info("CPC+ file retrieval request succeeded");

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_XML);

return new ResponseEntity<>(content, httpHeaders, HttpStatus.OK);
}

/**
* Updates a file's status to processed in the database
*
* @param fileId Identifier of the file needing to be updated
* @return Message if the file was updated or not
*/
@RequestMapping(method = RequestMethod.PUT, value = "/file/{fileId}",
headers = {"Accept=" + Constants.V1_API_ACCEPT} )
public ResponseEntity<String> markFileProcessed(@PathVariable("fileId") String fileId) {
if (blockCpcPlusApi()) {
API_LOG.info("CPC+ unprocessed files request blocked by feature flag");
return new ResponseEntity<>(null, null, HttpStatus.FORBIDDEN);
}

API_LOG.info("CPC+ update file as processed request received");
String message = cpcFileService.processFileById(fileId);
API_LOG.info("CPC+ update file as processed request succeeded");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);

return new ResponseEntity<>(message, httpHeaders, HttpStatus.OK);
}

/**
* Checks whether the the CPC+ APIs should not be allowed to execute.
*
* @return Whether the CPC+ APIs should be blocked.
*/
private boolean blockCpcPlusApi() {
return EnvironmentHelper.isPresent(Constants.NO_CPC_PLUS_API_ENV_VARIABLE);

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gov.cms.qpp.conversion.api.controllers.v1;

import gov.cms.qpp.conversion.api.exceptions.InvalidFileTypeException;
import gov.cms.qpp.conversion.api.exceptions.NoFileInDatabaseException;
import gov.cms.qpp.conversion.api.model.Constants;
import gov.cms.qpp.conversion.api.services.AuditService;
Expand Down Expand Up @@ -60,7 +61,7 @@ ResponseEntity<AllErrors> handleQppValidationException(QppValidationException ex

/**
* "Catch" the {@link NoFileInDatabaseException}.
* Return the {@link AllErrors} with an HTTP status 422.
* Return the {@link AllErrors} with an HTTP status 404.
*
* @param exception The NoFileInDatabaseException that was "caught".
* @return The NoFileInDatabaseException message
Expand All @@ -74,6 +75,23 @@ ResponseEntity<String> handleFileNotFoundException(NoFileInDatabaseException exc

return new ResponseEntity<>(exception.getMessage(), httpHeaders, HttpStatus.NOT_FOUND);
}

/**
* "Catch" the {@link InvalidFileTypeException}.
* Return the {@link AllErrors} with an HTTP status 404.
*
* @param exception The InvalidFileTypeException that was "caught".
* @return The InvalidFileTypeException message
*/
@ExceptionHandler(InvalidFileTypeException.class)
@ResponseBody
ResponseEntity<String> handleInvalidFileTypeException(InvalidFileTypeException exception) {
API_LOG.error("A file type error occurred", exception);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);

return new ResponseEntity<>(exception.getMessage(), httpHeaders, HttpStatus.NOT_FOUND);
}

private ResponseEntity<AllErrors> cope(TransformException exception) {
HttpHeaders httpHeaders = new HttpHeaders();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package gov.cms.qpp.conversion.api.exceptions;

/**
* Exception for handling an invalid file type
*/
public class InvalidFileTypeException extends RuntimeException {

/**
* Constructor to call RuntimeException
* @param message Error response
*/
public InvalidFileTypeException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@ public interface CpcFileService {
* @throws IOException for invalid IOUtils usage
*/
InputStreamResource getFileById(String fileId) throws IOException;

/**
* Marks a CPC File as processed by id
*
* @param fileId Identifier of the CPC+ file
* @return Success or failure message
*/
String processFileById(String fileId);
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package gov.cms.qpp.conversion.api.services;

import gov.cms.qpp.conversion.api.exceptions.InvalidFileTypeException;
import gov.cms.qpp.conversion.api.exceptions.NoFileInDatabaseException;
import gov.cms.qpp.conversion.api.model.Metadata;
import gov.cms.qpp.conversion.api.model.UnprocessedCpcFileData;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

/**
* Service for handling Cpc File meta data
*/
@Service
public class CpcFileServiceImpl implements CpcFileService {

public static final String FILE_NOT_FOUND = "File not found!";
protected static final String INVALID_FILE = "The file was not a CPC+ file.";
protected static final String FILE_FOUND = "The file was found and will be updated as processed.";

@Autowired
private DbService dbService;
Expand Down Expand Up @@ -44,13 +47,35 @@ public List<UnprocessedCpcFileData> getUnprocessedCpcPlusFiles() {
*/
public InputStreamResource getFileById(String fileId) {
Metadata metadata = dbService.getMetadataById(fileId);
if (metadata != null && metadata.getCpc() != null && !metadata.getCpcProcessed()) {
if (isAnUnprocessedCpcFile(metadata)) {
return new InputStreamResource(storageService.getFileByLocationId(metadata.getSubmissionLocator()));
} else {
throw new NoFileInDatabaseException(FILE_NOT_FOUND);
}
}

/**
* Process to ensure the file is an unprocessed cpc+ file and marks the file as processed
*
* @param fileId Identifier of the CPC+ file
* @return Success or failure message.
*/
public String processFileById(String fileId) {
Metadata metadata = dbService.getMetadataById(fileId);
if (metadata == null) {
throw new NoFileInDatabaseException(FILE_NOT_FOUND);
} else if (metadata.getCpc() == null) {
throw new InvalidFileTypeException(INVALID_FILE);
} else if (metadata.getCpcProcessed()) {
return FILE_FOUND;
} else {
metadata.setCpcProcessed(true);
CompletableFuture<Metadata> metadataFuture = dbService.write(metadata);
metadataFuture.join();
return FILE_FOUND;
}
}

/**
* Service to transform a {@link Metadata} list into the {@link UnprocessedCpcFileData}
*
Expand All @@ -60,4 +85,14 @@ public InputStreamResource getFileById(String fileId) {
private List<UnprocessedCpcFileData> transformMetaDataToUnprocessedCpcFileData(List<Metadata> metadataList) {
return metadataList.stream().map(UnprocessedCpcFileData::new).collect(Collectors.toList());
}

/**
* Determines if the file is unprocessed and is CPC+
*
* @param metadata Data to be determined valid or invalid
* @return result of the check
*/
private boolean isAnUnprocessedCpcFile(Metadata metadata) {
return metadata != null && metadata.getCpc() != null && !metadata.getCpcProcessed();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -71,6 +72,28 @@ void testGetFileById() throws IOException {
.isEqualTo("1234");
}

@Test
void testMarkFileAsProcessedReturnsSuccess() {
when(cpcFileService.processFileById(anyString())).thenReturn("success!");

ResponseEntity<String> response = cpcFileControllerV1.markFileProcessed("meep");

verify(cpcFileService, times(1)).processFileById("meep");

assertThat(response.getBody()).isEqualTo("success!");
}

@Test
void testMarkFileAsProcessedHttpStatusOk() {
when(cpcFileService.processFileById(anyString())).thenReturn("success!");

ResponseEntity<String> response = cpcFileControllerV1.markFileProcessed("meep");

verify(cpcFileService, times(1)).processFileById("meep");

assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}

@Test
void testEndpoint1WithFeatureFlagDisabled() {
System.setProperty(Constants.NO_CPC_PLUS_API_ENV_VARIABLE, "trueOrWhatever");
Expand All @@ -91,6 +114,16 @@ void testEndpoint2WithFeatureFlagDisabled() throws IOException {
assertThat(cpcResponse.getBody()).isNull();
}

@Test
void testEndpoint3WithFeatureFlagDisabled() throws IOException {
System.setProperty(Constants.NO_CPC_PLUS_API_ENV_VARIABLE, "trueOrWhatever");

ResponseEntity<String> cpcResponse = cpcFileControllerV1.markFileProcessed("meep");

assertThat(cpcResponse.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
assertThat(cpcResponse.getBody()).isNull();
}

List<UnprocessedCpcFileData> createMockedUnprocessedDataList() {
Metadata metadata = new Metadata();
metadata.setSubmissionLocator("Test");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package gov.cms.qpp.conversion.api.controllers.v1;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.powermock.api.mockito.PowerMockito.when;

import gov.cms.qpp.conversion.Converter;
import gov.cms.qpp.conversion.PathSource;
import gov.cms.qpp.conversion.api.exceptions.InvalidFileTypeException;
import gov.cms.qpp.conversion.api.exceptions.NoFileInDatabaseException;
import gov.cms.qpp.conversion.api.services.AuditService;
import gov.cms.qpp.conversion.api.services.CpcFileServiceImpl;
import gov.cms.qpp.conversion.model.error.AllErrors;
import gov.cms.qpp.conversion.model.error.QppValidationException;
import gov.cms.qpp.conversion.model.error.TransformException;
import gov.cms.qpp.test.MockitoExtension;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -19,15 +23,10 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import gov.cms.qpp.conversion.Converter;
import gov.cms.qpp.conversion.PathSource;
import gov.cms.qpp.conversion.api.exceptions.NoFileInDatabaseException;
import gov.cms.qpp.conversion.api.services.AuditService;
import gov.cms.qpp.conversion.api.services.CpcFileServiceImpl;
import gov.cms.qpp.conversion.model.error.AllErrors;
import gov.cms.qpp.conversion.model.error.QppValidationException;
import gov.cms.qpp.conversion.model.error.TransformException;
import gov.cms.qpp.test.MockitoExtension;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.powermock.api.mockito.PowerMockito.when;

@ExtendWith(MockitoExtension.class)
class ExceptionHandlerControllerV1Test {
Expand Down Expand Up @@ -149,4 +148,36 @@ void testFileNotFoundExceptionBody() {
ResponseEntity<String> responseEntity = objectUnderTest.handleFileNotFoundException(exception);
assertThat(responseEntity.getBody()).isEqualTo(CpcFileServiceImpl.FILE_NOT_FOUND);
}

@Test
void testInvalidFileTypeExceptionStatusCode() {
InvalidFileTypeException exception =
new InvalidFileTypeException(CpcFileServiceImpl.FILE_NOT_FOUND);

ResponseEntity<String> responseEntity = objectUnderTest.handleInvalidFileTypeException(exception);

assertWithMessage("The response entity's status code must be 422.")
.that(responseEntity.getStatusCode())
.isEquivalentAccordingToCompareTo(HttpStatus.NOT_FOUND);
}

@Test
void testInvalidFileTypeExceptionHeaderContentType() {
InvalidFileTypeException exception =
new InvalidFileTypeException(CpcFileServiceImpl.FILE_NOT_FOUND);

ResponseEntity<String> responseEntity = objectUnderTest.handleInvalidFileTypeException(exception);

assertThat(responseEntity.getHeaders().getContentType())
.isEquivalentAccordingToCompareTo(MediaType.TEXT_PLAIN);
}

@Test
void testInvalidFileTypeExceptionBody() {
InvalidFileTypeException exception =
new InvalidFileTypeException(CpcFileServiceImpl.FILE_NOT_FOUND);

ResponseEntity<String> responseEntity = objectUnderTest.handleInvalidFileTypeException(exception);
assertThat(responseEntity.getBody()).isEqualTo(CpcFileServiceImpl.FILE_NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gov.cms.qpp.conversion.api.exceptions;

import org.junit.jupiter.api.Test;

import static com.google.common.truth.Truth.assertThat;

class InvalidFileTypeExceptionTest {

@Test
void testConstructor() {
InvalidFileTypeException invalidFileTypeException = new InvalidFileTypeException("test");


assertThat(invalidFileTypeException).hasMessageThat().isEqualTo("test");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gov.cms.qpp.conversion.api.exceptions;

import org.junit.jupiter.api.Test;

import static com.google.common.truth.Truth.assertThat;

class NoFileInDatabaseExceptionTest {

@Test
void testConstructor() {
NoFileInDatabaseException noFileInDatabaseException = new NoFileInDatabaseException("test");


assertThat(noFileInDatabaseException).hasMessageThat().isEqualTo("test");
}
}
Loading

0 comments on commit 301fa3d

Please sign in to comment.