fix(android): reject manual workouts ending in the future - #1067
fix(android): reject manual workouts ending in the future#1067TAKEOFF69 wants to merge 1 commit into
Conversation
ryanbr
left a comment
There was a problem hiding this comment.
The Android fix is clean — end > now → null with end-exactly-at-now staying valid is the right rule, and the tests pin all three edges (now, +1s, overflow). The overflow guard is only reachable by the synthetic Long.MAX case since durationMin is already bounded to 1..1440, which your comment nicely calls out.
One thing before merge though: the same bug exists on iOS and isn't fixed here. Strand/Data/WorkoutSource.swift buildManualRow guards start <= now but computes endTs = s + durationMin * 60 with no future-end check — and ManualWorkoutSheet only constrains the start (its DatePicker is in: ...Date(), duration goes to 24h). So start = now - 30s, duration = 45min still stores a workout ending ~44 min in the future on iOS — exactly the row this PR now rejects on Android.
That matters because ManualWorkoutSheet's own header says these inputs are "validated by WorkoutSource.buildManualRow (the same honest-row rules the engine uses)" — the intent is one shared rule across platforms, and a future-ending WorkoutRow feeds rescoreManualWorkouts / zones downstream. Could you mirror the guard into the Swift twin so the two stay in sync? It's basically:
let end = s + durationMin * 60
guard end <= Int(now.timeIntervalSince1970) else { return nil }(Swift Int is 64-bit and durationMin ≤ 1440, so no overflow guard needed there.) With that folded in, one PR fixes both platforms and I'm happy to merge. Thanks!
What this PR does
Reject manual workout rows whose computed end timestamp is later than the injected wall-clock
nowSeconds. The check also guards the start-plus-duration addition againstLongoverflow. A workout ending exactly atnowSecondsremains valid.Type of change
How it was tested
android/gradlew.bat testFullDebugUnitTest --no-daemon --dependency-verification=off --console=plain— 3,448 tests, 0 failures, 0 errors (5 pre-existing skips).android/gradlew.bat testFullDebugUnitTest --tests com.noop.ui.WorkoutEditingTest --no-daemon --dependency-verification=off --console=plain— 41 tests, 0 failures, 0 errors.android/gradlew.bat assembleFullDebug --no-daemon --dependency-verification=off --console=plain— passed.nowSeconds, one second beyondnowSeconds, andLong.MAX_VALUEaddition overflow.The local
--dependency-verification=offflag only bypasses the existing unpinned Windows AAPT2 artifact in this checkout; no verification metadata was changed. Standard CI runs the commands without that local workaround.Checklist
swift testinPackages/<name>)android/(./gradlew testFullDebugUnitTest)StrandDesigntokens — no hardcoded colors, fonts, or spacingdocs/CONTRIBUTING.mdStrand.xcodeproj/) or any secrets/keystoresRelated issues
Not applicable — this is a deterministic Android input-validation fix with focused regression coverage.