CMSgov/dpc-app

View on GitHub
dpc-bluebutton/src/main/java/gov/cms/dpc/bluebutton/clientV2/MockBlueButtonClientV2.java

Summary

Maintainability
A
3 hrs
Test Coverage
package gov.cms.dpc.bluebutton.clientV2;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.PerformanceOptionsEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import com.google.inject.name.Named;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CapabilityStatement;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Map;

public class MockBlueButtonClientV2 implements BlueButtonClientV2 {

    private static final String SAMPLE_EOB_PATH_PREFIX = "bb-test-data-v2/eob/";
    private static final String SAMPLE_PATIENT_PATH_PREFIX = "bb-test-data-v2/patient/";
    private static final String SAMPLE_COVERAGE_PATH_PREFIX = "bb-test-data-v2/coverage/";
    private static final String SAMPLE_METADATA_PATH_PREFIX = "bb-test-data-v2/";
    private static final String SAMPLE_EMPTY_BUNDLE = "bb-test-data-v2/empty.xml";

    public static final List<String> TEST_PATIENT_MBIS = List.of("2SW4N00AA00", "4SP0P00AA00", "3S58A00AA00", "4S58A00AA00", "5S58A00AA00", "1SQ3F00AA00");
    public static final Map<String, String> MBI_BENE_ID_MAP = Map.of(
            TEST_PATIENT_MBIS.get(0), "-20140000008325",
            TEST_PATIENT_MBIS.get(1), "-20140000009893",
            TEST_PATIENT_MBIS.get(2), "-19990000002208",
            TEST_PATIENT_MBIS.get(3), "-19990000002209",
            TEST_PATIENT_MBIS.get(4), "-19990000002210",
            TEST_PATIENT_MBIS.get(5), "-20000000001809"




            );
    public static final Map<String, String> MBI_HASH_MAP = Map.of(
            TEST_PATIENT_MBIS.get(0), "abadf57ff8dc94610ca0d479feadb1743c9cd3c77caf1eafde5719a154379fb6",
            TEST_PATIENT_MBIS.get(1), "8930cab29ba5fe4311a5f5bcfd5b7384f3722b711402aacf796d2ae6fea54242",
            TEST_PATIENT_MBIS.get(2), "e411277fd31da392eaa9a45df53b0c429e365626182f50d9f35810d77f0e2756",
            TEST_PATIENT_MBIS.get(3), "41af07535e0a66226cf2f0e6c551c0a15bd49192fc055aa5cd2e63f31f90a419",
            TEST_PATIENT_MBIS.get(4), "d35350fce12f555089f938c0323a13122622123038e8af057a4191fd450c2b90",
            TEST_PATIENT_MBIS.get(5), "a006edba97087f2911a35706e46bf1287d21d8fa515024ace44d589bdef9d819"

    );
    public static final List<String> TEST_PATIENT_WITH_BAD_IDS = List.of("-1", "-2", TEST_PATIENT_MBIS.get(0), TEST_PATIENT_MBIS.get(1), "-3");
    public static final String MULTIPLE_RESULTS_MBI = "0SW4N00AA00";
    public static final OffsetDateTime BFD_TRANSACTION_TIME = OffsetDateTime.ofInstant(Instant.now().truncatedTo(ChronoUnit.MILLIS), ZoneOffset.UTC);
    public static final OffsetDateTime TEST_LAST_UPDATED = OffsetDateTime.parse("2020-01-01T00:00:00-05:00");

    private final IParser parser;

    public MockBlueButtonClientV2(@Named("fhirContextR4") FhirContext fhirContextR4) {
        fhirContextR4.setPerformanceOptions(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING);
        parser = fhirContextR4.newXmlParser();
    }

    @Override
    public Bundle requestPatientFromServerByMbi(String mbi, Map<String, String> headers) throws ResourceNotFoundException {
        return loadBundle(SAMPLE_PATIENT_PATH_PREFIX, MBI_BENE_ID_MAP.getOrDefault(mbi,""));
    }

    @Override
    public Bundle requestPatientFromServer(String beneId, DateRangeParam lastUpdated, Map<String, String> headers) throws ResourceNotFoundException {
        return isInDateRange(lastUpdated) ?
                loadBundle(SAMPLE_PATIENT_PATH_PREFIX, beneId) :
                loadEmptyBundle();
    }


    @Override
    public Bundle requestPatientFromServerByMbiHash(String mbiHash, Map<String, String> headers) throws ResourceNotFoundException {
        String mbi = MBI_HASH_MAP.values().stream()
                .filter(h -> h.equals(mbiHash))
                .findFirst()
                .orElse("");
        return loadBundle(SAMPLE_PATIENT_PATH_PREFIX, MBI_BENE_ID_MAP.get(mbi));
    }

    @Override
    public Bundle requestEOBFromServer(String beneId, DateRangeParam lastUpdated, Map<String, String> headers) throws ResourceNotFoundException {
        return isInDateRange(lastUpdated) ?
                loadBundle(SAMPLE_EOB_PATH_PREFIX, beneId) :
                loadEmptyBundle();
    }

    @Override
    public Bundle requestCoverageFromServer(String beneId, DateRangeParam lastUpdated, Map<String, String> headers) throws ResourceNotFoundException {
        return isInDateRange(lastUpdated) ?
                loadBundle(SAMPLE_COVERAGE_PATH_PREFIX, beneId) :
                loadEmptyBundle();
    }

    @Override
    @SuppressWarnings("JdkObsolete") // Date class is used by FHIR stu3 Meta model
    public Bundle requestNextBundleFromServer(Bundle bundle, Map<String, String> headers) throws ResourceNotFoundException {
        // This is code is very specific to the bb-test-data directory and its contents
        final var nextLink = bundle.getLink(Bundle.LINK_NEXT).getUrl();
        final var nextUrl = URI.create(nextLink);
        final var params = URLEncodedUtils.parse(nextUrl.getQuery(), StandardCharsets.UTF_8);
        final var patient = params.stream().filter(pair -> pair.getName().equals("patient")).findFirst().orElseThrow().getValue();
        final var startIndex = params.stream().filter(pair -> pair.getName().equals("startIndex")).findFirst().orElseThrow().getValue();
        var path = SAMPLE_EOB_PATH_PREFIX + patient + "_" + startIndex + ".xml";

        try(InputStream sampleData = MockBlueButtonClientV2.class.getClassLoader().getResourceAsStream(path)) {
            final var nextBundle = parser.parseResource(Bundle.class, sampleData);
            nextBundle.getMeta().setLastUpdated(Date.from(BFD_TRANSACTION_TIME.toInstant()));
            return nextBundle;
        } catch(IOException ex) {
            throw new ResourceNotFoundException("Missing next bundle");
        }
    }

    @Override
    public CapabilityStatement requestCapabilityStatement() throws ResourceNotFoundException {
        final var path = SAMPLE_METADATA_PATH_PREFIX + "meta.xml";
        try(InputStream sampleData = loadResource(path, null)) {
            return parser.parseResource(CapabilityStatement.class, sampleData);
        } catch(IOException ex) {
            throw formNoPatientException(null);
        }
    }

    @Override
    public String hashMbi(String mbi) {
        return MBI_HASH_MAP.get(mbi);
    }

    /**
     * Read a Bundle FHIR Resource from jar's Bundle resource file.
     *
     * @param pathPrefix - Path to the XML sample data
     * @param beneId - CCW/BFD beneficiary ID of patient (https://bluebutton.cms.gov/resources/variables/bene_id)
     * @return FHIR Resource
     */
    @SuppressWarnings("JdkObsolete") // Date class is used by FHIR stu3 Meta model
    private Bundle loadBundle(String pathPrefix, String beneId) {
        try(InputStream sampleData = loadResource(pathPrefix, beneId)) {
            final var bundle = parser.parseResource(Bundle.class, sampleData);
            bundle.getMeta().setLastUpdated(Date.from(BFD_TRANSACTION_TIME.toInstant()));
            return bundle;
        } catch(IOException ex) {
            throw formNoPatientException(beneId);
        }
    }

    /**
     * Create a stream from a resource.
     *
     * @param pathPrefix - The path to the resource file
     * @param beneId - The beneficiary ID of the patient associated with the file
     * @return the stream associated with the resource
     */
    private InputStream loadResource(String pathPrefix, String beneId) throws ResourceNotFoundException {
        if (!MBI_BENE_ID_MAP.containsValue(beneId)) {
            throw formNoPatientException(beneId);
        }
        final var path = pathPrefix + beneId + ".xml";
        return MockBlueButtonClientV2.class.getClassLoader().getResourceAsStream(path);
    }

    /**
     * Does the passed in date range matches resources without lastUpdated
     *
     * @param range to test
     * @return true iff date range matches
     */
    @SuppressWarnings("JdkObsolete") // Date class is used by HAPI FHIR DateRangeParam
    private boolean isInDateRange(DateRangeParam range) {
        if (range == null) return true;
        final var upperBound = range.getUpperBoundAsInstant();
        final var lowerBound = range.getLowerBoundAsInstant();
        return (upperBound == null || upperBound.toInstant().isAfter(TEST_LAST_UPDATED.toInstant())) &&
            (lowerBound == null || lowerBound.toInstant().isBefore(TEST_LAST_UPDATED.toInstant()));
    }

    /**
     * Return an empty bundle.
     *
     * @return a Bundle
     */
    @SuppressWarnings("JdkObsolete") // Date class is used by FHIR stu3 Meta model
    private Bundle loadEmptyBundle() {
        try(InputStream sampleData = MockBlueButtonClientV2.class.getClassLoader().getResourceAsStream(SAMPLE_EMPTY_BUNDLE)) {
            final var bundle = parser.parseResource(Bundle.class, sampleData);
            bundle.getMeta().setLastUpdated(Date.from(BFD_TRANSACTION_TIME.toInstant()));
            return bundle;
        } catch(IOException ex) {
            throw formNoPatientException(null);
        }
    }

    private ResourceNotFoundException formNoPatientException(String patientID) {
        return new ResourceNotFoundException("No patient found with ID: " + patientID);
    }
}