Skip to content

Conversation

@markstos
Copy link
Collaborator

@markstos markstos commented Sep 25, 2025

Also, remove dead test function for getting an access token.

Summary by CodeRabbit

  • New Features
    • None
  • Documentation
    • Clarified test suite scope and running instructions; explained use of mocked API tests, integration-token requirements, and steps to run the full suite.
  • Tests
    • Converted tests to promise/async style; added broad activity and auth scenario coverage, per-test token handling, HTTP mocking, and setup/teardown for isolation.
  • Chores
    • Removed obsolete token-retrieval helper.
  • Contributors
    • Added a new contributor to project metadata.

Also, remove dead test function for getting an access token.
@coderabbitai
Copy link

coderabbitai bot commented Sep 25, 2025

Walkthrough

The PR shifts tests from callback/Sinon/real-API usage to promise/async style with nock HTTP mocks, removes the testsHelper.getAccessToken file-lookup, updates README test documentation, and adds a contributor in package.json. Activity and client tests now use per-test tokens and mocked Strava API responses.

Changes

Cohort / File(s) Summary
Docs
README.md
Reworded test-suite description (removed "shallow tests"); documented that many unit tests use nock to mock the Strava API; expanded instructions for obtaining an access_token (including ./scripts usage and auth-code exchange); clarified Mocha/Should.js/Axios testing details and integration-test prerequisites.
Metadata
package.json
Added contributor Wesley Schlenker; no runtime or dependency changes.
Test Helpers
test/_helper.js
Removed testsHelper.getAccessToken() (file/env lookup and try/catch); simplified imports and retained testsHelper exports.
Activity Tests (Promises + Nock)
test/activities.js
Converted tests from callback/Sinon to promise/async style; replaced real-API calls with nock mocks for POST/GET/PUT endpoints (create/get/update/updateSportType/listLaps/listComments/listKudos); added testActivity setup and afterEach nock cleanup; added Authorization header expectations and removed dependency on local authenticator helper.
Client Tests (Async + Nock)
test/client.js
Replaced file-based token loading with explicit per-test GOOD_TOKEN/BAD_TOKEN; introduced nock HTTP interception and afterEach cleanup; converted tests to async/await; added 401/200 mocks for /api/v3/athlete and updated assertions for error and success cases.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant T as Test (Mocha)
  participant C as Strava Client
  participant N as Nock (HTTP Mock)

  Note over T,N: Tests use promises/async and nock to intercept HTTP calls

  T->>C: call client.activities.create/get/update(...)\n(return Promise)
  C->>N: HTTP request (POST/GET/PUT) with Authorization header
  N-->>C: Mocked HTTP response (200 / 401)
  C-->>T: Promise resolves with payload or rejects with error

  alt Unmatched or missing mock
    C->>N: Unmatched HTTP request
    N-->>T: Error (test fails)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

A nibble of tests, a hop to mock,
I swap my yarn for npm stock.
Nock hums replies and promises sing,
Tokens snug, the mocked calls ring.
Thump-thump — rabbit cheers the testing spring. 🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly states the main change—removal of Sinon and migration of all mocks to Nock—and concisely summarizes the PR’s core intent.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch drop-sinon

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07b7d5c and c167b79.

📒 Files selected for processing (4)
  • README.md (2 hunks)
  • package.json (0 hunks)
  • test/_helper.js (0 hunks)
  • test/activities.js (3 hunks)
💤 Files with no reviewable changes (2)
  • package.json
  • test/_helper.js
🧰 Additional context used
🧬 Code graph analysis (1)
test/activities.js (3)
test/oauth.js (3)
  • nock (5-5)
  • strava (4-4)
  • payload (66-66)
index.js (2)
  • require (18-18)
  • strava (21-21)
lib/oauth.js (1)
  • require (2-2)

wesleyschlenker and others added 2 commits September 25, 2025 13:10
- Added Wesley Schlenker to the list of contributors.
- Updated README to reflect the use of `yarn test` instead of `npm test` for running the test suite.
So no live Strava API key is required.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e58773a and a80118e.

📒 Files selected for processing (1)
  • test/client.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/client.js (2)
test/oauth.js (4)
  • should (1-1)
  • nock (5-5)
  • strava (4-4)
  • payload (66-66)
index.js (2)
  • require (18-18)
  • strava (21-21)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
test/activities.js (3)

8-11: Assert mocks are consumed and prevent accidental live network.

  • Add nock.isDone() verification to catch unconsumed interceptors.
  • Disable outbound network in this suite and restore afterward to guard against real calls.

Apply this diff:

   before(function () {
     // Set a default test activity to use in tests
     testActivity = { id: 123456789, resource_state: 3, name: 'Sample Activity' }
+    // Prevent accidental live network in this suite (restored in `after`)
+    nock.disableNetConnect()
   })
   
-  afterEach(function () {
-    // Clean all nock interceptors after each test
-    nock.cleanAll()
-  })
+  afterEach(function () {
+    // Assert all expected HTTP mocks were used
+    const done = nock.isDone()
+    const pending = done ? [] : nock.pendingMocks()
+    nock.cleanAll()
+    if (!done) {
+      throw new Error('Not all expected HTTP mocks were called: ' + pending.join(', '))
+    }
+  })
+
+  after(function () {
+    // Restore network behavior for other suites
+    nock.enableNetConnect()
+  })

Also applies to: 13-16


50-66: Avoid inter-test coupling on testActivity.id.

These tests depend on the prior create() test mutating testActivity. Prefer per-test known IDs or reset state in a beforeEach to make tests independent.


68-83: Duplicate of the previous get() test.

This repeats the same Promise flow and assertions. Consider removing to keep the suite lean.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a80118e and 398563b.

📒 Files selected for processing (1)
  • test/activities.js (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/activities.js (2)
test/client.js (4)
  • nock (3-3)
  • require (4-4)
  • strava (5-5)
  • should (2-2)
test/oauth.js (4)
  • nock (5-5)
  • strava (4-4)
  • payload (66-66)
  • should (1-1)
🔇 Additional comments (5)
test/activities.js (5)

2-2: Adopting nock for HTTP mocking looks good.

Import and usage are consistent with the new mocking approach.


85-100: Good verification of explicit token handling.

Precise header match ensures access_token passthrough is tested.


111-120: Update flow mock and assertions look correct.

Covers both resource_state and name.

Also applies to: 121-125


28-41: Nice end-to-end create() mock. Consider stabilizing token source.

Right now, most tests match any Bearer token. To make tests fully self-contained, either:

  • pass an explicit access_token in the call and match exactly in nock, or
  • seed STRAVA_ACCESS_TOKEN only when unset and restore it after (suite-level).

If you prefer the env approach, verify whether any suite already sets STRAVA_ACCESS_TOKEN. You can run:

#!/bin/bash
# Inspect tests for env token handling
rg -n -C2 -g '!**/node_modules/**' -P 'process\.env\.STRAVA_ACCESS_TOKEN' test

Also applies to: 42-47


170-178: LGTM on laps/comments/kudos mocks.

Clean, minimal responses with header checks; combined with the isDone() assertion, these will be reliable.

Also applies to: 179-183, 187-194, 195-199, 205-210, 211-215

@markstos
Copy link
Collaborator Author

I think Code Rabbit flagged a real bug here, which is that we have docs and a code comment that says we reject non-2xx responses with an error of type StatusCodeError, but it appears we don't actually do so... but it's not as simple a fix as just starting to do so, because we also have code about updating rate limits... which suggests in fact maybe there are some rate-limited HTTP response codes which we should maybe also treat as success instead of a status code error?

Also, now we have >50% of the test suite running without a live Strava API key, which means we could set up Github Actions to have it at at least run the tests that pass with mocks, then we can continue to work on mock'ing the API until we can run 100% of the test suite on mocks.

Mocks have their downsides, but the reality is that often now we don't run the tests at all due to the live API key requirement.

I think with Nock there may be a way that once we are completely mocked, there may a way we can enable people to disable all the mocks globally and use a real API key if they want to.

wesleyschlenker and others added 2 commits October 8, 2025 10:45
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f186577 and 23235d3.

📒 Files selected for processing (1)
  • test/client.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/client.js (1)
test/oauth.js (4)
  • should (1-1)
  • nock (5-5)
  • strava (4-4)
  • payload (66-66)
🪛 Biome (2.1.2)
test/client.js

[error] 76-76: Expected a catch clause but instead found ')'.

Expected a catch clause here.

(parse)


[error] 78-78: expected a name for the function in a function declaration, but found none

(parse)

🔇 Additional comments (5)
test/client.js (5)

7-9: LGTM!

Explicit test tokens improve test isolation and eliminate file system dependencies.


12-15: LGTM!

The afterEach hook with nock.cleanAll() ensures proper cleanup of HTTP mocks between tests, preventing test interference.


20-35: LGTM!

The test correctly verifies that non-2xx responses are rejected with StatusCodeError. The should.fail() guard on Line 30 ensures the test cannot false-pass if the rejection doesn't occur.


37-56: LGTM!

The test correctly mocks a successful athlete data retrieval and validates the response payload structure.


78-99: LGTM once the syntax error is fixed.

This test correctly validates successful athlete data retrieval with a different mock response. It will function properly once the duplicate try statement on Line 67 is removed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
test/activities.js (2)

68-83: Remove redundant promise-based test.

Both #get() tests now use the same promise-based pattern after the Sinon-to-nock migration. The "(Promise API)" label is misleading since both tests are promise-based. Consider removing this duplicate test to reduce maintenance overhead.


19-215: Consider verifying nock interceptors were used.

While the tests correctly clean up nock with cleanAll(), they don't verify that all mocked requests were actually made. Calling scope.done() after each test assertion helps catch scenarios where expected HTTP calls never occurred.

Based on learnings

Example pattern for one test:

 it('should create an activity', function () {
   var args = {
     name: 'Most Epic Ride EVER!!!',
     elapsed_time: 18373,
     distance: 1557840,
     start_date_local: '2013-10-23T10:02:13Z',
     type: 'Ride'
   }

   // Mock the create activity API call
-  nock('https://www.strava.com')
+  const scope = nock('https://www.strava.com')
     .post('/api/v3/activities')
     .matchHeader('authorization', /Bearer .+/)
     .reply(201, {
       id: 987654321,
       resource_state: 3,
       name: 'Most Epic Ride EVER!!!',
       elapsed_time: 18373,
       distance: 1557840,
       start_date_local: '2013-10-23T10:02:13Z',
       type: 'Ride'
     })

   return strava.activities.create(args)
     .then(function (payload) {
       testActivity = payload;
       (payload.resource_state).should.be.exactly(3)
+      scope.done()
     })
 })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23235d3 and 177b329.

📒 Files selected for processing (1)
  • test/activities.js (3 hunks)
🔇 Additional comments (1)
test/activities.js (1)

8-16: LGTM! Proper test setup and cleanup.

The before hook initializes a concrete test activity, and the afterEach hook correctly cleans up nock interceptors between tests.

@markstos
Copy link
Collaborator Author

markstos commented Oct 9, 2025

@wesleyschlenker I get failures here for the athletes tests. Do they pass for you?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
test/client.js (5)

12-15: Consider strengthening mock isolation with network controls.

The afterEach cleanup is good, but tests could accidentally hit the real Strava API if nock mocks fail to match requests.

Add nock.disableNetConnect() in a beforeEach hook to ensure tests never make real network calls:

 describe('client_test', function () {
+  beforeEach(() => {
+    // Prevent any real network requests
+    nock.disableNetConnect()
+  })
+
   afterEach(() => {
     // Clean up after each test
     nock.cleanAll()
   })

Based on learnings: "Use nock.disableNetConnect() to ensure tests never make real network calls" and "Keep mocks isolated per-test and call nock.cleanAll() in test teardown."


20-35: Verify mock consumption to catch test logic errors.

The test correctly asserts error behavior, but doesn't verify the nock mock was actually consumed. If the mock fails to match, the test might hit unexpected code paths.

Capture the nock scope and verify it was consumed:

     it('Should reject promise with StatusCodeError for non-2xx response', async function () {
       // Mock athlete endpoint for BAD token -> 401
-      nock('https://www.strava.com')
+      const scope = nock('https://www.strava.com')
         .get('/api/v3/athlete')
         .matchHeader('authorization', 'Bearer ' + BAD_TOKEN)
         .reply(401, { message: 'Authorization Error', errors: [{ resource: 'Application', code: 'invalid' }] })

       const badClient = new strava.client(BAD_TOKEN)
       try {
         await badClient.athlete.get({})
         should.fail('Expected athlete.get to reject with StatusCodeError')
       } catch (err) {
         should(err).be.instanceOf(StatusCodeError)
         should(err.statusCode).equal(401)
       }
+
+      // Verify the mock was consumed
+      scope.done()
     })

Based on learnings: "Use scope.done() or nock.isDone() in teardown to assert expected requests were made."


37-56: Verify mock consumption for test correctness.

Same issue: without verifying the mock was consumed, the test could pass even if the mock wasn't used as expected.

Apply this pattern:

     it('should return detailed athlete information about athlete associated to access_token', function () {
       // Mock athlete endpoint for GOOD token -> 200
-      nock('https://www.strava.com')
+      const scope = nock('https://www.strava.com')
         .get('/api/v3/athlete')
         .matchHeader('authorization', 'Bearer ' + GOOD_TOKEN)
         .reply(200, {
           resource_state: 3,
           id: 12345,
           firstname: 'Test',
           lastname: 'User'
         })

       const client = new strava.client(GOOD_TOKEN)
       return client.athlete.get({})
         .then((payload) => {
           should(payload).be.ok()
           should(payload.resource_state).equal(3)
           should(payload.id).equal(12345)
+
+          // Verify the mock was consumed
+          scope.done()
         })
     })

58-75: Verify mock consumption.

     it('Should reject promise with StatusCodeError when using bad token', async function () {
       // Mock athlete endpoint for BAD token -> 401
       // Testing with a second interceptor to ensure nock works correctly
-      nock('https://www.strava.com')
+      const scope = nock('https://www.strava.com')
         .get('/api/v3/athlete')
         .matchHeader('authorization', 'Bearer ' + BAD_TOKEN)
         .reply(401, { message: 'Authorization Error' })

       const badClient = new strava.client(BAD_TOKEN)
       try {
         await badClient.athlete.get({})
         should.fail('Expected athlete.get to reject with StatusCodeError')
       } catch (err) {
         should(err).be.instanceOf(StatusCodeError)
         should(err.statusCode).equal(401)
         should(err.data.message).equal('Authorization Error')
       }
+
+      scope.done()
     })

77-98: Verify mock consumption.

     it('Should successfully return athlete data with valid token', function () {
       // Mock athlete endpoint for GOOD token -> 200
       // Testing a second successful request to verify client instances work correctly
-      nock('https://www.strava.com')
+      const scope = nock('https://www.strava.com')
         .get('/api/v3/athlete')
         .matchHeader('authorization', 'Bearer ' + GOOD_TOKEN)
         .reply(200, {
           resource_state: 3,
           id: 67890,
           firstname: 'Another',
           lastname: 'Athlete'
         })

       const client = new strava.client(GOOD_TOKEN)
       return client.athlete.get({})
         .then((payload) => {
           should(payload).be.ok()
           should(payload.resource_state).equal(3)
           should(payload.id).equal(67890)
           should(payload.firstname).equal('Another')
+
+          scope.done()
         })
     })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 177b329 and 506194a.

📒 Files selected for processing (2)
  • test/_helper.js (1 hunks)
  • test/client.js (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-26T14:09:38.947Z
Learnt from: markstos
PR: node-strava/node-strava-v3#173
File: README.md:390-392
Timestamp: 2025-09-26T14:09:38.947Z
Learning: The node-strava-v3 repository contains a script at `scripts/generate-access-token.js` that helps users generate access tokens for testing purposes.

Applied to files:

  • test/_helper.js
🧬 Code graph analysis (1)
test/client.js (1)
test/oauth.js (4)
  • should (1-1)
  • nock (5-5)
  • strava (4-4)
  • payload (66-66)
🔇 Additional comments (3)
test/client.js (2)

2-3: LGTM! Proper imports for nock-based testing.

The addition of should and nock imports supports the migration to promise-based mocking.


7-9: LGTM! Explicit test tokens improve test clarity.

Using explicit token constants instead of file-based loading makes tests more isolated and easier to understand.

test/_helper.js (1)

1-1: LGTM! Clean migration from file-based token loading; no remaining getAccessToken() references in test files.

@markstos
Copy link
Collaborator Author

markstos commented Oct 9, 2025

The Code Rabbit "nitpick" feedback here looks like a good quality improvement.

@wesleyschlenker
Copy link
Collaborator

wesleyschlenker commented Oct 10, 2025

@markstos I get failures starting in the athlete_test, but it doesn't look like those tests use mock api calls yet. I'll migrate the rest of the tests to the mock api later when I have some time. Feel free to push back, but I think getting to 100% mocked first is a better strategy than starting on a fresh CI pipeline. Theoretically, CI isn't even needed if everyone is religiously running their tests, so I like the idea of tackling that problem first.

I hadn't realized you've assigned this PR to me, so I will try to carve out some time later.

@markstos
Copy link
Collaborator Author

The tests should pass on this branch are the ones that are modified and mocked, which are test/client.js and test/actitivites.js, but I currently get test failures in the client test. Looks like we are now throwing errors a different way that the test suite is not expecting.

 2) client_test
       #athlete.get()
         Should reject promise with StatusCodeError when using bad token:
     AssertionError: expected Error {
  operator: undefined,
  expected: undefined,
  stackStartFunction: Function { name: 'fail' },
  actual: 'Expected athlete.get to reject with StatusCodeError',
  negate: false,
  assertion: Assertion {
    obj: 'Expected athlete.get to reject with StatusCodeError',
    anyOne: false,
    negate: false,
    params: Object {
      operator: undefined,
      expected: undefined,
      message: undefined,
      stackStartFunction: Function { name: 'fail' },
      actual: 'Expected athlete.get to reject with StatusCodeError',
      negate: false,
      assertion: [Circular]
    }
  }
} to be an instance of StatusCodeError

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants