Files
discordeno/packages/utils/tests/bucket.spec.ts
Fleny 27c261fee2 formatter: Use semicolons (#4686)
I prefer semicolors, they also help avoiding certain pitfalls in JavaScript/TypeScript, such as the following code sample:
```js
const xyz = "test"
(something.else as string) = "another"
```
This results in a TypeError: "test" is not a function, this is because js thinks we are trying to call the string "test" as a function.
To fix this it requires a `;` somewhere before the `(`, such as `;(something ... ` which in my opinion is ugly and less clean overall.
2026-01-17 21:54:15 +01:00

199 lines
5.1 KiB
TypeScript

import { expect } from 'chai';
import { afterEach, beforeEach, describe, it } from 'mocha';
import sinon from 'sinon';
import { LeakyBucket } from '../src/bucket.js';
async function promiseState(p: Promise<any>): Promise<string> {
const t = {};
return await Promise.race([p, t]).then(
(v) => (v === t ? 'pending' : 'fulfilled'),
() => 'rejected',
);
}
describe('bucket.ts', () => {
let clock: sinon.SinonFakeTimers;
beforeEach(() => {
clock = sinon.useFakeTimers();
});
afterEach(() => {
sinon.restore();
clock.restore();
});
describe('LeakyBucket function', () => {
it('will return bucket with given options', () => {
const options = {
max: 6006,
refillInterval: 2002,
refillAmount: 3003,
tokens: 4004,
someThingElse: {
thing: 'else',
},
};
const bucket = new LeakyBucket(options);
expect(bucket.max).to.equal(options.max);
expect(bucket.refillInterval).to.equal(options.refillInterval);
expect(bucket.refillAmount).to.equal(options.refillAmount);
});
it('will return bucket with refillAmount within max', () => {
const options = {
max: 111,
refillInterval: 2002,
refillAmount: 3003,
tokens: 4004,
};
const bucket = new LeakyBucket(options);
expect(bucket.refillAmount).to.equal(options.max);
});
it('will return bucket with tokensState within max', () => {
const options = {
max: 111,
refillInterval: 2002,
refillAmount: 3003,
tokens: 4004,
};
const bucket = new LeakyBucket(options);
expect(bucket.refillAmount).to.equal(options.max);
});
it('will return bucket with default property', () => {
const bucket = new LeakyBucket();
expect(bucket.max).equals(1);
expect(bucket.refillInterval).equals(5000);
expect(bucket.refillAmount).equals(1);
expect(bucket.queue).to.deep.equal([]);
});
it('will acquire a request', async () => {
const bucket = new LeakyBucket({
max: 120,
refillInterval: 60000,
refillAmount: 120,
});
await bucket.acquire(true);
expect(bucket.remaining).to.be.equal(119);
expect(bucket.used).to.be.equal(1);
});
it('will handle multiple requests at once', async () => {
const bucket = new LeakyBucket({
max: 120,
refillInterval: 60000,
refillAmount: 120,
});
for (let i = 0; i < 10; i++) {
bucket.acquire();
}
});
it('will handle too many requests', async () => {
const bucket = new LeakyBucket({
max: 5,
refillInterval: 10000,
refillAmount: 5,
});
for (let i = 0; i < 10; i++) {
bucket.acquire();
}
});
it('bucket refills are done properly', async () => {
const bucket = new LeakyBucket({
max: 2,
refillInterval: 500,
refillAmount: 2,
});
await bucket.acquire();
expect(bucket.remaining).equals(1);
expect(bucket.used).equals(1);
await clock.tickAsync(1000);
expect(bucket.remaining).equals(2);
expect(bucket.used).equals(0);
await bucket.acquire();
await clock.tickAsync(1000);
});
it('bucket refills when refill amount is < max', async () => {
const bucket = new LeakyBucket({
max: 3,
refillInterval: 800,
refillAmount: 1,
});
await bucket.acquire();
await bucket.acquire();
expect(bucket.remaining).equals(1);
expect(bucket.used).equals(2);
await clock.tickAsync(1000);
expect(bucket.remaining).equals(2);
expect(bucket.used).equals(1);
await clock.tickAsync(2000);
expect(bucket.remaining).equals(3);
expect(bucket.used).equals(0);
});
it('bucket refills when refill interval is slow', async () => {
const bucket = new LeakyBucket({
max: 1,
refillInterval: 500,
refillAmount: 1,
});
const acquired1 = bucket.acquire();
const acquired2 = bucket.acquire();
// js event loop
await (async () => {})();
expect(await promiseState(acquired1)).to.equal('fulfilled');
expect(await promiseState(acquired2)).to.equal('pending');
await clock.tickAsync(499);
expect(await promiseState(acquired2)).to.equal('pending');
await clock.tickAsync(1);
expect(await promiseState(acquired2)).to.equal('fulfilled');
expect(bucket.remaining).equals(0);
expect(bucket.used).equals(1);
});
describe('remaining', () => {
it('should be 0 even used too many', () => {
const bucket = new LeakyBucket({
max: 1,
refillInterval: 500,
refillAmount: 1,
});
// max is < used
bucket.used = 2;
expect(bucket.remaining).equals(0);
});
});
it("Don't process queue twice", () => {
const bucket = new LeakyBucket({
max: 1,
refillInterval: 500,
refillAmount: 1,
});
// fake processing
bucket.processing = true;
// request when already processing
bucket.processQueue();
});
});
});