Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions functions/strava.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const STRAVA_RECENT_ACTIVITY_LIMIT = 5;
const STRAVA_ACTIVITY_NAME_MAX_LENGTH = 1_024;
const STRAVA_ACTIVITY_TYPE_MAX_LENGTH = 128;
const STRAVA_ACTIVITY_DATE_MAX_LENGTH = 64;
const STRAVA_LONG_MAX_AS_NUMBER = 9_223_372_036_854_776_000;
const STRAVA_REFRESH_ERROR_MESSAGE = 'Strava connection could not be refreshed.';
const STRAVA_DATA_ERROR_MESSAGE = 'Strava activity data could not be loaded.';
const STRAVA_STATS_REQUEST_ERROR_MESSAGE = 'Strava statistics request is invalid.';
Expand All @@ -45,7 +44,6 @@ const arrayIsArray = Array.isArray;
const arrayPrototype = Array.prototype;
const arrayPush = Array.prototype.push;
const numberIsFinite = Number.isFinite;
const numberIsInteger = Number.isInteger;
const numberIsSafeInteger = Number.isSafeInteger;
const reflectApply = Reflect.apply;
const reflectHas = Reflect.has;
Expand Down Expand Up @@ -344,14 +342,6 @@ function isFiniteNumberInRange(value, maximum) {
&& value <= maximum;
}

function isPositiveIntegerInRange(value, maximum) {
return typeof value === 'number'
&& numberIsFinite(value)
&& numberIsInteger(value)
&& value > 0
&& value <= maximum;
}

function hasLoneSurrogate(value) {
for (let index = 0; index < value.length; index += 1) {
const codeUnit = reflectApply(stringCharCodeAt, value, [index]);
Expand Down Expand Up @@ -515,7 +505,7 @@ function snapshotProviderActivity(record) {
(value) => isBoundedVisibleAscii(value, STRAVA_ACTIVITY_DATE_MAX_LENGTH),
);
if (
!isPositiveIntegerInRange(id, STRAVA_LONG_MAX_AS_NUMBER)
!isPositiveSafeInteger(id)
|| !isBoundedActivityName(name)
|| type === INVALID_SELECTED_VALUE
|| !isFiniteNumberInRange(distance, Number.MAX_SAFE_INTEGER)
Expand Down
47 changes: 43 additions & 4 deletions functions/strava.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4890,6 +4890,45 @@ describe('Strava activity data failure boundary', () => {
});
});

describe('OAUTH-001A2H precision-safe activity IDs', () => {
test('rejects the first precision-unsafe integer before statistics JSON', async () => {
await expectInvalidSuccessfulProviderData({
activities: [validProviderActivity({
id: Number.MAX_SAFE_INTEGER + 1,
})],
invalidSurface: 'activities',
});
});

test('preserves the safe-integer ceiling as an exact numeric result', async () => {
seedFreshConnection();
const { activitiesJson, statsJson } = mockSuccessfulProviderData({
activities: [validProviderActivity({
id: Number.MAX_SAFE_INTEGER,
})],
});

const result = await stravaFetchStats({}, CONTEXT);

expect(result.recentActivities[0]).toEqual({
id: Number.MAX_SAFE_INTEGER,
name: 'Synthetic Morning Run',
type: 'Run',
distanceMeters: 5000.5,
movingTimeSeconds: 1500,
startDate: '2026-01-02T12:00:00Z',
});
expect(typeof result.recentActivities[0].id).toBe('number');
expect(activitiesJson).toHaveBeenCalledTimes(1);
expect(statsJson).toHaveBeenCalledTimes(1);
expectTwoFreshBearerReads();
expect(admin.__getReads()).toEqual([CONNECTION_PATH, SECRET_PATH]);
expect(admin.__getDeletes()).toEqual([]);
expect(Timestamp.now).not.toHaveBeenCalled();
expectNoWritesOrLogs();
});
});

test.each([
['id', 987654],
['name', 'Synthetic Morning Run'],
Expand Down Expand Up @@ -4961,11 +5000,11 @@ describe('Strava activity data failure boundary', () => {
expectNoWritesOrLogs();
});

test('accepts exact bounds, five dense entries, and the rounded signed-Long ceiling', async () => {
test('accepts exact bounds, five dense entries, and the safe-integer activity ceiling', async () => {
seedFreshConnection();
const longId = MAX_STRAVA_LONG_AS_NUMBER;
const safeId = Number.MAX_SAFE_INTEGER;
const activities = Array.from({ length: 5 }, (_unused, index) => validProviderActivity({
id: index === 0 ? longId : index + 1,
id: index === 0 ? safeId : index + 1,
name: index === 0 ? '🏃'.repeat(MAX_ACTIVITY_NAME_LENGTH / 2) : `Run ${index}`,
type: index === 0 ? 't'.repeat(MAX_ACTIVITY_TYPE_LENGTH) : undefined,
sport_type: index === 0 ? 'ignored' : 'Run',
Expand All @@ -4979,7 +5018,7 @@ describe('Strava activity data failure boundary', () => {

expect(result.recentActivities).toHaveLength(5);
expect(result.recentActivities[0]).toEqual({
id: longId,
id: safeId,
name: '🏃'.repeat(MAX_ACTIVITY_NAME_LENGTH / 2),
type: 't'.repeat(MAX_ACTIVITY_TYPE_LENGTH),
distanceMeters: Number.MAX_SAFE_INTEGER,
Expand Down