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
16 changes: 11 additions & 5 deletions packages/v1-ready/hubspot/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,19 @@ class Api extends OAuth2Requester {
properties = await this._propertiesList('contact');
}

const query = { limit, properties };
// HubSpot rejects `after=null`/`after=` with a 400, so only send the
// paging cursor once we actually have one (i.e. from the second page
// onward). Without this guard, paging past the first page is impossible
// and callers are forced onto the search endpoint, which is hard-capped
// at 10,000 results.
if (after !== null && after !== undefined && after !== '') {
query.after = after;
}

const options = {
url: this.baseUrl + this.URLs.contacts,
query: {
limit,
after,
properties,
}
query,
};

return this._get(options);
Expand Down
50 changes: 50 additions & 0 deletions packages/v1-ready/hubspot/tests/listContacts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Unit tests for Api.listContacts pagination query-building.
*
* The module's other tests (api.test.js) are live-OAuth integration tests that
* require real HubSpot credentials. These are fast unit tests that stub the
* underlying `_get` so we can assert the request query without a network call.
*/
const { Api } = require('../api');

describe('Api.listContacts pagination', () => {
function makeApi() {
const api = new Api({});
api._get = jest.fn().mockResolvedValue({ results: [], paging: {} });
return api;
}

it('omits `after` from the query on the first page (no cursor)', async () => {
const api = makeApi();
await api.listContacts({ limit: 100, properties: ['email'] });

expect(api._get).toHaveBeenCalledTimes(1);
const { query } = api._get.mock.calls[0][0];
// HubSpot 400s on `after=null`; the first page must not send it at all.
expect(query).not.toHaveProperty('after');
expect(query.limit).toBe(100);
expect(query.properties).toEqual(['email']);
});

it('sends `after` from the second page onward', async () => {
const api = makeApi();
await api.listContacts({
after: '12345',
limit: 100,
properties: ['email'],
});

expect(api._get.mock.calls[0][0].query.after).toBe('12345');
});

it('treats null `after` as "no cursor"', async () => {
const api = makeApi();
await api.listContacts({
after: null,
limit: 100,
properties: ['email'],
});

expect(api._get.mock.calls[0][0].query).not.toHaveProperty('after');
});
});
Loading