mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-14 18:43:31 +01:00
feat(rest): use undici (#7747)
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com> Co-authored-by: ckohen <chaikohen@gmail.com>
This commit is contained in:
@@ -1,10 +1,38 @@
|
||||
import nock from 'nock';
|
||||
import { DefaultRestOptions, DiscordAPIError, HTTPError, RateLimitError, REST, RESTEvents } from '../src';
|
||||
import { MockAgent, setGlobalDispatcher } from 'undici';
|
||||
import type { Interceptable, MockInterceptor } from 'undici/types/mock-interceptor';
|
||||
import { genPath } from './util';
|
||||
import { DiscordAPIError, HTTPError, RateLimitError, REST, RESTEvents } from '../src';
|
||||
|
||||
let mockAgent: MockAgent;
|
||||
let mockPool: Interceptable;
|
||||
|
||||
const api = new REST({ timeout: 2000, offset: 5 }).setToken('A-Very-Fake-Token');
|
||||
const invalidAuthApi = new REST({ timeout: 2000 }).setToken('Definitely-Not-A-Fake-Token');
|
||||
const rateLimitErrorApi = new REST({ rejectOnRateLimit: ['/channels'] }).setToken('Obviously-Not-A-Fake-Token');
|
||||
|
||||
beforeEach(() => {
|
||||
mockAgent = new MockAgent();
|
||||
mockAgent.disableNetConnect();
|
||||
setGlobalDispatcher(mockAgent);
|
||||
|
||||
mockPool = mockAgent.get('https://discord.com');
|
||||
api.setAgent(mockAgent);
|
||||
invalidAuthApi.setAgent(mockAgent);
|
||||
rateLimitErrorApi.setAgent(mockAgent);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await mockAgent.close();
|
||||
});
|
||||
|
||||
// @discordjs/rest uses the `content-type` header to detect whether to parse
|
||||
// the response as JSON or as an ArrayBuffer.
|
||||
const responseOptions: MockInterceptor.MockResponseOptions = {
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
let resetAfter = 0;
|
||||
let sublimitResetAfter = 0;
|
||||
let retryAfter = 0;
|
||||
@@ -13,7 +41,10 @@ let sublimitHits = 0;
|
||||
let serverOutage = true;
|
||||
let unexpected429 = true;
|
||||
let unexpected429cf = true;
|
||||
const sublimitIntervals = {
|
||||
const sublimitIntervals: {
|
||||
reset: NodeJS.Timer | null;
|
||||
retry: NodeJS.Timer | null;
|
||||
} = {
|
||||
reset: null,
|
||||
retry: null,
|
||||
};
|
||||
@@ -40,187 +71,16 @@ function startSublimitIntervals() {
|
||||
}
|
||||
}
|
||||
|
||||
nock(`${DefaultRestOptions.api}/v${DefaultRestOptions.version}`)
|
||||
.persist()
|
||||
.replyDate()
|
||||
.get('/standard')
|
||||
.times(3)
|
||||
.reply((): nock.ReplyFnResult => {
|
||||
const response = Date.now() >= resetAfter ? 204 : 429;
|
||||
resetAfter = Date.now() + 250;
|
||||
if (response === 204) {
|
||||
return [
|
||||
204,
|
||||
undefined,
|
||||
{
|
||||
'x-ratelimit-limit': '1',
|
||||
'x-ratelimit-remaining': '0',
|
||||
'x-ratelimit-reset-after': ((resetAfter - Date.now()) / 1000).toString(),
|
||||
'x-ratelimit-bucket': '80c17d2f203122d936070c88c8d10f33',
|
||||
via: '1.1 google',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
429,
|
||||
{
|
||||
limit: '1',
|
||||
remaining: '0',
|
||||
resetAfter: (resetAfter / 1000).toString(),
|
||||
bucket: '80c17d2f203122d936070c88c8d10f33',
|
||||
retryAfter: (resetAfter - Date.now()).toString(),
|
||||
},
|
||||
{
|
||||
'x-ratelimit-limit': '1',
|
||||
'x-ratelimit-remaining': '0',
|
||||
'x-ratelimit-reset-after': ((resetAfter - Date.now()) / 1000).toString(),
|
||||
'x-ratelimit-bucket': '80c17d2f203122d936070c88c8d10f33',
|
||||
'retry-after': (resetAfter - Date.now()).toString(),
|
||||
via: '1.1 google',
|
||||
},
|
||||
];
|
||||
})
|
||||
.get('/triggerGlobal')
|
||||
.reply(
|
||||
(): nock.ReplyFnResult => [
|
||||
204,
|
||||
{ global: true },
|
||||
{
|
||||
'x-ratelimit-global': 'true',
|
||||
'retry-after': '1',
|
||||
via: '1.1 google',
|
||||
},
|
||||
],
|
||||
)
|
||||
.get('/regularRequest')
|
||||
.reply(204, { test: true })
|
||||
.patch('/channels/:id', (body) => ['name', 'topic'].some((key) => Reflect.has(body as Record<string, unknown>, key)))
|
||||
.reply((): nock.ReplyFnResult => {
|
||||
sublimitHits += 1;
|
||||
sublimitRequests += 1;
|
||||
const response = 2 - sublimitHits >= 0 && 10 - sublimitRequests >= 0 ? 204 : 429;
|
||||
startSublimitIntervals();
|
||||
if (response === 204) {
|
||||
return [
|
||||
204,
|
||||
undefined,
|
||||
{
|
||||
'x-ratelimit-limit': '10',
|
||||
'x-ratelimit-remaining': `${10 - sublimitRequests}`,
|
||||
'x-ratelimit-reset-after': ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
via: '1.1 google',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
429,
|
||||
{
|
||||
limit: '10',
|
||||
remaining: `${10 - sublimitRequests}`,
|
||||
resetAfter: (sublimitResetAfter / 1000).toString(),
|
||||
retryAfter: ((retryAfter - Date.now()) / 1000).toString(),
|
||||
},
|
||||
{
|
||||
'x-ratelimit-limit': '10',
|
||||
'x-ratelimit-remaining': `${10 - sublimitRequests}`,
|
||||
'x-ratelimit-reset-after': ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
'retry-after': ((retryAfter - Date.now()) / 1000).toString(),
|
||||
via: '1.1 google',
|
||||
},
|
||||
];
|
||||
})
|
||||
.patch('/channels/:id', (body) =>
|
||||
['name', 'topic'].every((key) => !Reflect.has(body as Record<string, unknown>, key)),
|
||||
)
|
||||
.reply((): nock.ReplyFnResult => {
|
||||
sublimitRequests += 1;
|
||||
const response = 10 - sublimitRequests >= 0 ? 204 : 429;
|
||||
startSublimitIntervals();
|
||||
if (response === 204) {
|
||||
return [
|
||||
204,
|
||||
undefined,
|
||||
{
|
||||
'x-ratelimit-limit': '10',
|
||||
'x-ratelimit-remaining': `${10 - sublimitRequests}`,
|
||||
'x-ratelimit-reset-after': ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
via: '1.1 google',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
429,
|
||||
{
|
||||
limit: '10',
|
||||
remaining: `${10 - sublimitRequests}`,
|
||||
resetAfter: (sublimitResetAfter / 1000).toString(),
|
||||
retryAfter: ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
},
|
||||
{
|
||||
'x-ratelimit-limit': '10',
|
||||
'x-ratelimit-remaining': `${10 - sublimitRequests}`,
|
||||
'x-ratelimit-reset-after': ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
'retry-after': ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
via: '1.1 google',
|
||||
},
|
||||
];
|
||||
})
|
||||
.get('/unexpected')
|
||||
.times(3)
|
||||
.reply((): nock.ReplyFnResult => {
|
||||
if (unexpected429) {
|
||||
unexpected429 = false;
|
||||
return [
|
||||
429,
|
||||
undefined,
|
||||
{
|
||||
'retry-after': '1',
|
||||
via: '1.1 google',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [204, { test: true }];
|
||||
})
|
||||
.get('/unexpected-cf')
|
||||
.times(2)
|
||||
.reply((): nock.ReplyFnResult => {
|
||||
if (unexpected429cf) {
|
||||
unexpected429cf = false;
|
||||
return [
|
||||
429,
|
||||
undefined,
|
||||
{
|
||||
'retry-after': '1',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [204, { test: true }];
|
||||
})
|
||||
.get('/temp')
|
||||
.times(2)
|
||||
.reply((): nock.ReplyFnResult => {
|
||||
if (serverOutage) {
|
||||
serverOutage = false;
|
||||
return [500];
|
||||
}
|
||||
return [204, { test: true }];
|
||||
})
|
||||
.get('/outage')
|
||||
.times(2)
|
||||
.reply(500)
|
||||
.get('/slow')
|
||||
.times(2)
|
||||
.delay(3000)
|
||||
.reply(200)
|
||||
.get('/badRequest')
|
||||
.reply(403, { message: 'Missing Permissions', code: 50013 })
|
||||
.get('/unauthorized')
|
||||
.reply(401, { message: '401: Unauthorized', code: 0 })
|
||||
.get('/malformedRequest')
|
||||
.reply(601);
|
||||
|
||||
// This is tested first to ensure the count remains accurate
|
||||
test('Significant Invalid Requests', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/badRequest'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(403, { message: 'Missing Permissions', code: 50013 }, responseOptions)
|
||||
.times(10);
|
||||
|
||||
const invalidListener = jest.fn();
|
||||
const invalidListener2 = jest.fn();
|
||||
api.on(RESTEvents.InvalidRequestWarning, invalidListener);
|
||||
@@ -258,6 +118,54 @@ test('Significant Invalid Requests', async () => {
|
||||
});
|
||||
|
||||
test('Handle standard rate limits', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/standard'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(() => {
|
||||
const response = Date.now() >= resetAfter ? 204 : 429;
|
||||
resetAfter = Date.now() + 250;
|
||||
|
||||
if (response === 204) {
|
||||
return {
|
||||
statusCode: 204,
|
||||
data: '',
|
||||
responseOptions: {
|
||||
headers: {
|
||||
'x-ratelimit-limit': '1',
|
||||
'x-ratelimit-remaining': '0',
|
||||
'x-ratelimit-reset-after': ((resetAfter - Date.now()) / 1000).toString(),
|
||||
'x-ratelimit-bucket': '80c17d2f203122d936070c88c8d10f33',
|
||||
via: '1.1 google',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 429,
|
||||
data: {
|
||||
limit: '1',
|
||||
remaining: '0',
|
||||
resetAfter: (resetAfter / 1000).toString(),
|
||||
bucket: '80c17d2f203122d936070c88c8d10f33',
|
||||
retryAfter: (resetAfter - Date.now()).toString(),
|
||||
},
|
||||
responseOptions: {
|
||||
headers: {
|
||||
'x-ratelimit-limit': '1',
|
||||
'x-ratelimit-remaining': '0',
|
||||
'x-ratelimit-reset-after': ((resetAfter - Date.now()) / 1000).toString(),
|
||||
'x-ratelimit-bucket': '80c17d2f203122d936070c88c8d10f33',
|
||||
'retry-after': (resetAfter - Date.now()).toString(),
|
||||
via: '1.1 google',
|
||||
},
|
||||
},
|
||||
};
|
||||
})
|
||||
.times(3);
|
||||
|
||||
const [a, b, c] = [api.get('/standard'), api.get('/standard'), api.get('/standard')];
|
||||
const uint8 = new Uint8Array();
|
||||
|
||||
@@ -267,18 +175,107 @@ test('Handle standard rate limits', async () => {
|
||||
const previous2 = performance.now();
|
||||
expect(new Uint8Array((await c) as ArrayBuffer)).toStrictEqual(uint8);
|
||||
const now = performance.now();
|
||||
expect(previous2).toBeGreaterThanOrEqual(previous1 + 250);
|
||||
expect(now).toBeGreaterThanOrEqual(previous2 + 250);
|
||||
});
|
||||
|
||||
test('Handle global rate limits', async () => {
|
||||
const earlier = performance.now();
|
||||
expect(await api.get('/triggerGlobal')).toStrictEqual({ global: true });
|
||||
expect(await api.get('/regularRequest')).toStrictEqual({ test: true });
|
||||
expect(performance.now()).toBeGreaterThanOrEqual(earlier + 100);
|
||||
expect(previous2).toBeGreaterThanOrEqual(previous1 + 200);
|
||||
expect(now).toBeGreaterThanOrEqual(previous2 + 200);
|
||||
});
|
||||
|
||||
test('Handle sublimits', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/channels/:id'),
|
||||
method: 'PATCH',
|
||||
})
|
||||
.reply((t) => {
|
||||
const body = JSON.parse(t.body as string) as Record<string, unknown>;
|
||||
|
||||
if ('name' in body || 'topic' in body) {
|
||||
sublimitHits += 1;
|
||||
sublimitRequests += 1;
|
||||
const response = 2 - sublimitHits >= 0 && 10 - sublimitRequests >= 0 ? 200 : 429;
|
||||
startSublimitIntervals();
|
||||
|
||||
if (response === 200) {
|
||||
return {
|
||||
statusCode: 200,
|
||||
data: '',
|
||||
responseOptions: {
|
||||
headers: {
|
||||
'x-ratelimit-limit': '10',
|
||||
'x-ratelimit-remaining': `${10 - sublimitRequests}`,
|
||||
'x-ratelimit-reset-after': ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
via: '1.1 google',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 429,
|
||||
data: {
|
||||
limit: '10',
|
||||
remaining: `${10 - sublimitRequests}`,
|
||||
resetAfter: (sublimitResetAfter / 1000).toString(),
|
||||
retryAfter: ((retryAfter - Date.now()) / 1000).toString(),
|
||||
},
|
||||
responseOptions: {
|
||||
headers: {
|
||||
'x-ratelimit-limit': '10',
|
||||
'x-ratelimit-remaining': `${10 - sublimitRequests}`,
|
||||
'x-ratelimit-reset-after': ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
'retry-after': ((retryAfter - Date.now()) / 1000).toString(),
|
||||
via: '1.1 google',
|
||||
...responseOptions.headers,
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (!('name' in body) && !('topic' in body)) {
|
||||
sublimitRequests += 1;
|
||||
const response = 10 - sublimitRequests >= 0 ? 200 : 429;
|
||||
startSublimitIntervals();
|
||||
|
||||
if (response === 200) {
|
||||
return {
|
||||
statusCode: 200,
|
||||
data: '',
|
||||
responseOptions: {
|
||||
headers: {
|
||||
'x-ratelimit-limit': '10',
|
||||
'x-ratelimit-remaining': `${10 - sublimitRequests}`,
|
||||
'x-ratelimit-reset-after': ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
via: '1.1 google',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 429,
|
||||
data: {
|
||||
limit: '10',
|
||||
remaining: `${10 - sublimitRequests}`,
|
||||
resetAfter: (sublimitResetAfter / 1000).toString(),
|
||||
retryAfter: ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
},
|
||||
responseOptions: {
|
||||
headers: {
|
||||
'x-ratelimit-limit': '10',
|
||||
'x-ratelimit-remaining': `${10 - sublimitRequests}`,
|
||||
'x-ratelimit-reset-after': ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
'retry-after': ((sublimitResetAfter - Date.now()) / 1000).toString(),
|
||||
via: '1.1 google',
|
||||
...responseOptions.headers,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 420,
|
||||
data: 'Oh no',
|
||||
};
|
||||
})
|
||||
.persist();
|
||||
|
||||
// Return the current time on these results as their response does not indicate anything
|
||||
// Queue all requests, don't wait, to allow retroactive check
|
||||
const [aP, bP, cP, dP, eP] = [
|
||||
@@ -297,20 +294,60 @@ test('Handle sublimits', async () => {
|
||||
]); // For additional sublimited checks
|
||||
const e = await eP;
|
||||
|
||||
expect(a).toBeLessThan(b);
|
||||
expect(b).toBeLessThan(c);
|
||||
expect(d).toBeLessThan(c);
|
||||
expect(c).toBeLessThan(e);
|
||||
expect(d).toBeLessThan(e);
|
||||
expect(e).toBeLessThan(f);
|
||||
expect(e).toBeLessThan(g);
|
||||
expect(g).toBeLessThan(f);
|
||||
expect(a).toBeLessThanOrEqual(b);
|
||||
expect(b).toBeLessThanOrEqual(c);
|
||||
expect(d).toBeLessThanOrEqual(c);
|
||||
expect(c).toBeLessThanOrEqual(e);
|
||||
expect(d).toBeLessThanOrEqual(e);
|
||||
expect(e).toBeLessThanOrEqual(f);
|
||||
expect(e).toBeLessThanOrEqual(g);
|
||||
expect(g).toBeLessThanOrEqual(f);
|
||||
|
||||
clearInterval(sublimitIntervals.reset);
|
||||
clearInterval(sublimitIntervals.retry);
|
||||
clearInterval(sublimitIntervals.reset!);
|
||||
clearInterval(sublimitIntervals.retry!);
|
||||
|
||||
// Reject on RateLimit
|
||||
const [aP2, bP2, cP2] = [
|
||||
rateLimitErrorApi.patch('/channels/:id', sublimit),
|
||||
rateLimitErrorApi.patch('/channels/:id', sublimit),
|
||||
rateLimitErrorApi.patch('/channels/:id', sublimit),
|
||||
];
|
||||
await expect(aP2).resolves;
|
||||
await expect(bP2).rejects.toThrowError();
|
||||
await expect(bP2).rejects.toBeInstanceOf(RateLimitError);
|
||||
await expect(cP2).rejects.toThrowError();
|
||||
await expect(cP2).rejects.toBeInstanceOf(RateLimitError);
|
||||
});
|
||||
|
||||
test('Handle unexpected 429', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/unexpected'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(() => {
|
||||
if (unexpected429) {
|
||||
unexpected429 = false;
|
||||
return {
|
||||
statusCode: 429,
|
||||
data: '',
|
||||
responseOptions: {
|
||||
headers: {
|
||||
'retry-after': '1',
|
||||
via: '1.1 google',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
data: { test: true },
|
||||
responseOptions,
|
||||
};
|
||||
})
|
||||
.times(3);
|
||||
|
||||
const previous = performance.now();
|
||||
let firstResolvedTime: number;
|
||||
let secondResolvedTime: number;
|
||||
@@ -330,33 +367,133 @@ test('Handle unexpected 429', async () => {
|
||||
});
|
||||
|
||||
test('Handle unexpected 429 cloudflare', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/unexpected-cf'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(() => {
|
||||
if (unexpected429cf) {
|
||||
unexpected429cf = false;
|
||||
|
||||
return {
|
||||
statusCode: 429,
|
||||
data: '',
|
||||
responseOptions: {
|
||||
headers: {
|
||||
'retry-after': '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
data: { test: true },
|
||||
responseOptions,
|
||||
};
|
||||
})
|
||||
.times(2); // twice because it re-runs the request after first 429
|
||||
|
||||
const previous = Date.now();
|
||||
expect(await api.get('/unexpected-cf')).toStrictEqual({ test: true });
|
||||
expect(Date.now()).toBeGreaterThanOrEqual(previous + 1000);
|
||||
});
|
||||
|
||||
test('Handle global rate limits', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/triggerGlobal'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(() => ({
|
||||
data: { global: true },
|
||||
statusCode: 200,
|
||||
responseOptions,
|
||||
}));
|
||||
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/regularRequest'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(() => ({
|
||||
data: { test: true },
|
||||
statusCode: 200,
|
||||
responseOptions,
|
||||
}));
|
||||
|
||||
expect(await api.get('/triggerGlobal')).toStrictEqual({ global: true });
|
||||
expect(await api.get('/regularRequest')).toStrictEqual({ test: true });
|
||||
});
|
||||
|
||||
test('Handle temp server outage', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/temp'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(() => {
|
||||
if (serverOutage) {
|
||||
serverOutage = false;
|
||||
|
||||
return {
|
||||
statusCode: 500,
|
||||
data: '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
data: { test: true },
|
||||
responseOptions,
|
||||
};
|
||||
})
|
||||
.times(2);
|
||||
|
||||
expect(await api.get('/temp')).toStrictEqual({ test: true });
|
||||
});
|
||||
|
||||
test('perm server outage', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/outage'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(500, '', responseOptions)
|
||||
.times(4);
|
||||
|
||||
const promise = api.get('/outage');
|
||||
await expect(promise).rejects.toThrowError();
|
||||
await expect(promise).rejects.toBeInstanceOf(HTTPError);
|
||||
});
|
||||
|
||||
test('server responding too slow', async () => {
|
||||
const promise = api.get('/slow');
|
||||
await expect(promise).rejects.toThrowError('The user aborted a request.');
|
||||
}, 10000);
|
||||
const api2 = new REST({ timeout: 1 }).setToken('A-Very-Really-Real-Token');
|
||||
|
||||
test('Bad Request', async () => {
|
||||
const promise = api.get('/badRequest');
|
||||
await expect(promise).rejects.toThrowError('Missing Permissions');
|
||||
await expect(promise).rejects.toBeInstanceOf(DiscordAPIError);
|
||||
});
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/slow'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(200, '')
|
||||
.delay(100)
|
||||
.times(10);
|
||||
|
||||
const promise = api2.get('/slow');
|
||||
|
||||
await expect(promise).rejects.toThrowError('Request aborted');
|
||||
}, 1000);
|
||||
|
||||
test('Unauthorized', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/unauthorized'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(401, { message: '401: Unauthorized', code: 0 }, responseOptions)
|
||||
.times(2);
|
||||
|
||||
const setTokenSpy = jest.spyOn(invalidAuthApi.requestManager, 'setToken');
|
||||
|
||||
// Ensure authless requests don't reset the token
|
||||
@@ -372,19 +509,32 @@ test('Unauthorized', async () => {
|
||||
expect(setTokenSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Reject on RateLimit', async () => {
|
||||
const [aP, bP, cP] = [
|
||||
rateLimitErrorApi.patch('/channels/:id', sublimit),
|
||||
rateLimitErrorApi.patch('/channels/:id', sublimit),
|
||||
rateLimitErrorApi.patch('/channels/:id', sublimit),
|
||||
];
|
||||
await expect(aP).resolves;
|
||||
await expect(bP).rejects.toThrowError();
|
||||
await expect(bP).rejects.toBeInstanceOf(RateLimitError);
|
||||
await expect(cP).rejects.toThrowError();
|
||||
await expect(cP).rejects.toBeInstanceOf(RateLimitError);
|
||||
test('Bad Request', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/badRequest'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(403, { message: 'Missing Permissions', code: 50013 }, responseOptions);
|
||||
|
||||
const promise = api.get('/badRequest');
|
||||
await expect(promise).rejects.toThrowError('Missing Permissions');
|
||||
await expect(promise).rejects.toBeInstanceOf(DiscordAPIError);
|
||||
});
|
||||
|
||||
test('malformedRequest', async () => {
|
||||
expect(await api.get('/malformedRequest')).toBe(null);
|
||||
// This test doesn't really make sense because
|
||||
// there is no such thing as a 601 status code.
|
||||
// So, what exactly is a malformed request?
|
||||
mockPool
|
||||
.intercept({
|
||||
path: genPath('/malformedRequest'),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(() => ({
|
||||
statusCode: 405,
|
||||
data: '',
|
||||
}));
|
||||
|
||||
await expect(api.get('/malformedRequest')).rejects.toBeInstanceOf(DiscordAPIError);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user