Skip to content

Commit 2ceca22

Browse files
committed
Add read aggregated data unit test
1 parent bb24df5 commit 2ceca22

8 files changed

Lines changed: 176 additions & 59 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@
1919
height="80">](obtainium://app/%7B%22id%22%3A%22com.rafapps.taskerhealthconnect%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2FRafhaanShah%2FTaskerHealthConnect%22%2C%22author%22%3A%22RafhaanShah%22%2C%22name%22%3A%22Tasker%20Health%20Connect%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22filterReleaseTitlesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22filterReleaseNotesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22verifyLatestTag%5C%22%3Afalse%2C%5C%22sortMethodChoice%5C%22%3A%5C%22date%5C%22%2C%5C%22useLatestAssetDateAsReleaseDate%5C%22%3Afalse%2C%5C%22releaseTitleAsVersion%5C%22%3Atrue%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Atrue%2C%5C%22releaseDateAsVersion%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22appName%5C%22%3A%5C%22TaskerHealthConnect%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22RafhaanShah%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%7D%22%7D)
2020

2121
## Usage
22-
- Caveat: this is not a beginner friendly plugin - it requires a fair amount of work and basic knowledge of JSON to use properly
22+
- Caveat: this is not a beginner friendly plugin - it requires a fair amount of work and basic knowledge of JSON and checking the Health Connect API to use properly
2323
- Run the app, it will check to make sure that Health Connect is installed and will prompt for required permissions
2424
- Open Tasker, and look for 'Tasker Health Connect' inside Action -> Plugins
2525
- All Input and Output is in JSON format, you can use Tasker variables and Tasker's built-in JSON processing or other plugins
2626
- For the expected format of the input and output, look at the documentation for the [Health Connect API](https://developer.android.com/reference/kotlin/androidx/health/connect/client/HealthConnectClient)
27+
- You can find sample input data inside the [unit test resources](app/src/test/resources/input)
2728

2829
## Building
2930
- Clone the repository: `git clone https://github.com/RafhaanShah/TaskerHealthConnect`
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package android.os.ext;
2+
3+
// used by AggregationTestUtils -> AggregationMappingsKt
4+
public class SdkExtensions {
5+
public static int getExtensionVersion(int extension) {
6+
return Integer.MAX_VALUE;
7+
}
8+
}

app/src/test/java/android/util/Log.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ object Log {
2727

2828
@JvmStatic
2929
fun e(tag: String, msg: String, t: Throwable): Int {
30-
println("ERROR: $tag: $msg, ${t.message}")
30+
println("ERROR: $tag: $msg, $t")
31+
t.printStackTrace()
3132
return 0
3233
}
3334
}

app/src/test/java/com/rafapps/taskerhealthconnect/TestData.kt

Lines changed: 75 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.rafapps.taskerhealthconnect
22

3+
import androidx.health.connect.client.aggregate.AggregateMetric
34
import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord
45
import androidx.health.connect.client.records.BasalBodyTemperatureRecord
56
import androidx.health.connect.client.records.BasalMetabolicRateRecord
@@ -10,16 +11,17 @@ import androidx.health.connect.client.records.BodyTemperatureRecord
1011
import androidx.health.connect.client.records.BodyWaterMassRecord
1112
import androidx.health.connect.client.records.BoneMassRecord
1213
import androidx.health.connect.client.records.CervicalMucusRecord
14+
import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
1315
import androidx.health.connect.client.records.DistanceRecord
1416
import androidx.health.connect.client.records.ElevationGainedRecord
1517
import androidx.health.connect.client.records.ExerciseCompletionGoal
1618
import androidx.health.connect.client.records.ExerciseLap
1719
import androidx.health.connect.client.records.ExercisePerformanceTarget
1820
import androidx.health.connect.client.records.ExerciseSegment
1921
import androidx.health.connect.client.records.ExerciseSessionRecord
20-
import androidx.health.connect.client.records.PlannedExerciseSessionRecord
2122
import androidx.health.connect.client.records.FloorsClimbedRecord
2223
import androidx.health.connect.client.records.HeartRateRecord
24+
import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
2325
import androidx.health.connect.client.records.HeightRecord
2426
import androidx.health.connect.client.records.HydrationRecord
2527
import androidx.health.connect.client.records.IntermenstrualBleedingRecord
@@ -30,6 +32,7 @@ import androidx.health.connect.client.records.NutritionRecord
3032
import androidx.health.connect.client.records.OvulationTestRecord
3133
import androidx.health.connect.client.records.OxygenSaturationRecord
3234
import androidx.health.connect.client.records.PlannedExerciseBlock
35+
import androidx.health.connect.client.records.PlannedExerciseSessionRecord
3336
import androidx.health.connect.client.records.PlannedExerciseStep
3437
import androidx.health.connect.client.records.PowerRecord
3538
import androidx.health.connect.client.records.Record
@@ -60,8 +63,11 @@ import androidx.health.connect.client.units.Volume
6063
import java.time.Duration
6164
import java.time.Instant
6265
import java.time.ZoneOffset
66+
import kotlin.random.Random
6367
import kotlin.reflect.KClass
6468
import kotlin.reflect.full.companionObject
69+
import kotlin.reflect.full.companionObjectInstance
70+
import kotlin.reflect.full.memberProperties
6571

6672
private val metadata = Metadata.manualEntry()
6773

@@ -139,7 +145,7 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
139145
)
140146

141147
BodyFatRecord::class -> BodyFatRecord(
142-
metadata = metadata
148+
time = time, zoneOffset = zoneOffset, percentage = percentage, metadata = metadata
143149
)
144150

145151
BodyTemperatureRecord::class -> BodyTemperatureRecord(
@@ -151,23 +157,15 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
151157
)
152158

153159
BodyWaterMassRecord::class -> BodyWaterMassRecord(
154-
time = time,
155-
zoneOffset = zoneOffset,
156-
mass = mass,
157160
time = time, zoneOffset = zoneOffset, mass = mass, metadata = metadata
158161
)
159162

160163
BoneMassRecord::class -> BoneMassRecord(
161-
time = time,
162-
zoneOffset = zoneOffset,
163-
mass = mass,
164-
metadata = metadata
164+
time = time, zoneOffset = zoneOffset, mass = mass, metadata = metadata
165165
)
166166

167167
CervicalMucusRecord::class -> CervicalMucusRecord(
168-
metadata = metadata,
169-
appearance = 0,
170-
sensation = 0
168+
time = time, zoneOffset = zoneOffset, metadata = metadata, appearance = 0, sensation = 0
171169
)
172170

173171
CyclingPedalingCadenceRecord::class -> CyclingPedalingCadenceRecord(
@@ -178,7 +176,7 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
178176
metadata = metadata,
179177
samples = listOf(
180178
CyclingPedalingCadenceRecord.Sample(
181-
revolutionsPerMinute = 8.9
179+
time = time, revolutionsPerMinute = 8.9
182180
)
183181
)
184182
)
@@ -210,9 +208,7 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
210208
exerciseType = 0,
211209
segments = listOf(
212210
ExerciseSegment(
213-
startTime = time,
214-
endTime = endTime,
215-
segmentType = 0,
211+
startTime = time, endTime = endTime, segmentType = 0, repetitions = 1
216212
)
217213
),
218214
laps = listOf(ExerciseLap(startTime = time, endTime = endTime, length = length)),
@@ -238,14 +234,17 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
238234
metadata = metadata,
239235
)
240236

241-
HeightRecord::class -> HeightRecord(
237+
HeartRateVariabilityRmssdRecord::class -> HeartRateVariabilityRmssdRecord(
242238
time = time,
243239
zoneOffset = zoneOffset,
244-
height = height,
240+
heartRateVariabilityMillis = 1.0,
245241
metadata = metadata
246242
)
247243

248244
HeightRecord::class -> HeightRecord(
245+
time = time, zoneOffset = zoneOffset, height = height, metadata = metadata
246+
)
247+
249248
HydrationRecord::class -> HydrationRecord(
250249
startTime = startTime,
251250
startZoneOffset = startZoneOffset,
@@ -256,20 +255,15 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
256255
)
257256

258257
IntermenstrualBleedingRecord::class -> IntermenstrualBleedingRecord(
259-
time = time,
260-
zoneOffset = zoneOffset,
261258
time = time, zoneOffset = zoneOffset, metadata = metadata
262259
)
263260

264261
LeanBodyMassRecord::class -> LeanBodyMassRecord(
265-
time = time,
266-
zoneOffset = zoneOffset,
267-
mass = mass,
268-
metadata = metadata
262+
time = time, zoneOffset = zoneOffset, mass = mass, metadata = metadata
269263
)
270264

271265
MenstruationFlowRecord::class -> MenstruationFlowRecord(
272-
flow = 1
266+
time = time, zoneOffset = zoneOffset, metadata = metadata, flow = 1
273267
)
274268

275269
MenstruationPeriodRecord::class -> MenstruationPeriodRecord(
@@ -333,16 +327,11 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
333327
)
334328

335329
OvulationTestRecord::class -> OvulationTestRecord(
336-
time = time,
337-
zoneOffset = zoneOffset,
338-
result = 1,
339-
metadata = metadata
330+
time = time, zoneOffset = zoneOffset, result = 1, metadata = metadata
340331
)
341332

342333
OxygenSaturationRecord::class -> OxygenSaturationRecord(
343-
zoneOffset = zoneOffset,
344-
percentage = percentage,
345-
metadata = metadata
334+
time = time, zoneOffset = zoneOffset, percentage = percentage, metadata = metadata
346335
)
347336

348337
PlannedExerciseSessionRecord::class -> PlannedExerciseSessionRecord(
@@ -353,14 +342,12 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
353342
metadata = metadata,
354343
blocks = listOf(
355344
PlannedExerciseBlock(
356-
repetitions = 1,
357-
steps = listOf(
345+
repetitions = 1, steps = listOf(
358346
PlannedExerciseStep(
359347
exerciseType = 0,
360348
exercisePhase = 0,
361349
completionGoal = ExerciseCompletionGoal.DistanceAndDurationGoal(
362-
distance = distance,
363-
duration = duration
350+
distance = distance, duration = duration
364351
),
365352
performanceTargets = listOf(
366353
ExercisePerformanceTarget.RateOfPerceivedExertionTarget(
@@ -369,7 +356,7 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
369356
),
370357
description = "description"
371358
)
372-
description = "description"
359+
), description = "description"
373360
)
374361
),
375362
exerciseType = 0,
@@ -391,15 +378,11 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
391378
)
392379

393380
RestingHeartRateRecord::class -> RestingHeartRateRecord(
394-
time = time,
395-
zoneOffset = zoneOffset,
396-
beatsPerMinute = 67,
397-
metadata = metadata
381+
time = time, zoneOffset = zoneOffset, beatsPerMinute = 67, metadata = metadata
398382
)
399383

400384
SexualActivityRecord::class -> SexualActivityRecord(
401-
metadata = metadata,
402-
protectionUsed = 1
385+
time = time, zoneOffset = zoneOffset, metadata = metadata, protectionUsed = 1
403386
)
404387

405388
SkinTemperatureRecord::class -> SkinTemperatureRecord(
@@ -423,7 +406,7 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
423406
notes = "notes",
424407
stages = listOf(
425408
SleepSessionRecord.Stage(
426-
stage = 1
409+
startTime = startTime, endTime = endTime, stage = 1
427410
)
428411
)
429412
)
@@ -473,10 +456,7 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
473456
)
474457

475458
WeightRecord::class -> WeightRecord(
476-
time = time,
477-
zoneOffset = zoneOffset,
478-
weight = weight,
479-
metadata = metadata
459+
time = time, zoneOffset = zoneOffset, weight = weight, metadata = metadata
480460
)
481461

482462
WheelchairPushesRecord::class -> WheelchairPushesRecord(
@@ -491,3 +471,50 @@ val testData: Map<KClass<out Record>, Record> = recordTypes.associateWith { kCla
491471
else -> throw IllegalArgumentException("Unsupported record type: $kClass")
492472
}
493473
}
474+
475+
data class AggregateTestData(
476+
val key: String,
477+
val metric: AggregateMetric<*>,
478+
val testData: Any
479+
)
480+
481+
val aggregateMetrics: List<AggregateTestData> = recordTypes.flatMap { clazz ->
482+
val companionInstance =
483+
runCatching { clazz.companionObjectInstance }.getOrNull() ?: return@flatMap emptyList()
484+
485+
runCatching {
486+
clazz.companionObject
487+
?.memberProperties
488+
?.filter { it.returnType.classifier == AggregateMetric::class }
489+
?.mapNotNull { member ->
490+
val value = member.call(companionInstance)
491+
val genericType = member.returnType.arguments.first().type?.classifier as KClass<*>
492+
if (value is AggregateMetric<*>) {
493+
AggregateTestData(
494+
key = "${clazz.simpleName}.${member.name}",
495+
metric = value,
496+
testData = generateTestData(genericType)
497+
).also { println(it) }
498+
} else null
499+
}
500+
}.getOrNull() ?: emptyList()
501+
}
502+
503+
private fun generateTestData(value: KClass<*>): Any {
504+
return when (value) {
505+
Int::class -> Random.nextInt(1, 100)
506+
Long::class -> Random.nextLong(1,100)
507+
Double::class -> Random.nextDouble(1.0, 100.0)
508+
Duration::class -> Duration.ofMinutes(Random.nextLong(1, 60))
509+
Energy::class -> Energy.calories(Random.nextDouble(1.0, 100.0))
510+
Length::class -> Length.meters(Random.nextDouble(1.0, 100.0))
511+
Mass::class -> Mass.kilograms(Random.nextDouble(1.0, 100.0))
512+
Power::class -> Power.watts(Random.nextDouble(1.0, 100.0))
513+
Volume::class -> Volume.liters(Random.nextDouble(1.0, 100.0))
514+
Pressure::class -> Pressure.millimetersOfMercury(Random.nextDouble(1.0, 100.0))
515+
Temperature::class -> Temperature.celsius(Random.nextDouble(1.0, 100.0))
516+
TemperatureDelta::class -> TemperatureDelta.celsius(Random.nextDouble(1.0, 100.0))
517+
Velocity::class -> Velocity.kilometersPerHour(Random.nextDouble(1.0, 100.0))
518+
else -> throw IllegalArgumentException("Unsupported Test Data Type: ${value.simpleName}")
519+
}
520+
}

app/src/test/java/com/rafapps/taskerhealthconnect/TestMapper.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.rafapps.taskerhealthconnect
22

3-
import androidx.health.connect.client.records.ExerciseRouteResult
43
import androidx.health.connect.client.units.BloodGlucose
54
import androidx.health.connect.client.units.Energy
65
import androidx.health.connect.client.units.Length
@@ -14,7 +13,6 @@ import androidx.health.connect.client.units.Velocity
1413
import androidx.health.connect.client.units.Volume
1514
import com.fasterxml.jackson.core.type.TypeReference
1615
import org.junit.Assert.assertEquals
17-
import org.junit.Assert.fail
1816
import java.time.Duration
1917
import java.time.Instant
2018
import java.time.ZoneOffset
@@ -49,6 +47,7 @@ class TestMapper {
4947
value as Map<String, Any>
5048
)
5149

50+
in mapKeys -> assertRecord(expected as Map<String, Any>, value as Map<String, Any>)
5251
in energyKeys -> assertEnergy(expected as Energy, value)
5352
in instantKeys -> assertEquals((expected as Instant).toEpochMilli(), value)
5453
in lengthKeys -> assertLength((expected as Length), value)
@@ -70,7 +69,6 @@ class TestMapper {
7069
is Int -> assertEquals(expected, (value as Number).toInt())
7170
is Long -> assertEquals(expected, (value as Number).toLong())
7271
else -> {
73-
println(key)
7472
assertEquals(expected, value)
7573
}
7674
}
@@ -296,13 +294,14 @@ class TestMapper {
296294
)
297295

298296
private val objectKeys = setOf("exerciseRouteResult", "exerciseRoute")
297+
private val mapKeys = setOf("longValues", "doubleValues")
299298
private val energyKeys = setOf("energy", "totalCalories", "energyFromFat")
300299
private val instantKeys = setOf("time", "startTime", "endTime")
301300
private val powerKeys = setOf("power", "minPower", "maxPower", "basalMetabolicRate")
302301
private val pressureKeys = setOf("systolic", "diastolic")
303302
private val temperatureKeys = setOf("temperature", "baseline")
304303
private val velocityKeys = setOf("speed", "minSpeed", "maxSpeed")
305304
private val zoneOffsetKeys = setOf("startZoneOffset", "endZoneOffset")
306-
private val ignoreKeys = setOf("metadata")
305+
private val ignoreKeys = setOf("metadata", "dataOrigins")
307306
}
308307
}

0 commit comments

Comments
 (0)