diff --git a/src/limiter.ts b/src/limiter.ts index 5eddfae..3ea2850 100644 --- a/src/limiter.ts +++ b/src/limiter.ts @@ -53,8 +53,8 @@ export class Limiter implements LimiterStoreContract { * * @param key - Unique identifier for the rate limit */ - consume(key: string | number): Promise { - return this.#store.consume(key) + consume(key: string | number, amount?: number): Promise { + return this.#store.consume(key, amount) } /** @@ -62,9 +62,10 @@ export class Limiter implements LimiterStoreContract { * Unlike consume(), this method does not throw when the limit is reached. * * @param key - Unique identifier for the rate limit + * @param amount - Number of requests to increment (default: 1) */ - increment(key: string | number): Promise { - return this.#store.increment(key) + increment(key: string | number, amount?: number): Promise { + return this.#store.increment(key, amount) } /** @@ -72,9 +73,10 @@ export class Limiter implements LimiterStoreContract { * Will not decrement below zero. * * @param key - Unique identifier for the rate limit + * @param amount - Number of requests to decrement (default: 1) */ - decrement(key: string | number): Promise { - return this.#store.decrement(key) + decrement(key: string | number, amount?: number): Promise { + return this.#store.decrement(key, amount) } /** @@ -95,7 +97,11 @@ export class Limiter implements LimiterStoreContract { * } * ``` */ - async attempt(key: string | number, callback: () => T | Promise): Promise { + async attempt( + key: string | number, + callback: () => T | Promise, + amount?: number + ): Promise { /** * Return early when remaining requests are less than * zero. @@ -110,7 +116,7 @@ export class Limiter implements LimiterStoreContract { } try { - await this.consume(key) + await this.consume(key, amount) return callback() } catch (error) { if (error instanceof E_TOO_MANY_REQUESTS === false) { @@ -144,7 +150,8 @@ export class Limiter implements LimiterStoreContract { */ async penalize( key: string | number, - callback: () => T | Promise + callback: () => T | Promise, + amount?: number ): Promise<[null, T] | [ThrottleException, null]> { const response = await this.get(key) @@ -169,7 +176,7 @@ export class Limiter implements LimiterStoreContract { * an error. */ if (callbackError) { - const { consumed, limit } = await this.increment(key) + const { consumed, limit } = await this.increment(key, amount) if (consumed >= limit && this.blockDuration) { await this.block(key, this.blockDuration) } diff --git a/src/stores/bridge.ts b/src/stores/bridge.ts index 62a85b2..f291f46 100644 --- a/src/stores/bridge.ts +++ b/src/stores/bridge.ts @@ -91,10 +91,12 @@ export default abstract class RateLimiterBridge implements LimiterStoreContract * console.log(`Remaining: ${response.remaining}`) * ``` */ - async consume(key: string | number): Promise { + async consume(key: string | number, amount?: number): Promise { + const consumeAmount = amount !== undefined && amount > 0 ? amount : 1 + try { - const response = await this.rateLimiter.consume(key, 1) - debug('request consumed for key %s', key) + const response = await this.rateLimiter.consume(key, consumeAmount) + debug('request consumed for key %s with amount %d', key, consumeAmount) return this.makeLimiterResponse(response) } catch (errorResponse: unknown) { debug('unable to consume request for key %s, %O', key, errorResponse) @@ -117,8 +119,13 @@ export default abstract class RateLimiterBridge implements LimiterStoreContract * const response = await limiter.increment('user:123') * ``` */ - async increment(key: string | number): Promise { - const response = await this.rateLimiter.penalty(key, 1) + async increment(key: string | number, amount: number = 1): Promise { + if (amount <= 0) { + debug('invalid increment amount "%d" provided. Falling back to 1', amount) + amount = 1 + } + + const response = await this.rateLimiter.penalty(key, amount) debug('increased requests count for key %s', key) return this.makeLimiterResponse(response) @@ -135,7 +142,7 @@ export default abstract class RateLimiterBridge implements LimiterStoreContract * const response = await limiter.decrement('user:123') * ``` */ - async decrement(key: string | number): Promise { + async decrement(key: string | number, amount: number = 1): Promise { const existingKey = await this.rateLimiter.get(key) /** @@ -145,6 +152,11 @@ export default abstract class RateLimiterBridge implements LimiterStoreContract return this.set(key, 0, this.duration) } + if (amount <= 0) { + debug('invalid decrement amount "%d" provided. Falling back to 1', amount) + amount = 1 + } + /** * Do not decrement beyond zero */ @@ -152,10 +164,14 @@ export default abstract class RateLimiterBridge implements LimiterStoreContract return this.makeLimiterResponse(existingKey) } + if (amount > existingKey.consumedPoints) { + amount = existingKey.consumedPoints + } + /** * Decrement */ - const response = await this.rateLimiter.reward(key, 1) + const response = await this.rateLimiter.reward(key, amount) debug('decreased requests count for key %s', key) return this.makeLimiterResponse(response) diff --git a/src/types.ts b/src/types.ts index 7794d8b..ccadb36 100644 --- a/src/types.ts +++ b/src/types.ts @@ -186,18 +186,18 @@ export interface LimiterStoreContract { * when all the requests have already been consumed or if * the key is blocked. */ - consume(key: string | number): Promise + consume(key: string | number, amount?: number): Promise /** * Increment the number of consumed requests for a given key. * No errors are thrown when limit has reached */ - increment(key: string | number): Promise + increment(key: string | number, amount?: number): Promise /** * Decrement the number of consumed requests for a given key. */ - decrement(key: string | number): Promise + decrement(key: string | number, amount?: number): Promise /** * Block a given key for the given duration. The duration must be diff --git a/tests/limiter.spec.ts b/tests/limiter.spec.ts index c5a6fc3..ce8efe0 100644 --- a/tests/limiter.spec.ts +++ b/tests/limiter.spec.ts @@ -34,21 +34,27 @@ test.group('Limiter', () => { */ const consumeCall = sinon.spy(store, 'consume') await limiter.consume('ip_localhost') - assert.isTrue(consumeCall.calledOnceWithExactly('ip_localhost'), 'consume called') + assert.isTrue(consumeCall.calledOnceWithExactly('ip_localhost', undefined), 'consume called') /** * increment call */ const incrementCall = sinon.spy(store, 'increment') await limiter.increment('ip_localhost') - assert.isTrue(incrementCall.calledOnceWithExactly('ip_localhost'), 'increment called') + assert.isTrue( + incrementCall.calledOnceWithExactly('ip_localhost', undefined), + 'increment called' + ) /** * decrement call */ const decrementCall = sinon.spy(store, 'decrement') await limiter.decrement('ip_localhost') - assert.isTrue(decrementCall.calledOnceWithExactly('ip_localhost'), 'decrement called') + assert.isTrue( + decrementCall.calledOnceWithExactly('ip_localhost', undefined), + 'decrement called' + ) /** * get call @@ -104,6 +110,142 @@ test.group('Limiter', () => { await assert.doesNotReject(() => limiter.increment('ip_localhost')) }) + test('increment requests count with negative amount should default to 1', async ({ assert }) => { + const redis = createRedis(['rlflx:ip_localhost']).connection() + const store = new LimiterRedisStore(redis, { + duration: '1 minute', + requests: 5, + }) + + const limiter = new Limiter(store) + + await limiter.increment('ip_localhost', -5) + assert.equal(await limiter.remaining('ip_localhost'), 4) + }) + + test('decrement requests count with negative amount should default to 1', async ({ assert }) => { + const redis = createRedis(['rlflx:ip_localhost']).connection() + const store = new LimiterRedisStore(redis, { + duration: '1 minute', + requests: 5, + }) + + const limiter = new Limiter(store) + + await limiter.increment('ip_localhost', 5) + await limiter.decrement('ip_localhost', -3) + assert.equal(await limiter.remaining('ip_localhost'), 1) + }) + + test('increment requests count with zero amount should default to 1', async ({ assert }) => { + const redis = createRedis(['rlflx:ip_localhost']).connection() + const store = new LimiterRedisStore(redis, { + duration: '1 minute', + requests: 5, + }) + + const limiter = new Limiter(store) + + await limiter.increment('ip_localhost', 0) + assert.equal(await limiter.remaining('ip_localhost'), 4) + }) + + test('decrement requests count with zero amount should default to 1', async ({ assert }) => { + const redis = createRedis(['rlflx:ip_localhost']).connection() + const store = new LimiterRedisStore(redis, { + duration: '1 minute', + requests: 5, + }) + + const limiter = new Limiter(store) + + await limiter.increment('ip_localhost', 5) + await limiter.decrement('ip_localhost', 0) + assert.equal(await limiter.remaining('ip_localhost'), 1) + }) + + test('increment remaining requests by amount', async ({ assert }) => { + const redis = createRedis(['rlflx:ip_localhost']).connection() + const store = new LimiterRedisStore(redis, { + duration: '1 minute', + requests: 5, + }) + + const limiter = new Limiter(store) + + await limiter.increment('ip_localhost', 3) + const response = await limiter.get('ip_localhost') + assert.containsSubset(response, { + consumed: 3, + remaining: 2, + limit: 5, + }) + }) + + test('decrement consumed requests by amount', async ({ assert }) => { + const redis = createRedis(['rlflx:ip_localhost']).connection() + const store = new LimiterRedisStore(redis, { + duration: '1 minute', + requests: 5, + }) + + const limiter = new Limiter(store) + + await limiter.increment('ip_localhost', 4) + await limiter.decrement('ip_localhost', 2) + const response = await limiter.get('ip_localhost') + assert.containsSubset(response, { + consumed: 2, + remaining: 3, + limit: 5, + }) + }) + + test('consume remaining requests by amount', async ({ assert }) => { + const redis = createRedis(['rlflx:ip_localhost']).connection() + const store = new LimiterRedisStore(redis, { + duration: '1 minute', + requests: 5, + }) + + const limiter = new Limiter(store) + + await limiter.consume('ip_localhost', 3) + const response = await limiter.get('ip_localhost') + assert.containsSubset(response, { + consumed: 3, + remaining: 2, + limit: 5, + }) + }) + + test('increment requests count with a custom amount', async ({ assert }) => { + const redis = createRedis(['rlflx:ip_localhost']).connection() + const store = new LimiterRedisStore(redis, { + duration: '1 minute', + requests: 10, + }) + + const limiter = new Limiter(store) + + await limiter.increment('ip_localhost', 3) + assert.equal(await limiter.remaining('ip_localhost'), 7) + }) + + test('decrement requests count with a custom amount', async ({ assert }) => { + const redis = createRedis(['rlflx:ip_localhost']).connection() + const store = new LimiterRedisStore(redis, { + duration: '1 minute', + requests: 10, + }) + + const limiter = new Limiter(store) + + await limiter.increment('ip_localhost', 10) + await limiter.decrement('ip_localhost', 4) + assert.equal(await limiter.remaining('ip_localhost'), 4) + }) + test('do not run action when all requests have been exhausted', async ({ assert }) => { const executionStack: string[] = [] const redis = createRedis(['rlflx:ip_localhost']).connection() diff --git a/tests/stores/database.spec.ts b/tests/stores/database.spec.ts index 0430279..d23883c 100644 --- a/tests/stores/database.spec.ts +++ b/tests/stores/database.spec.ts @@ -59,7 +59,7 @@ test.group('Limiter database store | wrapper | consume', () => { const response = await store.consume('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 5, remaining: 4, consumed: 1, @@ -83,7 +83,7 @@ test.group('Limiter database store | wrapper | consume', () => { await store.consume('ip_localhost') } catch (error) { assert.instanceOf(error, E_TOO_MANY_REQUESTS) - assert.containsSubset(error.response.toJSON(), { + assert.containSubset(error.response.toJSON(), { limit: 1, remaining: 0, consumed: 2, @@ -109,7 +109,7 @@ test.group('Limiter database store | wrapper | consume', () => { await store.consume('ip_localhost') } catch (error) { assert.instanceOf(error, E_TOO_MANY_REQUESTS) - assert.containsSubset(error.response.toJSON(), { + assert.containSubset(error.response.toJSON(), { limit: 1, remaining: 0, consumed: 2, @@ -232,7 +232,7 @@ test.group('Limiter database store | wrapper | get', () => { await store.consume('ip_localhost') const response = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 4, consumed: 1, @@ -271,7 +271,7 @@ test.group('Limiter database store | wrapper | set', () => { const response = await store.set('ip_localhost', 2, '1 minute') const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 3, consumed: 2, @@ -299,7 +299,7 @@ test.group('Limiter database store | wrapper | set', () => { const response = await store.set('ip_localhost', 2, '1 minute') const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 3, consumed: 2, @@ -326,7 +326,7 @@ test.group('Limiter database store | wrapper | block', () => { const response = await store.block('ip_localhost', '2 minutes') const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 0, consumed: 6, @@ -369,7 +369,7 @@ test.group('Limiter database store | wrapper | delete', () => { await store.block('ip_localhost', '2 minutes') const response = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 0, consumed: 6, @@ -436,7 +436,7 @@ test.group('Limiter database store | wrapper | increment', () => { await store.consume('ip_localhost') const response = await store.increment('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 5, remaining: 3, consumed: 2, @@ -458,7 +458,7 @@ test.group('Limiter database store | wrapper | increment', () => { await store.increment('ip_localhost') const response = await store.increment('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 0, consumed: 3, @@ -478,7 +478,7 @@ test.group('Limiter database store | wrapper | increment', () => { const response = await store.increment('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 0, consumed: 1, @@ -501,7 +501,7 @@ test.group('Limiter database store | wrapper | decrement', () => { await store.consume('ip_localhost') const response = await store.decrement('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 5, remaining: 5, consumed: 0, @@ -525,14 +525,14 @@ test.group('Limiter database store | wrapper | decrement', () => { const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 1, consumed: 0, }) assert.instanceOf(freshResponse, LimiterResponse) - assert.containsSubset(freshResponse!.toJSON(), { + assert.containSubset(freshResponse!.toJSON(), { limit: 1, remaining: 1, consumed: 0, @@ -552,7 +552,7 @@ test.group('Limiter database store | wrapper | decrement', () => { const response = await store.decrement('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 1, consumed: 0, diff --git a/tests/stores/memory.spec.ts b/tests/stores/memory.spec.ts index a06dfe5..ac4b475 100644 --- a/tests/stores/memory.spec.ts +++ b/tests/stores/memory.spec.ts @@ -34,7 +34,7 @@ test.group('Limiter memory store | wrapper | consume', () => { const response = await store.consume('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 5, remaining: 4, consumed: 1, @@ -53,7 +53,7 @@ test.group('Limiter memory store | wrapper | consume', () => { await store.consume('ip_localhost') } catch (error) { assert.instanceOf(error, E_TOO_MANY_REQUESTS) - assert.containsSubset(error.response.toJSON(), { + assert.containSubset(error.response.toJSON(), { limit: 1, remaining: 0, consumed: 2, @@ -74,7 +74,7 @@ test.group('Limiter memory store | wrapper | consume', () => { await store.consume('ip_localhost') } catch (error) { assert.instanceOf(error, E_TOO_MANY_REQUESTS) - assert.containsSubset(error.response.toJSON(), { + assert.containSubset(error.response.toJSON(), { limit: 1, remaining: 0, consumed: 2, @@ -94,7 +94,7 @@ test.group('Limiter memory store | wrapper | get', () => { await store.consume('ip_localhost') const response = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 4, consumed: 1, @@ -123,7 +123,7 @@ test.group('Limiter memory store | wrapper | set', () => { const response = await store.set('ip_localhost', 2, '1 minute') const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 3, consumed: 2, @@ -147,7 +147,7 @@ test.group('Limiter memory store | wrapper | set', () => { const response = await store.set('ip_localhost', 2, '1 minute') const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 3, consumed: 2, @@ -169,7 +169,7 @@ test.group('Limiter memory store | wrapper | block', () => { const response = await store.block('ip_localhost', '2 minutes') const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 0, consumed: 6, @@ -201,7 +201,7 @@ test.group('Limiter memory store | wrapper | delete', () => { await store.block('ip_localhost', '2 minutes') const response = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 0, consumed: 6, @@ -253,7 +253,7 @@ test.group('Limiter database store | wrapper | increment', () => { await store.consume('ip_localhost') const response = await store.increment('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 5, remaining: 3, consumed: 2, @@ -270,7 +270,7 @@ test.group('Limiter database store | wrapper | increment', () => { await store.increment('ip_localhost') const response = await store.increment('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 0, consumed: 3, @@ -285,7 +285,7 @@ test.group('Limiter database store | wrapper | increment', () => { const response = await store.increment('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 0, consumed: 1, @@ -303,7 +303,7 @@ test.group('Limiter database store | wrapper | decrement', () => { await store.consume('ip_localhost') const response = await store.decrement('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 5, remaining: 5, consumed: 0, @@ -322,14 +322,14 @@ test.group('Limiter database store | wrapper | decrement', () => { const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 1, consumed: 0, }) assert.instanceOf(freshResponse, LimiterResponse) - assert.containsSubset(freshResponse!.toJSON(), { + assert.containSubset(freshResponse!.toJSON(), { limit: 1, remaining: 1, consumed: 0, @@ -344,7 +344,7 @@ test.group('Limiter database store | wrapper | decrement', () => { const response = await store.decrement('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 1, consumed: 0, diff --git a/tests/stores/redis.spec.ts b/tests/stores/redis.spec.ts index ac28d48..c48e2de 100644 --- a/tests/stores/redis.spec.ts +++ b/tests/stores/redis.spec.ts @@ -38,7 +38,7 @@ test.group('Limiter redis store | wrapper | consume', () => { const response = await store.consume('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 5, remaining: 4, consumed: 1, @@ -58,7 +58,7 @@ test.group('Limiter redis store | wrapper | consume', () => { await store.consume('ip_localhost') } catch (error) { assert.instanceOf(error, E_TOO_MANY_REQUESTS) - assert.containsSubset(error.response.toJSON(), { + assert.containSubset(error.response.toJSON(), { limit: 1, remaining: 0, consumed: 2, @@ -80,7 +80,7 @@ test.group('Limiter redis store | wrapper | consume', () => { await store.consume('ip_localhost') } catch (error) { assert.instanceOf(error, E_TOO_MANY_REQUESTS) - assert.containsSubset(error.response.toJSON(), { + assert.containSubset(error.response.toJSON(), { limit: 1, remaining: 0, consumed: 2, @@ -166,7 +166,7 @@ test.group('Limiter redis store | wrapper | get', () => { await store.consume('ip_localhost') const response = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 4, consumed: 1, @@ -196,7 +196,7 @@ test.group('Limiter redis store | wrapper | get', () => { await assert.rejects(() => store.consume('ip_localhost')) const response = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 1, remaining: 0, consumed: 2, @@ -216,7 +216,7 @@ test.group('Limiter redis store | wrapper | set', () => { const response = await store.set('ip_localhost', 2, '1 minute') const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 3, consumed: 2, @@ -240,7 +240,7 @@ test.group('Limiter redis store | wrapper | set', () => { const response = await store.set('ip_localhost', 2, '1 minute') const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 3, consumed: 2, @@ -263,7 +263,7 @@ test.group('Limiter redis store | wrapper | block', () => { const response = await store.block('ip_localhost', '2 minutes') const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 0, consumed: 6, @@ -298,7 +298,7 @@ test.group('Limiter redis store | wrapper | delete', () => { await store.block('ip_localhost', '2 minutes') const response = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response!.toJSON(), { + assert.containSubset(response!.toJSON(), { limit: 5, remaining: 0, consumed: 6, @@ -353,7 +353,7 @@ test.group('Limiter redis store | wrapper | increment', () => { await store.consume('ip_localhost') const response = await store.increment('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 5, remaining: 3, consumed: 2, @@ -371,7 +371,7 @@ test.group('Limiter redis store | wrapper | increment', () => { await store.increment('ip_localhost') const response = await store.increment('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 0, consumed: 3, @@ -387,7 +387,7 @@ test.group('Limiter redis store | wrapper | increment', () => { const response = await store.increment('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 0, consumed: 1, @@ -406,7 +406,7 @@ test.group('Limiter redis store | wrapper | decrement', () => { await store.consume('ip_localhost') const response = await store.decrement('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 5, remaining: 5, consumed: 0, @@ -426,14 +426,14 @@ test.group('Limiter redis store | wrapper | decrement', () => { const freshResponse = await store.get('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 1, consumed: 0, }) assert.instanceOf(freshResponse, LimiterResponse) - assert.containsSubset(freshResponse!.toJSON(), { + assert.containSubset(freshResponse!.toJSON(), { limit: 1, remaining: 1, consumed: 0, @@ -449,7 +449,7 @@ test.group('Limiter redis store | wrapper | decrement', () => { const response = await store.decrement('ip_localhost') assert.instanceOf(response, LimiterResponse) - assert.containsSubset(response.toJSON(), { + assert.containSubset(response.toJSON(), { limit: 1, remaining: 1, consumed: 0,