Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9dea8bd
chore: drop sinon as devDep: all mocks are now through nock.
markstos Sep 25, 2025
e73b93c
chore: update contributors and README test command
wesleyschlenker Sep 25, 2025
6edfcff
test: Start mocking all calls in test/client.js
markstos Sep 26, 2025
0284c1f
fixup: setting the env var was not required here.
markstos Sep 26, 2025
060b3ff
fix: ensure the rejection test cannot false-pass
wesleyschlenker Oct 8, 2025
dfa805d
fix: ensure the rejection test cannot false-pass
wesleyschlenker Oct 8, 2025
1fd376e
fix: update sportType to sport_type in activities test
wesleyschlenker Oct 8, 2025
f56a5b8
fix: duplicate line typo
wesleyschlenker Oct 8, 2025
ba91c85
lint: fs was loaded but not used.
markstos Oct 9, 2025
c859fe3
chore: remove dead code for deprecated "/running_races" endpoint.
markstos Oct 28, 2025
273e5dc
compate: There is no /athlete/routes endpoint
markstos Nov 3, 2025
719a737
compate: remove dead athletes/ endpoint
markstos Nov 3, 2025
5234340
compat: align stream endpoints with current API
markstos Nov 3, 2025
705e5f7
lint: lib/uploads.js
markstos Nov 3, 2025
555cb5a
bugFix: Quit attempting to parse non-JSON responses as JSON
markstos Nov 3, 2025
9a96489
bugFix: axios handling of "simple" option should now match what we di…
markstos Nov 3, 2025
d5ca3b1
chore: Update all tests to use mocks.
markstos Nov 3, 2025
319d82d
fixup: lint lib/stream.js
markstos Nov 3, 2025
4cfb1da
chore: simplify JSON parsing logic.
markstos Nov 3, 2025
a95ed10
fixup: simplify CI with basic testing, no annotations
markstos Nov 3, 2025
4ff744c
chore: slim deps by not requiring two devDeps for a script.
markstos Nov 4, 2025
2a32993
chire: remove dead es6-promise dep.
markstos Nov 4, 2025
adbd33f
deps: update mocha to address dependabot vuln
markstos Nov 4, 2025
d9296cf
chore: more test robust against parallel runs by using a tempdir for …
markstos Nov 4, 2025
c005ba6
fixup: linting
markstos Nov 4, 2025
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
29 changes: 29 additions & 0 deletions .github/workflows/on-pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Test Suite

on:
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x, 22.x]

steps:
- uses: actions/checkout@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: yarn

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Run test suite
run: yarn test
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Example usage:

```js
var strava = require('strava-v3');
strava.athletes.get({id:12345},function(err,payload,limits) {
strava.athlete.get({id:12345},function(err,payload,limits) {
//do something with your payload, track rate limits
});
```
Expand Down Expand Up @@ -256,26 +256,22 @@ See Strava API docs for returned data structures.
* `strava.athlete.get(args,done)`
* `strava.athlete.update(args,done)` // only 'weight' can be updated.
* `strava.athlete.listActivities(args,done)` *Get list of activity summaries*
* `strava.athlete.listRoutes(args,done)`
* `strava.athlete.listClubs(args,done)`
* `strava.athlete.listZones(args,done)`

#### Athletes

* `strava.athletes.get(args,done)` *Get a single activity. args.id is required*
* `strava.athletes.stats(args,done)`

#### Activities

* `strava.activities.get(args,done)`
* `strava.activities.create(args,done)`
* `strava.activities.update(args,done)`
* `strava.activities.listFriends(args,done)` -> deprecated at 2.2.0
* `strava.activities.listZones(args,done)`
* `strava.activities.listLaps(args,done)`
* `strava.activities.listComments(args,done)`
* `strava.activities.listKudos(args,done)`
* `strava.activities.listPhotos(args,done)` -> deprecated at 2.2.0

#### Clubs

Expand Down Expand Up @@ -380,14 +376,16 @@ This update maintains feature parity with the previous implementation of `reques
## Development

This package includes a full test suite runnable via `yarn test`.
It will both lint and run shallow tests on API endpoints.
It will both lint and run tests on API endpoints.

### Running the tests

You'll first need to supply `data/strava_config` with an `access_token` that
Many unit tests now use nock to mock the Strava API and can run without any real credentials.
However, some integration-style tests still expect a real token and account data.

If you want to run the full test suite (including integration tests), you'll need to supply `data/strava_config` with an `access_token` that
has both private read and write permissions. Look in `./scripts` for a tool
to help generate this token. Going forward we plan to more testing with a mocked
version of the Strava API so testing with real account credentials are not required.
to help generate this token.

* Make sure you've filled out all the fields in `data/strava_config`.
* Use `strava.oauth.getRequestAccessURL({scope:"view_private,write"})` to generate the request url and query it via your browser.
Expand All @@ -408,15 +406,17 @@ data in the account:
* Must have created at least one route
* Most recent activity with an achievement should also contain a segment

(Contributions to make the test suite more self-contained and robust by converting more tests
to use `nock` are welcome!)
(Parts of the test suite already use `nock` to mock the API. Contributions to convert remaining integration tests to mocks are welcome.)

* You're done! Paste the new `access_token` to `data/strava_config` and go run some tests:
You're done! Paste the new `access_token` to `data/strava_config` and run the full tests:

`yarn test`.

### How the tests work

- Tests use Mocha and Should.js.
- HTTP interaction is performed with Axios; tests that mock HTTP use `nock`.

Using the provided `access_token` tests will access each endpoint individually:

* (For all `GET` endpoints) checks to ensure the correct type has been returned from the Strava.
Expand Down
2 changes: 1 addition & 1 deletion axiosUtility.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const httpRequest = async (options) => {
data: options.body, // For request body
responseType: options.responseType || 'json', // Support different response types
maxRedirects: options.maxRedirects || 5, // Set max redirects
validateStatus: options.simple === false ? () => true : undefined // Handle 'simple' option
validateStatus: options.simple === false ? () => true : (status) => status >= 200 && status < 300 // Handle 'simple' option
})
return response.data
} catch (error) {
Expand Down
6 changes: 0 additions & 6 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,6 @@ export interface GearRoutes {
get(args: any, done?: Callback): Promise<any>;
}

export interface RunningRacesRoutes {
get(args: any, done?: Callback): Promise<any>;
listRaces(args: any, done?: Callback): Promise<any>;
}

export interface ClubsRoutes {
get(args: ClubsRoutesArgs, done?: Callback): Promise<any>;
listMembers(args: ClubsRoutesListArgs, done?: Callback): Promise<any>;
Expand Down Expand Up @@ -350,7 +345,6 @@ export interface Strava {
streams: StreamsRoutes;
uploads: UploadsRoutes;
rateLimiting: RateLimiting;
runningRaces: RunningRacesRoutes;
routes: RoutesRoutes;
oauth: OAuthRoutes;
}
Expand Down
3 changes: 0 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const SegmentEfforts = require('./lib/segmentEfforts')
const Streams = require('./lib/streams')
const Uploads = require('./lib/uploads')
const rateLimiting = require('./lib/rateLimiting')
const RunningRaces = require('./lib/runningRaces')
const Routes = require('./lib/routes')
const PushSubscriptions = require('./lib/pushSubscriptions')
const { axiosInstance, httpRequest } = require('./axiosUtility')
Expand Down Expand Up @@ -49,7 +48,6 @@ strava.client = function (token, request = httpRequest) {
this.streams = new Streams(httpClient)
this.uploads = new Uploads(httpClient)
this.rateLimiting = rateLimiting
this.runningRaces = new RunningRaces(httpClient)
this.routes = new Routes(httpClient)
// No Push subscriptions on the client object because they don't use OAuth.
}
Expand Down Expand Up @@ -77,7 +75,6 @@ strava.segmentEfforts = new SegmentEfforts(strava.defaultHttpClient)
strava.streams = new Streams(strava.defaultHttpClient)
strava.uploads = new Uploads(strava.defaultHttpClient)
strava.rateLimiting = rateLimiting
strava.runningRaces = new RunningRaces(strava.defaultHttpClient)
strava.routes = new Routes(strava.defaultHttpClient)
strava.pushSubscriptions = new PushSubscriptions(strava.defaultHttpClient)

Expand Down
3 changes: 0 additions & 3 deletions lib/athlete.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ athlete.prototype.listActivities = async function (args) {
athlete.prototype.listClubs = async function (args) {
return await this._listHelper('clubs', args)
}
athlete.prototype.listRoutes = async function (args) {
return await this._listHelper('routes', args)
}
athlete.prototype.listZones = async function (args) {
return await this._listHelper('zones', args)
}
Expand Down
3 changes: 0 additions & 3 deletions lib/athletes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ var athletes = function (client) {
}

//= ==== athletes endpoint =====
athletes.prototype.get = function (args, done) {
return this._listHelper('', args, done)
}
athletes.prototype.stats = function (args, done) {
return this._listHelper('stats', args, done)
}
Expand Down
25 changes: 12 additions & 13 deletions lib/httpClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,19 +188,18 @@ HttpClient.prototype._requestHelper = async function (options) {
}

if (typeof response === 'string') {
// parse the raw JSON string with big-integer reviver for 16+ digit numbers
return JSON.parse(response, (key, value) => {
if (options.responseType === 'json' || options.responseType === 'formdata') {
return JSON.parse(response, (key, value) => {
if (typeof value === 'string' && /^\d{16,}$/.test(value)) {
return JSONbig.parse(value)
}
return value
})
} else {
return response
}
})
// If responseType is 'text', return the raw string
if (options.responseType === 'text') {
return response
}

// For json or formdata, parse the raw JSON string with big-integer reviver for 16+ digit numbers
if (options.responseType === 'json' || options.responseType === 'formdata') {
return JSONbig.parse(response)
}

// Default: return as-is
return response
}

// response is not a string or object return it
Expand Down
28 changes: 0 additions & 28 deletions lib/runningRaces.js

This file was deleted.

13 changes: 6 additions & 7 deletions lib/streams.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ var streams = function (client) {
}

var _qsAllowedProps = [
'keys',
'key_by_type',
'original_size',
'resolution',
'series_type'
]
Expand Down Expand Up @@ -31,18 +34,14 @@ streams.prototype.route = function (args, done) {

//= ==== helpers =====
streams.prototype._typeHelper = function (endpoint, args, done) {
var qs = this.client.getQS(_qsAllowedProps, args)

// require id
if (typeof args.id === 'undefined') {
throw new Error('args must include an id')
}
// require types
if (typeof args.types === 'undefined') {
throw new Error('args must include types')
}

endpoint += '/' + args.id + '/streams/' + args.types + '?' + qs
const qs = this.client.getQS(_qsAllowedProps, args)

endpoint += '/' + args.id + '/streams' + '?' + qs
return this.client.getEndpoint(endpoint, args, done)
}
//= ==== helpers =====
Expand Down
3 changes: 1 addition & 2 deletions lib/uploads.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { setTimeout } = require('timers/promises');
const { setTimeout } = require('timers/promises')

var uploads = function (client) {
this.client = client
Expand Down Expand Up @@ -46,7 +46,6 @@ uploads.prototype.post = async function (args) {
access_token: args.access_token
}
return await self._check(checkArgs, args.statusCallback)

}

uploads.prototype._check = async function (args, cb) {
Expand Down
18 changes: 5 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
],
"author": "austin brown <[email protected]> (http://austinjamesbrown.com/)",
"contributors": [
"Mark Stosberg <[email protected]>"
"Mark Stosberg <[email protected]>",
"Wesley Schlenker <[email protected]>"
],
"license": "MIT",
"bugs": {
Expand All @@ -31,27 +32,18 @@
"json-bigint": "^1.0.0"
},
"devDependencies": {
"env-restorer": "^1.0.0",
"es6-promise": "^3.3.1",
"eslint": "^8.57.1",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-node": "^9.2.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-standard": "^4.1.0",
"inquirer": "^7.3.3",
"mocha": "^9.2.2",
"mock-fs": "^4.14.0",
"mocha": "11.7.4",
"nock": "^11.9.1",
"should": "^13.2.3",
"sinon": "^1.17.7",
"yargs": "^17.7.2"
"tmp": "^0.2.5"
},
"mocha": {
"globals": [
"should"
],
"timeout": 20000,
"timeout": 5000,
"checkLeaks": true,
"ui": "bdd",
"reporter": "spec"
Expand Down
Loading