session["end"].nil? was the active signal since this was first built, on the assumption Garmin leaves it unset until the activity actually stops. Checked directly against a real still-recording session today (2026-09-20): end was already non-nil, set to exactly start + 24h — the share link's own expiry, not an end time — so every currently-active session was showing as "Ended". A genuinely ended session's end does hold its real end time (confirmed against the 2026-09-06 session this was originally, and only, verified against), which is what made the bug easy to miss. Recency of the last track point is a directly-observable signal instead of reverse-engineering what a given `end` value means: Garmin posts one roughly every postTrackPointFrequency seconds (~15, observed) while genuinely still recording.
78 lines
4.1 KiB
Ruby
78 lines
4.1 KiB
Ruby
require_relative "test_helper"
|
|
require "maptool/livetrack_client"
|
|
|
|
# No real network calls here — Garmin's page format was verified manually
|
|
# against real sessions during development; a synthetic fixture (matching
|
|
# the real page's structure: several `self.__next_f.push(...)` calls, one
|
|
# holding the track points nested under some data-fetching library's own
|
|
# structure) is what actually needs to keep working over time.
|
|
class LiveTrackClientTest < Minitest::Test
|
|
def setup
|
|
@client = Maptool::LiveTrackClient.new
|
|
end
|
|
|
|
FAKE_PAGE = <<~HTML.freeze
|
|
<html><body>
|
|
<script>self.__next_f.push([1,"6:[\\"$\\",\\"div\\",null,{}]"])</script>
|
|
<script>self.__next_f.push([1,"9:{\\"session\\":{\\"sessionName\\":\\"Test Ride\\",\\"start\\":\\"2026-01-01T00:00:00Z\\",\\"end\\":\\"2026-01-01T01:00:00Z\\"}}"])</script>
|
|
<script>self.__next_f.push([1,"10:{\\"unrelated\\":true,\\"nested\\":{\\"data\\":{\\"pages\\":[{\\"trackPoints\\":[{\\"position\\":{\\"lat\\":48.1,\\"lon\\":17.2},\\"dateTime\\":\\"2026-01-01T00:00:00Z\\",\\"totalDistanceMeters\\":\\"$undefined\\"},{\\"position\\":{\\"lat\\":48.2,\\"lon\\":17.3},\\"dateTime\\":\\"2026-01-01T00:01:00Z\\",\\"activityType\\":\\"CYCLING\\",\\"totalDistanceMeters\\":5000,\\"totalDurationSecs\\":600}]}]}}}"])</script>
|
|
</body></html>
|
|
HTML
|
|
|
|
def test_finds_track_points_and_session_across_separate_push_payloads
|
|
payloads = @client.send(:parse_payloads, FAKE_PAGE)
|
|
|
|
points = @client.send(:search, payloads) { |n| n["trackPoints"] if n["trackPoints"].is_a?(Array) }
|
|
assert_equal 2, points.size
|
|
assert_in_delta 48.1, points[0]["position"]["lat"]
|
|
|
|
session = @client.send(:search, payloads) { |n| n if n["sessionName"] }
|
|
assert_equal "Test Ride", session["sessionName"]
|
|
end
|
|
|
|
def test_undefined_sentinel_is_cleaned_to_nil
|
|
payloads = @client.send(:parse_payloads, FAKE_PAGE)
|
|
points = @client.send(:search, payloads) { |n| n["trackPoints"] if n["trackPoints"].is_a?(Array) }
|
|
cleaned = @client.send(:clean, points)
|
|
|
|
assert_nil cleaned[0]["totalDistanceMeters"]
|
|
end
|
|
|
|
def test_summarize_pulls_from_last_point_and_session
|
|
payloads = @client.send(:parse_payloads, FAKE_PAGE)
|
|
points = @client.send(:clean, @client.send(:search, payloads) { |n| n["trackPoints"] if n["trackPoints"].is_a?(Array) })
|
|
session = @client.send(:clean, @client.send(:search, payloads) { |n| n if n["sessionName"] })
|
|
result = Maptool::LiveTrackClient::Result.new(points: points, session: session)
|
|
|
|
summary = Maptool::LiveTrackClient.summarize(result)
|
|
|
|
assert_equal "Test Ride", summary["session_name"]
|
|
assert_equal "CYCLING", summary["activity_type"]
|
|
assert_equal 5000, summary["distance_meters"]
|
|
refute summary["active"], "last track point is long in the past"
|
|
end
|
|
|
|
# Not session["end"].nil? (the original signal, before 2026-09-20 — see
|
|
# LiveTrackClient's own comment): verified live against a real,
|
|
# still-recording session that day, Garmin had already set `end` to
|
|
# exactly start + 24h (the share link's own expiry, not an end time),
|
|
# so that check called an active session "ended".
|
|
def test_summarize_marks_active_when_last_point_is_recent
|
|
payloads = @client.send(:parse_payloads, FAKE_PAGE)
|
|
points = @client.send(:clean, @client.send(:search, payloads) { |n| n["trackPoints"] if n["trackPoints"].is_a?(Array) })
|
|
points[-1] = points[-1].merge("dateTime" => Time.now.utc.iso8601)
|
|
# end present and in the future, same shape Garmin actually sends for a
|
|
# session that's still live — active must come from the point, not this.
|
|
session = @client.send(:clean, @client.send(:search, payloads) { |n| n if n["sessionName"] })
|
|
.merge("end" => (Time.now.utc + 24 * 60 * 60).iso8601)
|
|
result = Maptool::LiveTrackClient::Result.new(points: points, session: session)
|
|
|
|
assert Maptool::LiveTrackClient.summarize(result)["active"]
|
|
end
|
|
|
|
def test_no_track_points_found_in_a_page_without_them
|
|
points = @client.send(:search, @client.send(:parse_payloads, "<html></html>")) { |n| n["trackPoints"] if n["trackPoints"].is_a?(Array) }
|
|
assert_nil points
|
|
end
|
|
end
|