Skip to content

Commit

Permalink
fix: fix determination of read/write endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdlg committed Nov 22, 2018
1 parent 5a3e287 commit a753ef8
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 7 deletions.
17 changes: 10 additions & 7 deletions lib/get-client.js
Expand Up @@ -31,28 +31,30 @@ const getThrottler = memoize((rate, globalThrottler) =>
/**
* Determine if a call to a client function will trigger a read (`GET`) or a write (`POST`, `PATCH`, etc...) request.
*
* @param {String} limitKey The key to find the limit rate for the API endpoint and method.
* @param {String} endpoint The client API enpoint (for example the endpoint for a call to `github.repos.get` is `repos`).
* @param {String} command The client API command (for example the command for a call to `github.repos.get` is `get`).
*
* @return {String} `write` or `read` if there is rate limit configuration for this `endpoint` and `command`, `undefined` otherwise.
*/
const getAccess = (endpoint, command) => {
const getAccess = (limitKey, endpoint, command) => {
const method = GH_ROUTES[endpoint] && GH_ROUTES[endpoint][command] && GH_ROUTES[endpoint][command].method;
const access = method && method === 'GET' ? 'read' : 'write';
return RATE_LIMITS[endpoint][access] && access;
return RATE_LIMITS[limitKey][access] && access;
};

/**
* Get the limiter identifier associated with a client API call.
*
* @param {String} limitKey The key to find the limit rate for the API endpoint and method.
* @param {String} endpoint The client API enpoint (for example the endpoint for a call to `github.repos.get` is `repos`).
* @param {String} command The client API command (for example the command for a call to `github.repos.get` is `get`).
*
* @return {String} A string identifying the limiter to use for this `endpoint` and `command` (e.g. `search` or `core.write`).
*/
const getLimitKey = (endpoint, command) => {
return endpoint
? [endpoint, RATE_LIMITS[endpoint] && getAccess(endpoint, command)].filter(Boolean).join('.')
const getLimitKey = (limitKey, endpoint, command) => {
return limitKey
? [limitKey, RATE_LIMITS[limitKey] && getAccess(limitKey, endpoint, command)].filter(Boolean).join('.')
: RATE_LIMITS[command]
? command
: 'core';
Expand All @@ -65,10 +67,11 @@ const getLimitKey = (endpoint, command) => {
*
* @param {Throttler} globalThrottler The throller function for the global rate limit.
* @param {String} limitKey The key to find the limit rate for the API endpoint and method.
* @param {String} endpoint The client API enpoint (for example the endpoint for a call to `github.repos.get` is `repos`).
*
* @return {Function} The `handler` for a `Proxy` wrapping an Octokit instance.
*/
const handler = (globalThrottler, limitKey) => ({
const handler = (globalThrottler, limitKey, endpoint) => ({
/**
* If the target has the property as own, determine the rate limit based on the property name and recursively wrap the value in a `Proxy`. Otherwise returns the property value.
*
Expand All @@ -80,7 +83,7 @@ const handler = (globalThrottler, limitKey) => ({
*/
get: (target, name, receiver) =>
Reflect.apply(Object.prototype.hasOwnProperty, target, [name])
? new Proxy(target[name], handler(globalThrottler, getLimitKey(limitKey, name)))
? new Proxy(target[name], handler(globalThrottler, getLimitKey(limitKey, endpoint, name), endpoint || name))
: Reflect.get(target, name, receiver),

/**
Expand Down
22 changes: 22 additions & 0 deletions test/get-client.test.js
Expand Up @@ -163,6 +163,28 @@ test('Use the same throttler for endpoints in the same rate limit group', async
t.true(inRange(f - e, searchRate - 50, searchRate + 50));
});

test('Use different throttler for read and write endpoints', async t => {
const createRelease = stub().callsFake(() => Date.now());
const get = stub().callsFake(() => Date.now());
const octokit = {repos: {createRelease, get}, authenticate: stub()};
const writeRate = 300;
const readRate = 150;
const github = proxyquire('../lib/get-client', {
'@octokit/rest': stub().returns(octokit),
'./definitions/rate-limit': {RATE_LIMITS: {core: {write: writeRate, read: readRate}}, GLOBAL_RATE_LIMIT: 1},
})();

const a = await github.repos.get();
const b = await github.repos.get();
const c = await github.repos.createRelease();
const d = await github.repos.createRelease();

// `repos.get` should be called `readRate` ms after `repos.get`
t.true(inRange(b - a, readRate - 50, readRate + 50));
// `repos.createRelease` should be called `coreRate` ms after `repos.createRelease`
t.true(inRange(d - c, writeRate - 50, writeRate + 50));
});

test('Use the same throttler when retrying', async t => {
// eslint-disable-next-line require-await
const createRelease = stub().callsFake(async () => {
Expand Down

0 comments on commit a753ef8

Please sign in to comment.