From 2ffb71b7f8c8ade06e088293776475247372f6a9 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Mon, 11 May 2026 02:21:12 -0700 Subject: [PATCH 1/3] fix(axios): map options.form to URL-encoded body in httpRequest (#196) Signed-off-by: SAY-5 --- axiosUtility.js | 17 +++++++++++++++++ test/pushSubscriptions.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/axiosUtility.js b/axiosUtility.js index 89aacf7..87aa5f1 100644 --- a/axiosUtility.js +++ b/axiosUtility.js @@ -107,6 +107,23 @@ const httpRequest = async (options) => { maxRedirects: options.maxRedirects === 0 ? 0 : options.maxRedirects || 5, validateStatus: options.simple === false ? () => true : defaultValidateStatus } + // request/request-promise's `form` option (URL-encoded body) is not + // understood by axios out of the box — map it to URLSearchParams data + // and the matching Content-Type so endpoints like push_subscriptions + // do not POST an empty body (#196). + if (options.form && options.body === undefined) { + const params = new URLSearchParams() + for (const [key, value] of Object.entries(options.form)) { + if (value !== undefined && value !== null) { + params.append(key, String(value)) + } + } + config.data = params + config.headers = { + ...config.headers, + 'Content-Type': 'application/x-www-form-urlencoded' + } + } const response = await axiosInstance(/** @type {import('axios').AxiosRequestConfig} */ (config)) if (options.resolveWithFullResponse) { return { headers: response.headers, body: response.data } diff --git a/test/pushSubscriptions.js b/test/pushSubscriptions.js index 31e7c1f..87566cf 100644 --- a/test/pushSubscriptions.js +++ b/test/pushSubscriptions.js @@ -108,6 +108,35 @@ describe('pushSubscriptions_test', function () { 'updated_at': '2015-04-29T18:11:09.400558047-07:00' }) }) + + // Regression test for #196: the axios migration dropped the + // `options.form` → request body translation, so `pushSubscriptions.create` + // POSTed an empty body. Verify the URL-encoded fields are actually sent. + it('should POST URL-encoded fields, not an empty body', async () => { + let capturedBody + nock('https://www.strava.com') + .post('/api/v3/push_subscriptions', function (body) { + capturedBody = body + return true + }) + .matchHeader('content-type', /application\/x-www-form-urlencoded/) + .once() + .reply(200, { id: 1 }) + + await strava.pushSubscriptions.create({ + 'callback_url': 'http://you.com/callback/', + 'verify_token': 'node-strava-v3' + }) + + assert.ok(capturedBody, 'POST body must not be empty') + // nock parses URL-encoded bodies into objects when Content-Type + // signals form data; assert on the resulting fields rather than the + // raw bytes so the test is independent of encoding details. + assert.strictEqual(capturedBody.callback_url, 'http://you.com/callback/') + assert.strictEqual(capturedBody.verify_token, 'node-strava-v3') + assert.strictEqual(capturedBody.client_id, 'test-client-id') + assert.strictEqual(capturedBody.client_secret, 'test-client-secret') + }) }) describe('#delete({id:...})', function () { From 0620fa379f8b13515fb182e4db19795def43cf3c Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Mon, 11 May 2026 12:59:59 -0700 Subject: [PATCH 2/3] chore: remove em-dashes from comments --- axiosUtility.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axiosUtility.js b/axiosUtility.js index 87aa5f1..48d1de7 100644 --- a/axiosUtility.js +++ b/axiosUtility.js @@ -108,7 +108,7 @@ const httpRequest = async (options) => { validateStatus: options.simple === false ? () => true : defaultValidateStatus } // request/request-promise's `form` option (URL-encoded body) is not - // understood by axios out of the box — map it to URLSearchParams data + // understood by axios out of the box, map it to URLSearchParams data // and the matching Content-Type so endpoints like push_subscriptions // do not POST an empty body (#196). if (options.form && options.body === undefined) { From 3750a784bc4bc5afd278d7ed2e5cd33601b0d30a Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Mon, 11 May 2026 17:53:01 -0700 Subject: [PATCH 3/3] trim verbose comments --- axiosUtility.js | 4 ---- test/pushSubscriptions.js | 6 ------ 2 files changed, 10 deletions(-) diff --git a/axiosUtility.js b/axiosUtility.js index 48d1de7..4eb2960 100644 --- a/axiosUtility.js +++ b/axiosUtility.js @@ -107,10 +107,6 @@ const httpRequest = async (options) => { maxRedirects: options.maxRedirects === 0 ? 0 : options.maxRedirects || 5, validateStatus: options.simple === false ? () => true : defaultValidateStatus } - // request/request-promise's `form` option (URL-encoded body) is not - // understood by axios out of the box, map it to URLSearchParams data - // and the matching Content-Type so endpoints like push_subscriptions - // do not POST an empty body (#196). if (options.form && options.body === undefined) { const params = new URLSearchParams() for (const [key, value] of Object.entries(options.form)) { diff --git a/test/pushSubscriptions.js b/test/pushSubscriptions.js index 87566cf..90e71c1 100644 --- a/test/pushSubscriptions.js +++ b/test/pushSubscriptions.js @@ -109,9 +109,6 @@ describe('pushSubscriptions_test', function () { }) }) - // Regression test for #196: the axios migration dropped the - // `options.form` → request body translation, so `pushSubscriptions.create` - // POSTed an empty body. Verify the URL-encoded fields are actually sent. it('should POST URL-encoded fields, not an empty body', async () => { let capturedBody nock('https://www.strava.com') @@ -129,9 +126,6 @@ describe('pushSubscriptions_test', function () { }) assert.ok(capturedBody, 'POST body must not be empty') - // nock parses URL-encoded bodies into objects when Content-Type - // signals form data; assert on the resulting fields rather than the - // raw bytes so the test is independent of encoding details. assert.strictEqual(capturedBody.callback_url, 'http://you.com/callback/') assert.strictEqual(capturedBody.verify_token, 'node-strava-v3') assert.strictEqual(capturedBody.client_id, 'test-client-id')