> dataPoints = Lists.newArrayList();
+
+ for (JsonNode listEntryNode : asRequiredNode(responseNode, getListNodeName())) {
+
+ asDataPoint(listEntryNode, measureUnitMagicNumber).ifPresent(dataPoints::add);
+ }
+
+ return dataPoints;
+ }
+
+ /**
+ * Creates a data point header with information describing the data point created around the measure.
+ *
+ * Note: Additional properties within the header come from the iHealth API and are not defined by the data point
+ * header schema. Additional properties are subject to change.
+ */
+ protected DataPointHeader createDataPointHeader(JsonNode listEntryNode, Measure measure) {
+
+ DataPointAcquisitionProvenance.Builder acquisitionProvenanceBuilder =
+ new DataPointAcquisitionProvenance.Builder(RESOURCE_API_SOURCE_NAME);
+
+ asOptionalString(listEntryNode, "DataSource").ifPresent(
+ dataSource -> setAppropriateModality(dataSource, acquisitionProvenanceBuilder));
+
+ DataPointAcquisitionProvenance acquisitionProvenance = acquisitionProvenanceBuilder.build();
+
+ asOptionalString(listEntryNode, "DataID")
+ .ifPresent(externalId -> acquisitionProvenance.setAdditionalProperty("external_id",
+ externalId));
+
+ asOptionalLong(listEntryNode, "LastChangeTime").ifPresent(
+ lastUpdatedInUnixSecs -> acquisitionProvenance.setAdditionalProperty("source_updated_date_time",
+ ofInstant(ofEpochSecond(lastUpdatedInUnixSecs), ZoneId.of("Z"))));
+
+ return new DataPointHeader.Builder(UUID.randomUUID().toString(), measure.getSchemaId())
+ .setAcquisitionProvenance(acquisitionProvenance)
+ .build();
+
+ }
+
+ /**
+ * Get an effective time frame based on the measurement date/time information in the list entry node. The effective
+ * time frame is set as a single point in time using an OffsetDateTime. This method does not get effective time
+ * frame as a time interval.
+ *
+ * @param listEntryNode A single node from the response result array.
+ */
+ protected static Optional getEffectiveTimeFrameAsDateTime(JsonNode listEntryNode) {
+
+ Optional weirdSeconds = asOptionalLong(listEntryNode, "MDate");
+
+ if (!weirdSeconds.isPresent()) {
+ return Optional.empty();
+ }
+
+ ZoneOffset zoneOffset = null;
+
+ // if the time zone is a JSON string
+ if (asOptionalString(listEntryNode, "TimeZone").isPresent() &&
+ !asOptionalString(listEntryNode, "TimeZone").get().isEmpty()) {
+
+ zoneOffset = ZoneOffset.of(asOptionalString(listEntryNode, "TimeZone").get());
+ }
+ // if the time zone is an JSON integer
+ else if (asOptionalLong(listEntryNode, "TimeZone").isPresent()) {
+
+ Long timeZoneOffsetValue = asOptionalLong(listEntryNode, "TimeZone").get();
+
+ String timeZoneString = timeZoneOffsetValue.toString();
+
+ // Zone offset cannot parse a positive string offset that's missing a '+' sign (i.e., "0200" vs "+0200")
+ if (timeZoneOffsetValue >= 0) {
+
+ timeZoneString = "+" + timeZoneString;
+ }
+
+ zoneOffset = ZoneOffset.of(timeZoneString);
+ }
+
+ if (zoneOffset == null) {
+
+ return Optional.empty();
+ }
+
+ return Optional.of(new TimeFrame(getDateTimeWithCorrectOffset(weirdSeconds.get(), zoneOffset)));
+ }
+
+ /**
+ * This method transforms a timestamp from an iHealth response (which is in the form of local time as epoch
+ * seconds) into an {@link OffsetDateTime} with the correct date/time and offset. The timestamps provided in
+ * iHealth responses are not unix epoch seconds in UTC but instead a unix epoch seconds value that is offset by the
+ * time zone of the data point.
+ */
+ protected static OffsetDateTime getDateTimeWithCorrectOffset(Long localTimeAsEpochSeconds, ZoneOffset zoneOffset) {
+
+ /*
+ iHealth provides the local time of a measurement as if it had occurred in UTC, along with the timezone
+ offset where the measurement occurred. To retrieve the correct OffsetDateTime, we must retain the local
+ date/time value, but replace the timezone offset.
+ */
+ return OffsetDateTime.ofInstant(Instant.ofEpochSecond(localTimeAsEpochSeconds), ZoneOffset.UTC)
+ .withOffsetSameLocal(zoneOffset);
+ }
+
+ /**
+ * @param dateTimeInUnixSecondsWithLocalTimeOffset A unix epoch timestamp in local time.
+ * @param timeZoneString The time zone offset as a String (e.g., "+0200","-2").
+ * @return The date time with the correct offset.
+ */
+ protected static OffsetDateTime getDateTimeAtStartOfDayWithCorrectOffset(
+ Long dateTimeInUnixSecondsWithLocalTimeOffset, String timeZoneString) {
+
+ // Since the timestamps are in local time, we can use the local date time provided by rendering the timestamp
+ // in UTC, then translating that local time to the appropriate offset.
+ OffsetDateTime dateTimeFromOffsetInstant =
+ ofInstant(ofEpochSecond(dateTimeInUnixSecondsWithLocalTimeOffset),
+ ZoneId.of("Z"));
+
+ return dateTimeFromOffsetInstant.toLocalDate().atStartOfDay().atOffset(ZoneOffset.of(timeZoneString));
+ }
+
+ /**
+ * Gets the user note from a list entry node if that property exists.
+ *
+ * @param listEntryNode A single entry from the response result array.
+ */
+ protected static Optional getUserNoteIfExists(JsonNode listEntryNode) {
+
+ Optional note = asOptionalString(listEntryNode, "Note");
+
+ if (note.isPresent() && !note.get().isEmpty()) {
+
+ return note;
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Sets the correct DataPointModality based on the iHealth value indicating the source of the DataPoint.
+ *
+ * @param dataSourceValue The iHealth value in the list entry node indicating the source of the DataPoint.
+ * @param builder The DataPointAcquisitionProvenance builder to set the modality.
+ */
+ private void setAppropriateModality(String dataSourceValue, DataPointAcquisitionProvenance.Builder builder) {
+
+ if (dataSourceValue.equals(DATA_SOURCE_FROM_DEVICE)) {
+ builder.setModality(SENSED);
+ }
+ else if (dataSourceValue.equals(DATA_SOURCE_MANUAL)) {
+ builder.setModality(SELF_REPORTED);
+ }
+ }
+
+ /**
+ * @return The name of the JSON array that contains the individual data points. This is different per endpoint.
+ */
+ protected abstract String getListNodeName();
+
+ /**
+ * @return The name of the JSON property whose value indicates the unit of measure used to render the values in the
+ * response. This is different per endpoint and some endpoints do not provide any units, in which case, the value
+ * should be an empty Optional.
+ */
+ protected abstract Optional getMeasureUnitNodeName();
+
+ /**
+ * @param listEntryNode A single entry from the response result array.
+ * @param measureUnitMagicNumber The number representing the units used to render the response, according to
+ * iHealth. This is retrieved from the main body of the response node. If the measure type does not use units, then
+ * this value is null.
+ * @return The data point mapped from the listEntryNode, unless it is skipped.
+ */
+ protected abstract Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber);
+}
diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java
new file mode 100644
index 00000000..0b31f73c
--- /dev/null
+++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.DataPoint;
+import org.openmhealth.schema.domain.omh.HeartRate;
+
+import java.util.Optional;
+
+import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble;
+
+
+/**
+ * An abstract mapper that maps iHealth responses into {@link HeartRate} measures.
+ *
+ * @author Chris Schaefbauer
+ * @author Emerson Farrugia
+ */
+public abstract class IHealthHeartRateDataPointMapper extends IHealthDataPointMapper {
+
+ @Override
+ protected Optional getMeasureUnitNodeName() {
+ return Optional.empty();
+ }
+
+ @Override
+ protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) {
+
+ double heartRateValue = asRequiredDouble(listEntryNode, "HR");
+
+ if (heartRateValue == 0) {
+ return Optional.empty();
+ }
+
+ HeartRate.Builder heartRateBuilder = new HeartRate.Builder(heartRateValue);
+
+ getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(heartRateBuilder::setEffectiveTimeFrame);
+
+ getUserNoteIfExists(listEntryNode).ifPresent(heartRateBuilder::setUserNotes);
+
+ HeartRate heartRate = heartRateBuilder.build();
+ return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, heartRate), heartRate));
+ }
+}
diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java
new file mode 100644
index 00000000..f735a677
--- /dev/null
+++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.DataPoint;
+import org.openmhealth.schema.domain.omh.PhysicalActivity;
+
+import java.time.ZoneOffset;
+import java.util.Optional;
+
+import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndEndDateTime;
+import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*;
+
+
+/**
+ * A mapper that translates responses from the iHealth /sport.json
endpoint into {@link PhysicalActivity}
+ * measures.
+ *
+ * @author Chris Schaefbauer
+ * @author Emerson Farrugia
+ * @see endpoint
+ * documentation
+ */
+public class IHealthPhysicalActivityDataPointMapper extends IHealthDataPointMapper {
+
+ @Override
+ protected String getListNodeName() {
+ return "SPORTDataList";
+ }
+
+ @Override
+ protected Optional getMeasureUnitNodeName() {
+
+ return Optional.empty();
+ }
+
+ @Override
+ protected Optional> asDataPoint(JsonNode listEntryNode,
+ Integer measureUnitMagicNumber) {
+
+ String activityName = asRequiredString(listEntryNode, "SportName");
+
+ if (activityName.isEmpty()) {
+
+ return Optional.empty();
+ }
+
+ PhysicalActivity.Builder physicalActivityBuilder = new PhysicalActivity.Builder(activityName);
+
+ Optional startTimeUnixEpochSecs = asOptionalLong(listEntryNode, "SportStartTime");
+ Optional endTimeUnixEpochSecs = asOptionalLong(listEntryNode, "SportEndTime");
+ Optional timeZoneOffset = asOptionalInteger(listEntryNode, "TimeZone");
+
+ if (startTimeUnixEpochSecs.isPresent() && endTimeUnixEpochSecs.isPresent() && timeZoneOffset.isPresent()) {
+
+ Integer timeZoneOffsetValue = timeZoneOffset.get();
+ String timeZoneString = timeZoneOffsetValue.toString();
+
+ // Zone offset cannot parse a positive string offset that's missing a '+' sign (i.e., "0200" vs "+0200")
+ if (timeZoneOffsetValue >= 0) {
+ timeZoneString = "+" + timeZoneOffsetValue.toString();
+ }
+
+ physicalActivityBuilder.setEffectiveTimeFrame(ofStartDateTimeAndEndDateTime(
+ getDateTimeWithCorrectOffset(startTimeUnixEpochSecs.get(), ZoneOffset.of(timeZoneString)),
+ getDateTimeWithCorrectOffset(endTimeUnixEpochSecs.get(), ZoneOffset.of(timeZoneString))));
+ }
+
+ PhysicalActivity physicalActivity = physicalActivityBuilder.build();
+
+ return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, physicalActivity), physicalActivity));
+ }
+}
diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java
new file mode 100644
index 00000000..8259b37e
--- /dev/null
+++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.*;
+
+import java.time.ZoneOffset;
+import java.util.Optional;
+
+import static org.openmhealth.schema.domain.omh.TimeInterval.*;
+import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*;
+
+
+/**
+ * A mapper that translates responses from the iHealth /sleep.json
endpoint into {@link SleepDuration}
+ * measures.
+ *
+ * @author Chris Schaefbauer
+ * @see endpoint
+ * documentation
+ */
+public class IHealthSleepDurationDataPointMapper extends IHealthDataPointMapper {
+
+ @Override
+ protected String getListNodeName() {
+ return "SRDataList";
+ }
+
+ @Override
+ protected Optional getMeasureUnitNodeName() {
+ return Optional.empty();
+ }
+
+ @Override
+ protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) {
+
+ SleepDuration.Builder sleepDurationBuilder = new SleepDuration.Builder(
+ new DurationUnitValue(DurationUnit.MINUTE, asRequiredBigDecimal(listEntryNode, "HoursSlept")));
+
+ Optional startTime = asOptionalLong(listEntryNode, "StartTime");
+ Optional endTime = asOptionalLong(listEntryNode, "EndTime");
+
+ if (startTime.isPresent() && endTime.isPresent()) {
+
+ Optional timeZone = asOptionalString(listEntryNode, "TimeZone");
+
+ if (timeZone.isPresent()) {
+
+ sleepDurationBuilder.setEffectiveTimeFrame(ofStartDateTimeAndEndDateTime(
+ getDateTimeWithCorrectOffset(startTime.get(), ZoneOffset.of(timeZone.get())),
+ getDateTimeWithCorrectOffset(endTime.get(), ZoneOffset.of(timeZone.get()))));
+ }
+ }
+
+ getUserNoteIfExists(listEntryNode).ifPresent(sleepDurationBuilder::setUserNotes);
+
+ SleepDuration sleepDuration = sleepDurationBuilder.build();
+
+ asOptionalBigDecimal(listEntryNode, "Awaken")
+ .ifPresent(awaken -> sleepDuration.setAdditionalProperty("wakeup_count", awaken));
+
+ return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, sleepDuration), sleepDuration));
+ }
+}
diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java
new file mode 100644
index 00000000..04a04f44
--- /dev/null
+++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.DataPoint;
+import org.openmhealth.schema.domain.omh.DurationUnitValue;
+import org.openmhealth.schema.domain.omh.StepCount;
+
+import java.math.BigDecimal;
+import java.util.Optional;
+
+import static org.openmhealth.schema.domain.omh.DurationUnit.DAY;
+import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndDuration;
+import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*;
+
+
+/**
+ * A mapper that translates responses from the iHealth /activity.json
endpoint into {@link StepCount}
+ * measures.
+ *
+ * @author Chris Schaefbauer
+ * @see endpoint
+ * documentation
+ */
+public class IHealthStepCountDataPointMapper extends IHealthDataPointMapper {
+
+ @Override
+ protected String getListNodeName() {
+ return "ARDataList";
+ }
+
+ @Override
+ protected Optional getMeasureUnitNodeName() {
+ return Optional.empty();
+ }
+
+ @Override
+ protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) {
+
+ BigDecimal steps = asRequiredBigDecimal(listEntryNode, "Steps");
+
+ if (steps.intValue() == 0) {
+ return Optional.empty();
+ }
+
+ StepCount.Builder stepCountBuilder = new StepCount.Builder(steps);
+
+ Optional dateTimeString = asOptionalLong(listEntryNode, "MDate");
+
+ if (dateTimeString.isPresent()) {
+
+ Optional timeZone = asOptionalString(listEntryNode, "TimeZone");
+
+ if (timeZone.isPresent()) {
+
+ /* iHealth provides daily summaries for step counts and timestamp the datapoint at either the end of
+ the day (23:50) or at the latest time that datapoint was synced */
+ stepCountBuilder.setEffectiveTimeFrame(ofStartDateTimeAndDuration(
+ getDateTimeAtStartOfDayWithCorrectOffset(dateTimeString.get(), timeZone.get()),
+ new DurationUnitValue(DAY, 1)));
+ }
+ }
+
+ getUserNoteIfExists(listEntryNode).ifPresent(stepCountBuilder::setUserNotes);
+
+ StepCount stepCount = stepCountBuilder.build();
+
+ return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, stepCount), stepCount));
+ }
+
+
+}
diff --git a/shim-server/src/main/resources/application.yaml b/shim-server/src/main/resources/application.yaml
index 82b01332..70d56ee8 100644
--- a/shim-server/src/main/resources/application.yaml
+++ b/shim-server/src/main/resources/application.yaml
@@ -24,8 +24,21 @@ openmhealth:
server:
callbackUrlBase: http://localhost:8083
- #NOTE: Un-comment and fill in with your credentials if you're not using the UI
+ #NOTE: Un-comment and fill in the clientId/clientSecret with your credentials if you're not using the UI
#NOTE: Un-comment and set partnerAccess to true if your credentials for a given API have partner access
+ #NOTE: Un-comment and fill in your serialValues for iHealth, otherwise the iHealth shim will not work correctly
+ #ihealth:
+ # serialValues:
+ # SC: [YOUR_SC_VALUE]
+ # sportSV: [YOUR_SV_VALUE_FOR_THE_SPORT_ENDPOINT]
+ # bloodPressureSV: [YOUR_SV_VALUE_FOR_THE_BLOOD_PRESSURE_ENDPOINT]
+ # spo2SV: [YOUR_SV_VALUE_FOR_THE_SPO2_ENDPOINT]
+ # weightSV: [YOUR_SV_VALUE_FOR_THE_WEIGHT_ENDPOINT]
+ # bloodGlucoseSV: [YOUR_SV_VALUE_FOR_THE_BLOOD_GLUCOSE_ENDPOINT]
+ # activitySV: [YOUR_SV_VALUE_FOR_THE_ACTIVITY_ENDPOINT]
+ # sleepSV: [YOUR_SV_VALUE_FOR_THE_SLEEP_ENDPOINT]
+ # clientId: [YOUR_CLIENT_ID]
+ # clientSecret: [YOUR_CLIENT_SECRET]
#fitbit:
# partnerAccess: true
# clientId: [YOUR_CLIENT_ID]
@@ -33,7 +46,6 @@ openmhealth:
#fatsecret:
# clientId: [YOUR_CLIENT_ID]
# clientSecret: [YOUR_CLIENT_SECRET]
- #ihealth:
# clientId: [YOUR_CLIENT_ID]
# clientSecret: [YOUR_CLIENT_SECRET]
#jawbone:
diff --git a/shim-server/src/test/java/org/openmhealth/shim/common/mapper/DataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/common/mapper/DataPointMapperUnitTests.java
index 83c78005..171cf8c8 100644
--- a/shim-server/src/test/java/org/openmhealth/shim/common/mapper/DataPointMapperUnitTests.java
+++ b/shim-server/src/test/java/org/openmhealth/shim/common/mapper/DataPointMapperUnitTests.java
@@ -1,7 +1,13 @@
package org.openmhealth.shim.common.mapper;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.core.io.ClassPathResource;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static java.lang.String.format;
import static org.openmhealth.schema.configuration.JacksonConfiguration.newObjectMapper;
@@ -11,4 +17,25 @@
public abstract class DataPointMapperUnitTests {
protected static final ObjectMapper objectMapper = newObjectMapper();
+
+
+ /**
+ * @param classPathResourceName the name of the class path resource to load
+ * @return the contents of the resource as a {@link JsonNode}
+ * @throws RuntimeException if the resource can't be loaded
+ */
+ protected JsonNode asJsonNode(String classPathResourceName) {
+
+ ClassPathResource resource = new ClassPathResource(classPathResourceName);
+
+ try {
+ InputStream resourceInputStream = resource.getInputStream();
+ return objectMapper.readTree(resourceInputStream);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(
+ format("The class path resource '%s' can't be loaded as a JSON node.", classPathResourceName), e);
+ }
+ }
+
}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java
new file mode 100644
index 00000000..ecaf346a
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.*;
+import org.springframework.core.io.ClassPathResource;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.openmhealth.schema.domain.omh.BloodGlucose.SCHEMA_ID;
+import static org.openmhealth.schema.domain.omh.BloodGlucoseUnit.*;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED;
+import static org.openmhealth.schema.domain.omh.TemporalRelationshipToMeal.*;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthBloodGlucoseDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests {
+
+ private JsonNode responseNode;
+ private IHealthBloodGlucoseDataPointMapper mapper = new IHealthBloodGlucoseDataPointMapper();
+ List> dataPoints;
+
+ @BeforeTest
+ public void initializeResponseNode() throws IOException {
+
+ responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json");
+ }
+
+ @BeforeMethod
+ public void initializeDataPoints() {
+
+ dataPoints = mapper.asDataPoints(singletonList(responseNode));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() {
+
+ assertThat(dataPoints.size(), equalTo(2));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSensedDataPoints() {
+
+ BloodGlucose.Builder expectedBloodGlucoseBuilder = new BloodGlucose.Builder(
+ new TypedUnitValue<>(MILLIGRAMS_PER_DECILITER, 60))
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:03:27-08:00"))
+ .setTemporalRelationshipToMeal(BEFORE_BREAKFAST)
+ .setUserNotes("Such glucose, much blood.");
+
+ assertThat(dataPoints.get(0).getBody(), equalTo(expectedBloodGlucoseBuilder.build()));
+
+ assertThat(dataPoints.get(0).getBody().getAdditionalProperty("temporal_relationship_to_medication").get(),
+ equalTo("Before_taking_pills"));
+
+ testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED,
+ "f706b6152f684c0e9185b1fa6b7c5148", OffsetDateTime.parse("2015-09-17T20:03:41Z"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() {
+
+ BloodGlucose.Builder expectedBloodGlucoseBuilder =
+ new BloodGlucose.Builder(new TypedUnitValue<>(MILLIGRAMS_PER_DECILITER, 70))
+ .setTemporalRelationshipToMeal(AFTER_BREAKFAST)
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-24T14:44:40-06:00"));
+
+ assertThat(dataPoints.get(1).getBody(), equalTo(expectedBloodGlucoseBuilder.build()));
+
+ assertThat(dataPoints.get(1).getBody().getAdditionalProperty("temporal_relationship_to_medication").get(),
+ equalTo("After_taking_pills"));
+
+ assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnNoDataPointsWhenBloodGlucoseListIsEmpty() throws IOException {
+
+ ClassPathResource resource =
+ new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json");
+ JsonNode emptyListResponseNode = objectMapper.readTree(resource.getInputStream());
+
+ assertThat(mapper.asDataPoints(singletonList(emptyListResponseNode)), is(empty()));
+ }
+
+ @Test
+ public void getBloodGlucoseUnitFromMagicNumberShouldReturnCorrectBloodGlucoseUnit() {
+
+ assertThat(mapper.getBloodGlucoseUnitFromMagicNumber(0), equalTo(MILLIGRAMS_PER_DECILITER));
+ assertThat(mapper.getBloodGlucoseUnitFromMagicNumber(1), equalTo(MILLIMOLES_PER_LITER));
+ }
+
+ @Test(expectedExceptions = UnsupportedOperationException.class)
+ public void iHealthBloodGlucoseUnitEnumShouldThrowExceptionWhenInvalidMagicNumber() {
+
+ mapper.getBloodGlucoseUnitFromMagicNumber(5);
+ }
+
+}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java
new file mode 100644
index 00000000..b1a168ee
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.DataPoint;
+import org.openmhealth.schema.domain.omh.HeartRate;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED;
+import static org.openmhealth.schema.domain.omh.HeartRate.*;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests {
+
+ JsonNode responseNode;
+
+ private IHealthBloodOxygenEndpointHeartRateDataPointMapper mapper =
+ new IHealthBloodOxygenEndpointHeartRateDataPointMapper();
+
+ List> dataPoints;
+
+ @BeforeTest
+ public void initializeResponseNode() throws IOException {
+
+ responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json");
+ }
+
+ @BeforeMethod
+ public void initializeDataPoints() {
+
+ dataPoints = mapper.asDataPoints(singletonList(responseNode));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() {
+
+ assertThat(dataPoints.size(), equalTo(2));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSensedDataPoints() {
+
+ HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(80)
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-23T15:46:00-06:00"));
+
+ assertThat(dataPoints.get(0).getBody(), equalTo(expectedHeartRateBuilder.build()));
+
+ testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED,
+ "d7fb9db14b0fc3e8e1635720c28bda64", OffsetDateTime.parse("2015-09-23T21:46:00Z"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() {
+
+ HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(65)
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-24T15:03:00-06:00"))
+ .setUserNotes("Satch on satch ");
+
+ assertThat(dataPoints.get(1).getBody(), equalTo(expectedHeartRateBuilder.build()));
+
+ assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() {
+
+ assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue());
+ assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Satch on satch "));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() throws IOException {
+
+ JsonNode noHeartRateBloodOxygenNode = asJsonNode(
+ "org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json");
+
+ assertThat(mapper.asDataPoints(singletonList(noHeartRateBloodOxygenNode)), is(empty()));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() {
+
+ JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json");
+
+ assertThat(mapper.asDataPoints(singletonList(emptyNode)), is(empty()));
+
+ }
+}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java
new file mode 100644
index 00000000..9a06c4b2
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.*;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.openmhealth.schema.domain.omh.BloodPressure.*;
+import static org.openmhealth.schema.domain.omh.BloodPressureUnit.*;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthBloodPressureDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests {
+
+ private JsonNode responseNode;
+ private IHealthBloodPressureDataPointMapper mapper = new IHealthBloodPressureDataPointMapper();
+ List> dataPoints;
+
+ @BeforeTest
+ public void initializeResponseNode() throws IOException {
+
+ responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json");
+ }
+
+ @BeforeMethod
+ public void initializeDataPoints() {
+
+ dataPoints = mapper.asDataPoints(singletonList(responseNode));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() {
+
+ assertThat(dataPoints.size(), equalTo(2));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSensedDataPoints() {
+
+ BloodPressure expectedBloodPressure = new BloodPressure.Builder(
+ new SystolicBloodPressure(MM_OF_MERCURY, 120),
+ new DiastolicBloodPressure(MM_OF_MERCURY, 90))
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:23-08:00"))
+ .build();
+
+ assertThat(dataPoints.get(0).getBody(), equalTo(expectedBloodPressure));
+
+ DataPointHeader testHeader = dataPoints.get(0).getHeader();
+
+ testDataPointHeader(testHeader, SCHEMA_ID, SENSED, "c62b84d9d4b7480a8ff2aef1465aa454",
+ OffsetDateTime.parse("2015-09-17T20:04:30Z"));
+
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() {
+
+ BloodPressure expectedBloodPressure = new BloodPressure.Builder(
+ new SystolicBloodPressure(MM_OF_MERCURY, 130),
+ new DiastolicBloodPressure(MM_OF_MERCURY, 95))
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:45-06:00"))
+ .setUserNotes("BP on the up and up.")
+ .build();
+
+ assertThat(dataPoints.get(1).getBody(), equalTo(expectedBloodPressure));
+
+ DataPointHeader testHeader = dataPoints.get(1).getHeader();
+
+ assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED));
+
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() {
+
+ assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue());
+ assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("BP on the up and up."));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() {
+
+ JsonNode emptyNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json");
+
+ assertThat(mapper.asDataPoints(singletonList(emptyNode)), is(empty()));
+ }
+
+ @Test
+ public void getBloodPressureValueInMmHgReturnsCorrectValueForMmHgUnit() {
+
+ double bpValueFromMmHg = mapper.getBloodPressureValueInMmHg(120, 0);
+ assertThat(bpValueFromMmHg, equalTo(120.0));
+
+ double bpValueFromKpa = mapper.getBloodPressureValueInMmHg(16, 1);
+ assertThat(bpValueFromKpa, equalTo(120.0));
+
+ }
+
+ @Test(expectedExceptions = UnsupportedOperationException.class)
+ public void getBloodPressureValueShouldThrowExceptionForInvalidEnum() {
+
+ mapper.getBloodPressureValueInMmHg(12, 12);
+ }
+
+
+}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java
new file mode 100644
index 00000000..1ca5931d
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.DataPoint;
+import org.openmhealth.schema.domain.omh.HeartRate;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsEmptyCollection.empty;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED;
+import static org.openmhealth.schema.domain.omh.HeartRate.SCHEMA_ID;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests {
+
+ JsonNode responseNode;
+
+ private IHealthBloodPressureEndpointHeartRateDataPointMapper mapper =
+ new IHealthBloodPressureEndpointHeartRateDataPointMapper();
+
+ List> dataPoints;
+
+ @BeforeTest
+ public void initializeResponseNodes() throws IOException {
+
+ responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json");
+ }
+
+ @BeforeMethod
+ public void initializeDataPoints() {
+
+ dataPoints = mapper.asDataPoints(singletonList(responseNode));
+ }
+
+
+ @Test
+ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() {
+
+ assertThat(dataPoints.size(), equalTo(2));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSensedDataPoints() {
+
+ HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(100)
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:23-08:00"));
+ HeartRate expectedSensedHeartRate = expectedHeartRateBuilder.build();
+ assertThat(dataPoints.get(0).getBody(), equalTo(expectedSensedHeartRate));
+
+ testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED,
+ "c62b84d9d4b7480a8ff2aef1465aa454", OffsetDateTime.parse("2015-09-17T20:04:30Z"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() {
+
+ HeartRate expectedHeartRate = new HeartRate.Builder(75)
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:45-06:00"))
+ .setUserNotes("BP on the up and up.")
+ .build();
+
+ assertThat(dataPoints.get(1).getBody(), equalTo(expectedHeartRate));
+ assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() {
+
+ assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue());
+ assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("BP on the up and up."));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() throws IOException {
+
+ JsonNode noHeartRateBloodPressureNode = asJsonNode(
+ "org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json");
+
+ assertThat(mapper.asDataPoints(singletonList(noHeartRateBloodPressureNode)), is(empty()));
+ }
+}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java
new file mode 100644
index 00000000..43d38d6c
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.BodyMassIndex;
+import org.openmhealth.schema.domain.omh.DataPoint;
+import org.openmhealth.schema.domain.omh.TypedUnitValue;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.openmhealth.schema.domain.omh.BodyMassIndex.SCHEMA_ID;
+import static org.openmhealth.schema.domain.omh.BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthBodyMassIndexDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests {
+
+ private JsonNode responseNode;
+ private IHealthBodyMassIndexDataPointMapper mapper = new IHealthBodyMassIndexDataPointMapper();
+ List> dataPoints;
+
+ @BeforeTest
+ public void initializeResponse() throws IOException {
+
+ responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json");
+ }
+
+ @BeforeMethod
+ public void initializeDataPoints() {
+
+ dataPoints = mapper.asDataPoints(singletonList(responseNode));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() {
+
+ assertThat(dataPoints.size(), equalTo(2));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSensedDataPoints() {
+
+ BodyMassIndex.Builder expectedBodyMassIndexBuilder = new BodyMassIndex.Builder(new TypedUnitValue<>(
+ KILOGRAMS_PER_SQUARE_METER, 22.56052563257619))
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:09-08:00"));
+
+ assertThat(dataPoints.get(0).getBody(), equalTo(expectedBodyMassIndexBuilder.build()));
+
+ testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, "5fe5893c418b48cd8da7954f8b6c2f36",
+ OffsetDateTime.parse("2015-09-17T20:04:17Z"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() {
+
+ BodyMassIndex.Builder expectedBodyMassIndexBuilder = new BodyMassIndex.Builder(
+ new TypedUnitValue<>(KILOGRAMS_PER_SQUARE_METER, 22.56052398681641))
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:57-06:00"))
+ .setUserNotes("Weight so good, look at me now");
+
+ assertThat(dataPoints.get(1).getBody(), equalTo(expectedBodyMassIndexBuilder.build()));
+
+ testDataPointHeader(dataPoints.get(1).getHeader(), SCHEMA_ID, SELF_REPORTED,
+ "b702a3a5e998f2fca268df6daaa69871", OffsetDateTime.parse("2015-09-17T20:08:00Z"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnNoDataPointsWhenBodyMassIndexValueIsZero() throws IOException {
+
+ JsonNode zeroValueNode =
+ asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json");
+
+ assertThat(mapper.asDataPoints(singletonList(zeroValueNode)), is(empty()));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException {
+
+ JsonNode emptyListNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json");
+
+ assertThat(mapper.asDataPoints(singletonList(emptyListNode)), is(empty()));
+ }
+
+}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java
new file mode 100644
index 00000000..94b29f79
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.*;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsEmptyCollection.empty;
+import static org.openmhealth.schema.domain.omh.BodyWeight.*;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED;
+import static org.openmhealth.shim.ihealth.mapper.IHealthBodyWeightDataPointMapper.*;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthBodyWeightDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests {
+
+ protected JsonNode responseNode;
+ IHealthBodyWeightDataPointMapper mapper = new IHealthBodyWeightDataPointMapper();
+ List> dataPoints;
+
+ @BeforeTest
+ public void initializeResponseNode() throws IOException {
+
+ responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json");
+ }
+
+ @BeforeMethod
+ public void initializeDataPoints() {
+
+ dataPoints = mapper.asDataPoints(singletonList(responseNode));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() {
+
+ assertThat(dataPoints.size(), equalTo(2));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSensedDataPoints() {
+
+ BodyWeight.Builder expectedBodyWeightBuilder = new BodyWeight.Builder(
+ new MassUnitValue(MassUnit.KILOGRAM, 77.5643875134944))
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:09-08:00"));
+
+ assertThat(dataPoints.get(0).getBody(), equalTo(expectedBodyWeightBuilder.build()));
+
+
+ DataPointHeader dataPointHeader = dataPoints.get(0).getHeader();
+ testDataPointHeader(dataPointHeader, SCHEMA_ID, SENSED, "5fe5893c418b48cd8da7954f8b6c2f36",
+ OffsetDateTime.parse("2015-09-17T20:04:17Z"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() {
+
+ BodyWeight.Builder expectedBodyWeightBuilder =
+ new BodyWeight.Builder(new MassUnitValue(MassUnit.KILOGRAM, 77.56438446044922))
+ .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:57-06:00"))
+ .setUserNotes("Weight so good, look at me now");
+
+ assertThat(dataPoints.get(1).getBody(), equalTo(expectedBodyWeightBuilder.build()));
+
+ testDataPointHeader(dataPoints.get(1).getHeader(), SCHEMA_ID, SELF_REPORTED,
+ "b702a3a5e998f2fca268df6daaa69871", OffsetDateTime.parse("2015-09-17T20:08:00Z"));
+
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectUserNotes() {
+
+ assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue());
+ assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Weight so good, look at me now"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnNoDataPointsWhenWeightValueEqualsZero() throws IOException {
+
+ JsonNode zeroValueNode =
+ asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json");
+
+ assertThat(mapper.asDataPoints(singletonList(zeroValueNode)), is(empty()));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException {
+
+ JsonNode emptyListNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json");
+
+ assertThat(mapper.asDataPoints(singletonList(emptyListNode)), is(empty()));
+ }
+
+ @Test
+ public void getBodyWeightValueForUnitTypeShouldReturnCorrectValueForOmhCompatibleTypes() {
+
+ double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(66.3,
+ IHealthBodyWeightUnit.KG);
+ assertThat(bodyWeightValueForUnitType, equalTo(66.3));
+
+ bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(100.5,
+ IHealthBodyWeightUnit.LB);
+ assertThat(bodyWeightValueForUnitType, equalTo(100.5));
+ }
+
+ @Test
+ public void getBodyWeightValueForUnitTypeShouldReturnCorrectValueForOmhIncompatibleTypes() {
+
+ double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(12.4,
+ IHealthBodyWeightUnit.STONE);
+ assertThat(bodyWeightValueForUnitType, equalTo(78.74372));
+
+ }
+
+ @Test
+ public void getOmhUnitInBodyWeightUnitTypeShouldReturnCorrectMassUnits() {
+
+ assertThat(IHealthBodyWeightUnit.KG.getOmhUnit(), equalTo(MassUnit.KILOGRAM));
+ assertThat(IHealthBodyWeightUnit.LB.getOmhUnit(), equalTo(MassUnit.POUND));
+ assertThat(IHealthBodyWeightUnit.STONE.getOmhUnit(), equalTo(MassUnit.KILOGRAM));
+ }
+
+
+}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java
new file mode 100644
index 00000000..d7efdf07
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import org.openmhealth.schema.domain.omh.DataPointHeader;
+import org.openmhealth.schema.domain.omh.DataPointModality;
+import org.openmhealth.schema.domain.omh.SchemaId;
+import org.openmhealth.shim.common.mapper.DataPointMapperUnitTests;
+
+import java.time.OffsetDateTime;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.openmhealth.shim.ihealth.mapper.IHealthDataPointMapper.*;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthDataPointMapperUnitTests extends DataPointMapperUnitTests {
+
+ protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId, DataPointModality modality,
+ String externalId, OffsetDateTime updatedDateTime) {
+
+ assertThat(testHeader.getBodySchemaId(), equalTo(schemaId));
+ assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(modality));
+ assertThat(testHeader.getAcquisitionProvenance().getSourceName(),
+ equalTo(RESOURCE_API_SOURCE_NAME));
+ assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("external_id"), equalTo(
+ externalId));
+ assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("source_updated_date_time"),
+ equalTo(updatedDateTime));
+ }
+}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java
new file mode 100644
index 00000000..a384bb8b
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.HeartRate;
+import org.openmhealth.schema.domain.omh.TimeFrame;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.util.Optional;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.openmhealth.shim.ihealth.mapper.IHealthDataPointMapper.getDateTimeAtStartOfDayWithCorrectOffset;
+import static org.openmhealth.shim.ihealth.mapper.IHealthDataPointMapper.getEffectiveTimeFrameAsDateTime;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthDatapointMapperDateTimeUnitTests extends IHealthDataPointMapperUnitTests {
+
+ HeartRate.Builder builder;
+
+ @BeforeMethod
+ public void initializeBuilder() {
+
+ builder = new HeartRate.Builder(45);
+ }
+
+ @Test
+ public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsMissing() throws IOException {
+
+ JsonNode timeInfoNode = createResponseNodeWithTimeZone(null);
+ getEffectiveTimeFrameAsDateTime(timeInfoNode);
+
+ assertThat(getEffectiveTimeFrameAsDateTime(timeInfoNode).isPresent(), is(false));
+ }
+
+ @Test
+ public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsEmpty() throws IOException {
+
+ JsonNode timeInfoNode = createResponseNodeWithTimeZone("\"\"");
+
+ assertThat(getEffectiveTimeFrameAsDateTime(timeInfoNode).isPresent(), is(false));
+ }
+
+ @Test
+ public void setEffectiveTimeFrameReturnsTimeFrameInUtcWhenTimeZoneEqualsZero() throws IOException {
+
+ testTimeFrameWhenItShouldBeSetCorrectly("0", "2015-11-17T18:24:23Z");
+ }
+
+ @Test
+ public void setEffectiveTimeFrameShouldAddCorrectTimeFrameWhenTimeZoneIsPositiveInteger() throws IOException {
+
+ testTimeFrameWhenItShouldBeSetCorrectly("1", "2015-11-17T18:24:23+01:00");
+ }
+
+ @Test
+ public void setEffectiveTimeFrameShouldAddCorrectTimeFrameWhenTimeZoneIsNegativeInteger() throws IOException {
+
+ testTimeFrameWhenItShouldBeSetCorrectly("-8", "2015-11-17T18:24:23-08:00");
+ }
+
+ @Test
+ public void setEffectiveTimeFrameShouldAddCorrectTimeFrameWhenTimeZoneIsPositiveOffsetString() throws IOException {
+
+ testTimeFrameWhenItShouldBeSetCorrectly("\"+0100\"", "2015-11-17T18:24:23+01:00");
+ }
+
+
+ @Test
+ public void setEffectiveTimeFrameShouldAddCorrectTimeFrameWhenTimeZoneIsNegativeOffsetString() throws IOException {
+
+ testTimeFrameWhenItShouldBeSetCorrectly("\"-0700\"", "2015-11-17T18:24:23-07:00");
+ }
+
+ @Test
+ public void setEffectiveTimeFrameShouldAddTimeInUtcWhenTimeZoneIsZeroOffsetString() throws IOException {
+
+ testTimeFrameWhenItShouldBeSetCorrectly("\"+0000\"", "2015-11-17T18:24:23Z");
+ }
+
+ @Test
+ public void getDateTimeAtStartOfDayWithCorrectOffsetShouldReturnCorrectDateTimeWhenTimeIsAtStartOfDay() {
+
+ long startOfDayEpochSecond = OffsetDateTime.parse("2015-11-12T00:00:00Z").toEpochSecond();
+
+ OffsetDateTime dateTimeAtStartOfDay = getDateTimeAtStartOfDayWithCorrectOffset(startOfDayEpochSecond, "-0100");
+
+ assertThat(dateTimeAtStartOfDay, equalTo(OffsetDateTime.parse("2015-11-12T00:00:00-01:00")));
+ }
+
+ @Test
+ public void getDateTimeAtStartOfDayWithCorrectOffsetShouldReturnCorrectDateTimeWhenTimeIsAtEndOfDay() {
+
+ long startOfDayEpochSecond = OffsetDateTime.parse("2015-11-12T23:59:59Z").toEpochSecond();
+
+ OffsetDateTime dateTimeAtStartOfDay =
+ getDateTimeAtStartOfDayWithCorrectOffset(startOfDayEpochSecond, "+0100");
+
+ assertThat(dateTimeAtStartOfDay, equalTo(OffsetDateTime.parse("2015-11-12T00:00:00+01:00")));
+ }
+
+ public void testTimeFrameWhenItShouldBeSetCorrectly(String timezoneString, String expectedDateTime)
+ throws IOException {
+
+ JsonNode timeInfoNode = createResponseNodeWithTimeZone(timezoneString);
+ Optional effectiveTimeFrameAsDateTime = getEffectiveTimeFrameAsDateTime(timeInfoNode);
+
+ assertThat(effectiveTimeFrameAsDateTime.isPresent(), is(true));
+ assertThat(effectiveTimeFrameAsDateTime.get().getDateTime(), equalTo(OffsetDateTime.parse(expectedDateTime)));
+ }
+
+ public JsonNode createResponseNodeWithTimeZone(String timezoneString) throws IOException {
+
+ if (timezoneString == null) {
+ return objectMapper.readTree("{\"MDate\": 1447784663,\n" +
+ " \"Steps\": 100}\n");
+ }
+ else {
+ return objectMapper.readTree("{\"MDate\": 1447784663,\n" +
+ " \"Steps\": 100,\n" +
+ "\"TimeZone\": " + timezoneString + "}\n");
+ }
+ }
+}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java
new file mode 100644
index 00000000..f221b101
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.DataPoint;
+import org.openmhealth.schema.domain.omh.PhysicalActivity;
+import org.openmhealth.schema.domain.omh.TimeInterval;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED;
+import static org.openmhealth.schema.domain.omh.PhysicalActivity.SCHEMA_ID;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthPhysicalActivityDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests {
+
+
+ private JsonNode responseNode;
+ private IHealthPhysicalActivityDataPointMapper mapper = new IHealthPhysicalActivityDataPointMapper();
+ List> dataPoints;
+
+ @BeforeTest
+ public void initializeResponseNode() throws IOException {
+
+ responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json");
+ }
+
+ @BeforeMethod
+ public void initializeDataPoints() {
+
+ dataPoints = mapper.asDataPoints(singletonList(responseNode));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnTheCorrectNumberOfDataPoints() {
+
+ assertThat(dataPoints.size(), equalTo(2));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSensedDataPoints() {
+
+ PhysicalActivity.Builder expectedPhysicalActivityBuilder =
+ new PhysicalActivity.Builder("Swimming, breaststroke")
+ .setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime(
+ OffsetDateTime.parse("2015-09-17T20:02:28-08:00"),
+ OffsetDateTime.parse("2015-09-17T20:32:28-08:00")));
+
+ assertThat(dataPoints.get(0).getBody(), equalTo(expectedPhysicalActivityBuilder.build()));
+
+ testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED,
+ "3f8770f51cc84957a57d20f4fee1f34b", OffsetDateTime.parse("2015-09-17T20:02:57Z"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() {
+
+ PhysicalActivity.Builder expectedPhysicalActivityBuilder = new PhysicalActivity.Builder("Running")
+ .setEffectiveTimeFrame(
+ TimeInterval.ofStartDateTimeAndEndDateTime(
+ OffsetDateTime.parse("2015-09-22T20:43:03+01:00"),
+ OffsetDateTime.parse("2015-09-22T21:13:03+01:00")));
+
+ assertThat(dataPoints.get(1).getBody(), equalTo(expectedPhysicalActivityBuilder.build()));
+
+ assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED));
+ }
+
+ @Test
+ public void asDataPointsReturnsNoDataPointsForAnEmptyList() throws IOException {
+
+ JsonNode emptyListResponseNode =
+ asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json");
+
+ assertThat(mapper.asDataPoints(singletonList(emptyListResponseNode)), is(empty()));
+ }
+
+}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java
new file mode 100644
index 00000000..0cdd4cf8
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.openmhealth.schema.domain.omh.DataPoint;
+import org.openmhealth.schema.domain.omh.DurationUnitValue;
+import org.openmhealth.schema.domain.omh.SleepDuration;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.math.BigDecimal;
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED;
+import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED;
+import static org.openmhealth.schema.domain.omh.DurationUnit.MINUTE;
+import static org.openmhealth.schema.domain.omh.SleepDuration.*;
+import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndEndDateTime;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthSleepDurationDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests {
+
+ private JsonNode responseNode;
+ private IHealthSleepDurationDataPointMapper mapper = new IHealthSleepDurationDataPointMapper();
+ private List> dataPoints;
+
+ @BeforeClass
+ public void initializeResponse() {
+
+ responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-sleep.json");
+ }
+
+ @BeforeMethod
+ public void initializeDataPoints() {
+
+ dataPoints = mapper.asDataPoints(singletonList(responseNode));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() {
+
+ assertThat(dataPoints.size(), equalTo(3));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectDataPointsWhenSensed() {
+
+ SleepDuration.Builder expectedSleepDurationBuilder = new SleepDuration.Builder(new DurationUnitValue(
+ MINUTE, 345));
+
+ expectedSleepDurationBuilder.setEffectiveTimeFrame(ofStartDateTimeAndEndDateTime(
+ OffsetDateTime.parse("2015-11-15T01:51:00-07:00"),
+ OffsetDateTime.parse("2015-11-15T09:16:00-07:00")));
+
+ assertThat(dataPoints.get(0).getBody(), equalTo(expectedSleepDurationBuilder.build()));
+
+ testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED,
+ "7eb7292b90d710ae7b7f61b75f9425cf", OffsetDateTime.parse("2015-11-15T16:19:10Z"));
+ }
+
+ @Test
+ public void asDataPointsShouldMapAwakenAsAdditionalProperty() {
+
+ assertThat(((BigDecimal) dataPoints.get(0).getBody().getAdditionalProperties().get("wakeup_count")).intValue(),
+ equalTo(13));
+
+ assertThat(((BigDecimal) dataPoints.get(2).getBody().getAdditionalProperties().get("wakeup_count")).intValue(),
+ equalTo(0));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnDataPointWithUserNoteWhenNoteIsPresent() {
+
+ SleepDuration.Builder expectedSleepDurationBuilder =
+ new SleepDuration.Builder(new DurationUnitValue(MINUTE, 195));
+
+ expectedSleepDurationBuilder.setEffectiveTimeFrame(ofStartDateTimeAndEndDateTime(
+ OffsetDateTime.parse("2015-11-15T13:51:00+01:00"),
+ OffsetDateTime.parse("2015-11-15T17:16:00+01:00")));
+
+ expectedSleepDurationBuilder.setUserNotes("Best sleep ever");
+
+ assertThat(dataPoints.get(1).getBody(), equalTo(expectedSleepDurationBuilder.build()));
+
+ assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue());
+ assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Best sleep ever"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectDataPointsWhenManuallyEntered() {
+
+ assertThat(dataPoints.get(2).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() {
+
+ JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json");
+
+ assertThat(mapper.asDataPoints(singletonList(emptyNode)), is(empty()));
+ }
+}
diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java
new file mode 100644
index 00000000..69a35e97
--- /dev/null
+++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2015 Open mHealth
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmhealth.shim.ihealth.mapper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.hamcrest.Matchers;
+import org.openmhealth.schema.domain.omh.*;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.core.Is.is;
+import static org.openmhealth.schema.domain.omh.DataPointModality.*;
+import static org.openmhealth.schema.domain.omh.DurationUnit.*;
+import static org.openmhealth.schema.domain.omh.StepCount.*;
+
+
+/**
+ * @author Chris Schaefbauer
+ */
+public class IHealthStepCountDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests {
+
+ private JsonNode responseNode;
+ private IHealthStepCountDataPointMapper mapper = new IHealthStepCountDataPointMapper();
+ List> dataPoints;
+
+
+ @BeforeClass
+ public void initializeResponseNode() {
+
+ responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-activity.json");
+ }
+
+ @BeforeMethod
+ public void initializeDataPoints() {
+
+ dataPoints = mapper.asDataPoints(singletonList(responseNode));
+ }
+
+ @Test
+ public void asDataPointsShouldNotMapDataPointsWithZeroSteps() {
+
+ JsonNode nodeWithNoSteps = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-activity-no-steps.json");
+
+ assertThat(mapper.asDataPoints(singletonList(nodeWithNoSteps)), is(empty()));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() {
+
+ assertThat(mapper.asDataPoints(singletonList(responseNode)).size(), equalTo(2));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnCorrectDataPointsWhenSensed() {
+
+ StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(21);
+
+ expectedStepCountBuilder.setEffectiveTimeFrame(
+ TimeInterval.ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-11-16T00:00:00+05:00"),
+ new DurationUnitValue(DAY, 1)));
+
+ assertThat(dataPoints.get(0).getBody(), equalTo(expectedStepCountBuilder.build()));
+
+ testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED,
+ "ac67c4ccf64af669d92569af85d19f59", OffsetDateTime.parse("2015-11-17T19:23:21Z"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnDataPointWithUserNoteWhenNoteIsPresent() {
+
+ StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(4398);
+
+ expectedStepCountBuilder.setEffectiveTimeFrame(
+ TimeInterval.ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-11-18T00:00:00Z"),
+ new DurationUnitValue(DAY, 1))).setUserNotes("Great steps");
+
+ assertThat(dataPoints.get(1).getBody(), Matchers.equalTo(expectedStepCountBuilder.build()));
+
+ assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue());
+ assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Great steps"));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnSensedDataPointWhenManuallyEntered() {
+
+ assertThat(mapper.asDataPoints(singletonList(responseNode)).get(1).getHeader().getAcquisitionProvenance()
+ .getModality(), equalTo(SELF_REPORTED));
+ }
+
+ @Test
+ public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() {
+
+ JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json");
+
+ assertThat(mapper.asDataPoints(singletonList(emptyNode)), is(empty()));
+ }
+}
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity-no-steps.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity-no-steps.json
new file mode 100644
index 00000000..820e6a1b
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity-no-steps.json
@@ -0,0 +1,24 @@
+{
+ "ARDataList": [
+ {
+ "Calories": 1963,
+ "DataID": "de97222d4069d922b2ee8965d491c2e4",
+ "DataSource": "Manual",
+ "DistanceTraveled": 0,
+ "LastChangeTime": 1447826586,
+ "Lat": 0,
+ "Lon": 0,
+ "MDate": 1447800900,
+ "Note": "",
+ "Steps": 0,
+ "TimeZone": "-0700"
+ }
+ ],
+ "CurrentRecordCount": 1,
+ "DistanceUnit": 0,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 1
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity.json
new file mode 100644
index 00000000..dc056e80
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity.json
@@ -0,0 +1,37 @@
+{
+ "ARDataList": [
+ {
+ "Calories": 1571,
+ "DataID": "ac67c4ccf64af669d92569af85d19f59",
+ "DataSource": "FromDevice",
+ "DistanceTraveled": 0.01743,
+ "LastChangeTime": 1447788201,
+ "Lat": 0,
+ "Lon": 0,
+ "MDate": 1447698000,
+ "Note": "",
+ "Steps": 21,
+ "TimeZone": "+0500"
+ },
+ {
+ "Calories": 713,
+ "DataID": "a1c7ec7c4d75cf1fdb15e0d8acc4f83c",
+ "DataSource": "Manual",
+ "DistanceTraveled": 0,
+ "LastChangeTime": 1447827935,
+ "Lat": 0,
+ "Lon": 0,
+ "MDate": 1447831200,
+ "Note": "Great steps",
+ "Steps": 4398,
+ "TimeZone": "+0000"
+ }
+ ],
+ "CurrentRecordCount": 2,
+ "DistanceUnit": 0,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 2
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json
new file mode 100644
index 00000000..60ba0a06
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json
@@ -0,0 +1,37 @@
+{
+ "BGDataList": [
+ {
+ "BG": 60,
+ "DataID": "f706b6152f684c0e9185b1fa6b7c5148",
+ "DataSource": "FromDevice",
+ "DinnerSituation": "Before_breakfast",
+ "DrugSituation": "Before_taking_pills",
+ "LastChangeTime": 1442520221,
+ "Lat": -1,
+ "Lon": -1,
+ "MDate": 1442491407,
+ "Note": "Such glucose, much blood.",
+ "TimeZone": "-0800"
+ },
+ {
+ "BG": 70,
+ "DataID": "b0d61f8df4214f938a8d05d0ac583baa",
+ "DataSource": "Manual",
+ "DinnerSituation": "After_breakfast",
+ "DrugSituation": "After_taking_pills",
+ "LastChangeTime": 1443127602,
+ "Lat": -1,
+ "Lon": -1,
+ "MDate": 1443105880,
+ "Note": "",
+ "TimeZone": "-0600"
+ }
+ ],
+ "BGUnit": 0,
+ "CurrentRecordCount": 3,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 3
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json
new file mode 100644
index 00000000..a5cf36fa
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json
@@ -0,0 +1,22 @@
+{
+ "BODataList": [
+ {
+ "BO": 99,
+ "DataID": "d7fb9db14b0fc3e8e1635720c28bda64",
+ "DataSource": "FromDevice",
+ "HR": 0,
+ "LastChangeTime": 1443044760,
+ "Lat": 0,
+ "Lon": 0,
+ "MDate": 1443023160,
+ "Note": "",
+ "TimeZone": "-0600"
+ }
+ ],
+ "CurrentRecordCount": 1,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 1
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json
new file mode 100644
index 00000000..e35aef3f
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json
@@ -0,0 +1,34 @@
+{
+ "BODataList": [
+ {
+ "BO": 99,
+ "DataID": "d7fb9db14b0fc3e8e1635720c28bda64",
+ "DataSource": "FromDevice",
+ "HR": 80,
+ "LastChangeTime": 1443044760,
+ "Lat": 0,
+ "Lon": 0,
+ "MDate": 1443023160,
+ "Note": "",
+ "TimeZone": "-0600"
+ },
+ {
+ "BO": 90,
+ "DataID": "217e56a104cf8462f4977fd8bf743ca5",
+ "DataSource": "Manual",
+ "HR": 65,
+ "LastChangeTime": 1443128580,
+ "Lat": 0,
+ "Lon": 0,
+ "MDate": 1443106980,
+ "Note": "Satch on satch ",
+ "TimeZone": "-0600"
+ }
+ ],
+ "CurrentRecordCount": 2,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 2
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json
new file mode 100644
index 00000000..c22dd10c
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json
@@ -0,0 +1,26 @@
+{
+ "BPDataList": [
+ {
+ "BPL": 3,
+ "DataID": "d6c08fb0bde6dc50db5c5f305a76c455",
+ "DataSource": "Manual",
+ "HP": 130,
+ "HR": 0,
+ "IsArr": 0,
+ "LP": 95,
+ "LastChangeTime": 1442520465,
+ "Lat": 0,
+ "Lon": 0,
+ "MDate": 1442498865,
+ "Note": "BP on the up and up.",
+ "TimeZone": "-0600"
+ }
+ ],
+ "BPUnit": 0,
+ "CurrentRecordCount": 1,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 1
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json
new file mode 100644
index 00000000..20fa5f72
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json
@@ -0,0 +1,41 @@
+{
+ "BPDataList": [
+ {
+ "BPL": 3,
+ "DataID": "c62b84d9d4b7480a8ff2aef1465aa454",
+ "DataSource": "FromDevice",
+ "HP": 120,
+ "HR": 100,
+ "IsArr": -1,
+ "LP": 90,
+ "LastChangeTime": 1442520270,
+ "Lat": -1,
+ "Lon": -1,
+ "MDate": 1442491463,
+ "Note": "",
+ "TimeZone": "-0800"
+ },
+ {
+ "BPL": 3,
+ "DataID": "d6c08fb0bde6dc50db5c5f305a76c455",
+ "DataSource": "Manual",
+ "HP": 130,
+ "HR": 75,
+ "IsArr": 0,
+ "LP": 95,
+ "LastChangeTime": 1442520465,
+ "Lat": 0,
+ "Lon": 0,
+ "MDate": 1442498865,
+ "Note": "BP on the up and up.",
+ "TimeZone": "-0600"
+ }
+ ],
+ "BPUnit": 0,
+ "CurrentRecordCount": 5,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 5
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json
new file mode 100644
index 00000000..452fbf9f
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json
@@ -0,0 +1,41 @@
+{
+ "CurrentRecordCount": 4,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 2,
+ "WeightDataList": [
+ {
+ "BMI": 22.56052563257619,
+ "BoneValue": 0,
+ "DCI": 2035.774872767189,
+ "DataID": "5fe5893c418b48cd8da7954f8b6c2f36",
+ "DataSource": "FromDevice",
+ "FatValue": 10,
+ "LastChangeTime": 1442520257,
+ "MDate": 1442491449,
+ "MuscaleValue": 0,
+ "Note": "",
+ "TimeZone": "-0800",
+ "WaterValue": 25,
+ "WeightValue": 77.5643875134944
+ },
+ {
+ "BMI": 22.56052398681641,
+ "BoneValue": 0,
+ "DCI": 0,
+ "DataID": "b702a3a5e998f2fca268df6daaa69871",
+ "DataSource": "Manual",
+ "FatValue": 0,
+ "LastChangeTime": 1442520480,
+ "MDate": 1442498877,
+ "MuscaleValue": 0,
+ "Note": "Weight so good, look at me now",
+ "TimeZone": "-0600",
+ "WaterValue": 0,
+ "WeightValue": 77.56438446044922
+ }
+ ],
+ "WeightUnit": 0
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json
new file mode 100644
index 00000000..eabb67c7
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json
@@ -0,0 +1,10 @@
+{
+ "ARDataList": [],
+ "CurrentRecordCount": 0,
+ "DistanceUnit": 0,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 0
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json
new file mode 100644
index 00000000..895db9cb
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json
@@ -0,0 +1,9 @@
+{
+ "BODataList": [],
+ "CurrentRecordCount": 0,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 0
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json
new file mode 100644
index 00000000..138bb791
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json
@@ -0,0 +1,10 @@
+{
+ "BPDataList": [],
+ "BPUnit": 0,
+ "CurrentRecordCount": 0,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 0
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json
new file mode 100644
index 00000000..765b8503
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json
@@ -0,0 +1,10 @@
+{
+ "CurrentRecordCount": 0,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 0,
+ "WeightDataList": [],
+ "WeightUnit": 0
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json
new file mode 100644
index 00000000..1f97ef02
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json
@@ -0,0 +1,10 @@
+{
+ "BGDataList": [],
+ "BGUnit": 0,
+ "CurrentRecordCount": 0,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 0
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json
new file mode 100644
index 00000000..9b372102
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json
@@ -0,0 +1,9 @@
+{
+ "CurrentRecordCount": 0,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 0,
+ "SRDataList": []
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json
new file mode 100644
index 00000000..33b065b7
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json
@@ -0,0 +1,9 @@
+{
+ "CurrentRecordCount": 0,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 0,
+ "SPORTDataList": []
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json
new file mode 100644
index 00000000..89955bb3
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json
@@ -0,0 +1,26 @@
+{
+ "CurrentRecordCount": 4,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 1,
+ "WeightDataList": [
+ {
+ "BMI": 0,
+ "BoneValue": 0,
+ "DCI": 0,
+ "DataID": "b702a3a5e998f2fca268df6daaa69871",
+ "DataSource": "Manual",
+ "FatValue": 0,
+ "LastChangeTime": 1442520480,
+ "MDate": 1442498877,
+ "MuscaleValue": 0,
+ "Note": "Weight so good, look at me meow",
+ "TimeZone": "-0600",
+ "WaterValue": 0,
+ "WeightValue": 0
+ }
+ ],
+ "WeightUnit": 0
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sleep.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sleep.json
new file mode 100644
index 00000000..4a553fb7
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sleep.json
@@ -0,0 +1,55 @@
+{
+ "CurrentRecordCount": 3,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 3,
+ "SRDataList": [
+ {
+ "Awaken": 13,
+ "DataID": "7eb7292b90d710ae7b7f61b75f9425cf",
+ "DataSource": "FromDevice",
+ "EndTime": 1447578960,
+ "FallSleep": 0,
+ "HoursSlept": 345,
+ "LastChangeTime": 1447604350,
+ "Lat": 0,
+ "Lon": 0,
+ "Note": "",
+ "SleepEfficiency": 77,
+ "StartTime": 1447552260,
+ "TimeZone": "-0700"
+ },
+ {
+ "Awaken": 2,
+ "DataID": "34aa1c5351f965dbb6a09f3c5b730e83",
+ "DataSource": "FromDevice",
+ "EndTime": 1447607760,
+ "FallSleep": 0,
+ "HoursSlept": 195,
+ "LastChangeTime": 1447689288,
+ "Lat": 0,
+ "Lon": 0,
+ "Note": "Best sleep ever",
+ "SleepEfficiency": 95,
+ "StartTime": 1447595460,
+ "TimeZone": "+0100"
+ },
+ {
+ "Awaken": 0,
+ "DataID": "ed650c323ebd78e444430291675bd2fd",
+ "DataSource": "Manual",
+ "EndTime": 1447657920,
+ "FallSleep": 0,
+ "HoursSlept": 355,
+ "LastChangeTime": 1447689288,
+ "Lat": 0,
+ "Lon": 0,
+ "Note": "",
+ "SleepEfficiency": 87,
+ "StartTime": 1447633620,
+ "TimeZone": "-0700"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json
new file mode 100644
index 00000000..2744bc8a
--- /dev/null
+++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json
@@ -0,0 +1,34 @@
+{
+ "CurrentRecordCount": 2,
+ "NextPageUrl": "",
+ "PageLength": 50,
+ "PageNumber": 1,
+ "PrevPageUrl": "",
+ "RecordCount": 2,
+ "SPORTDataList": [
+ {
+ "Calories": 221.5,
+ "DataID": "3f8770f51cc84957a57d20f4fee1f34b",
+ "DataSource": "FromDevice",
+ "LastChangeTime": 1442520177,
+ "Lat": -1,
+ "Lon": -1,
+ "SportEndTime": 1442521948,
+ "SportName": "Swimming, breaststroke",
+ "SportStartTime": 1442520148,
+ "TimeZone": -8
+ },
+ {
+ "Calories": 202.5,
+ "DataID": "4bd1fdab8bab4e15950b7a95c1fff7ea",
+ "DataSource": "Manual",
+ "LastChangeTime": 1443127405,
+ "Lat": -1,
+ "Lon": -1,
+ "SportEndTime": 1442956383,
+ "SportName": "Running",
+ "SportStartTime": 1442954583,
+ "TimeZone": 1
+ }
+ ]
+}
\ No newline at end of file