diff --git a/jest.config.json b/jest.config.json index 719943f..ae3a75d 100644 --- a/jest.config.json +++ b/jest.config.json @@ -7,5 +7,6 @@ "moduleFileExtensions": ["js", "jsx", "json", "ts", "tsx"], "moduleNameMapper": { "^(\\.{1,2}/.*)\\.js$": "$1" - } + }, + "setupFilesAfterEnv": ["jest-expect-message"] } diff --git a/package-lock.json b/package-lock.json index a5052a4..e79f81f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "express": "4.18.2", "husky": "8.0.3", "jest": "29.7.0", + "jest-expect-message": "1.1.3", "lint-staged": "14.0.1", "npm-run-all": "4.1.5", "prettier": "3.0.3", @@ -7347,6 +7348,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-expect-message": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/jest-expect-message/-/jest-expect-message-1.1.3.tgz", + "integrity": "sha512-bTK77T4P+zto+XepAX3low8XVQxDgaEqh3jSTQOG8qvPpD69LsIdyJTa+RmnJh3HNSzJng62/44RPPc7OIlFxg==", + "dev": true + }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", diff --git a/package.json b/package.json index 2aedaa1..c526d0d 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "express": "4.18.2", "husky": "8.0.3", "jest": "29.7.0", + "jest-expect-message": "1.1.3", "lint-staged": "14.0.1", "npm-run-all": "4.1.5", "prettier": "3.0.3", diff --git a/test/helpers/mock-stores.ts b/test/helpers/mock-stores.ts index 47501ef..2a48a20 100644 --- a/test/helpers/mock-stores.ts +++ b/test/helpers/mock-stores.ts @@ -8,12 +8,12 @@ export class InvalidStore {} export class MockStore implements Store { store: { [key: string]: number } = {} - incrWasCalled = false + incrementWasCalled = false resetKeyWasCalled = false decrementWasCalled = false async increment(key: string) { - this.incrWasCalled = true + this.incrementWasCalled = true this.store[key] = (this.store[key] ?? 0) + 1 return { @@ -34,14 +34,14 @@ export class MockStore implements Store { } export class MockLegacyStore implements LegacyStore { - incrWasCalled = false + incrementWasCalled = false resetKeyWasCalled = false decrementWasCalled = false counter = 0 incr(key: string, cb: IncrementCallback): void { this.counter++ - this.incrWasCalled = true + this.incrementWasCalled = true cb(undefined, this.counter, new Date()) } diff --git a/test/helpers/server.ts b/test/helpers/server.ts new file mode 100644 index 0000000..f9be530 --- /dev/null +++ b/test/helpers/server.ts @@ -0,0 +1,43 @@ +// /test/helpers/server.ts +// Create an Express server for testing + +import createApp, { + type Application, + type Request, + type Response, + type RequestHandler, +} from 'express' + +/** + * Create an Express server with the given middleware. + */ +export const createServer = ( + middleware: RequestHandler | RequestHandler[], +): Application => { + // Create an Express server, and register the middleware. + const app = createApp() + app.use(middleware) + + // Register test routes. + app.all('/', (_request: Request, response: Response) => + response.send('Hi there!'), + ) + app.get('/ip', (request: Request, response: Response) => { + response.setHeader('x-your-ip', request.ip) + response.sendStatus(204) + }) + app.all('/sleepy', middleware, (_request: Request, response: Response) => { + const timerId = setTimeout(() => response.send('Hallo there!'), 100) + response.on('close', () => clearTimeout(timerId)) + }) + app.get('/error', (_request: Request, response: Response) => + response.sendStatus(400), + ) + app.post('/crash', (_request: Request, response: Response) => { + response.emit('error', new Error('Oops!')) + response.on('error', () => response.end()) + }) + + // Return the application instance. + return app +} diff --git a/test/integration-test.ts b/test/integration-test.ts new file mode 100644 index 0000000..335b551 --- /dev/null +++ b/test/integration-test.ts @@ -0,0 +1,420 @@ +// /test/integration-test.ts +// Tests the middleware with a real Express application. + +import EventEmitter from 'node:events' +// eslint-disable-next-line import/no-unassigned-import +import 'jest-expect-message' +import { type Application } from 'express' +import { agent as request } from 'supertest' +import slowDown from '../source/index.js' +import { MockStore } from './helpers/mock-stores.js' +import { createServer } from './helpers/server.js' + +/** + * Makes the program wait for the given number of milliseconds. + */ +const sleep = async (milliseconds: number): Promise => + // eslint-disable-next-line no-promise-executor-return + new Promise((resolve) => setTimeout(resolve, milliseconds)) + +/** + * Make a request to the default endpoint, and time it so we can check the + * delay applied to the response. + */ +const makeTimedRequest = async (app: Application) => { + const start = Date.now() + await request(app) + .get('/') + .expect(200) + .expect(/Hi there!/) + return Date.now() - start +} + +describe('integration', () => { + it('should call incr on the store', async () => { + const store = new MockStore() + const app = createServer( + slowDown({ + store, + validate: false, + }), + ) + + await request(app).get('/') + expect(store.incrementWasCalled).toBeTruthy() + }) + + it('should call resetKey on the store', () => { + const store = new MockStore() + const limiter = slowDown({ + store, + validate: false, + }) + limiter.resetKey('key') + + expect(store.resetKeyWasCalled).toBeTruthy() + }) + + it('should allow the first request with minimal delay', async () => { + const app = createServer(slowDown({ validate: false })) + + const delay = await makeTimedRequest(app) + expect(delay, `First resp took too long: ${delay} ms.`).toBeLessThan(100) + }) + + it('should apply a small delay to the second request', async () => { + const app = createServer( + slowDown({ + delayMs: 100, + validate: false, + }), + ) + + let delay = await makeTimedRequest(app) + expect(delay, `First resp took too long: ${delay} ms.`).toBeLessThan(100) + + delay = await makeTimedRequest(app) + expect( + delay, + `Second resp was served too quickly: ${delay} ms.`, + ).toBeGreaterThanOrEqual(100) + // Macos CI server is slow, and can add a 100-200ms of extra delay. + expect(delay, `Second resp took too long: ${delay} ms.`).toBeLessThan(400) + }) + + it('should apply a larger delay to the subsequent request', async () => { + const app = createServer( + slowDown({ + delayMs: (used) => (used - 1) * 100, + validate: false, + }), + ) + + await Promise.all([ + request(app).get('/'), // No delay + request(app).get('/'), // 100ms delay + request(app).get('/'), // 200ms delay + ]) + const delay = await makeTimedRequest(app) + + // Should be about 300ms delay on 4th request - because the multiplier starts at 0 + // BUT, this test frequently fails with a delay in the 4-500ms range on CI. + // So, loosening up the range a bit here. + expect( + delay >= 250 && delay <= 600, + `Fourth resp was served too fast or slow: ${delay} ms.`, + ).toBe(true) + }) + + it('should apply a cap of maxDelayMs on the the delay', async () => { + const app = createServer( + slowDown({ + delayAfter: 1, + delayMs: (used) => (used - 1) * 100, + maxDelayMs: 200, + validate: false, + }), + ) + + await Promise.all([ + request(app).get('/'), // 1st - no delay + request(app).get('/'), // 2nd - 100ms delay + request(app).get('/'), // 3rd - 200ms delay + ]) + const delay = await makeTimedRequest(app) + + // Should cap the delay so the 4th request delays about 200ms instead of 300ms + // this one also likes to fail with too much delay on macOS in CI + expect( + delay, + `Fourth resp was served too fast: ${delay} ms.`, + ).toBeGreaterThanOrEqual(150) + expect(delay, `Fourth resp was served too slow: ${delay} ms.`).toBeLessThan( + 600, + ) + }) + + it('should allow delayAfter requests before delaying responses', async () => { + const app = createServer( + slowDown({ + delayMs: 100, + delayAfter: 2, + validate: false, + }), + ) + + let delay = await makeTimedRequest(app) + expect(delay, `First resp was served too slow: ${delay} ms.`).toBeLessThan( + 50, + ) + + delay = await makeTimedRequest(app) + expect(delay, `Second resp was served too slow: ${delay} ms.`).toBeLessThan( + 50, + ) + + delay = await makeTimedRequest(app) + expect( + delay > 50 && delay < 150, + `Third request outside of range: ${delay} ms.`, + ).toBe(true) + }) + + it('should allow delayAfter to be a function', async () => { + const app = createServer( + slowDown({ + delayMs: 100, + delayAfter: () => 2, + validate: false, + }), + ) + + let delay = await makeTimedRequest(app) + expect(delay, `First resp was served too slow: ${delay} ms.`).toBeLessThan( + 50, + ) + + delay = await makeTimedRequest(app) + expect(delay, `Second resp was served too slow: ${delay} ms.`).toBeLessThan( + 50, + ) + + delay = await makeTimedRequest(app) + expect( + delay > 50 && delay < 150, + `Third request outside of range: ${delay} ms.`, + ).toBe(true) + }) + + it('should (eventually) return to full speed', async () => { + const app = createServer( + slowDown({ + delayMs: 100, + delayAfter: 1, + windowMs: 50, + validate: false, + }), + ) + + await Promise.all([ + request(app).get('/'), // 1st - no delay + request(app).get('/'), // 2nd - 100ms delay + request(app).get('/'), // 3rd - 200ms delay + ]) + + await sleep(500) + + const delay = await makeTimedRequest(app) + expect(delay, `Fourth resp was served too slow: ${delay} ms.`).toBeLessThan( + 50, + ) + }) + + it('should work repeatedly (issues #2 & #3)', async () => { + const app = createServer( + slowDown({ + delayMs: 100, + delayAfter: 2, + windowMs: 50, + validate: false, + }), + ) + + await Promise.all([ + request(app).get('/'), // 1st - no delay + request(app).get('/'), // 2nd - 100ms delay + request(app).get('/'), // 3rd - 200ms delay + ]) + await sleep(60) + + let delay = await makeTimedRequest(app) + expect(delay, `Fourth resp was served too slow: ${delay} ms.`).toBeLessThan( + 50, + ) + + await Promise.all([ + request(app).get('/'), // 1st - no delay + request(app).get('/'), // 2nd - 100ms delay + ]) + await sleep(60) + + delay = await makeTimedRequest(app) + expect( + delay, + `Eventual resp was served too slow: ${delay} ms.`, + ).toBeLessThan(50) + }) + + it('should allow individual IP to be reset', async () => { + const limiter = slowDown({ + delayMs: 100, + delayAfter: 1, + windowMs: 50, + validate: false, + }) + const app = createServer(limiter) + + const response = await request(app).get('/ip').expect(204) + + const myIp = response.headers['x-your-ip'] + if (!myIp) throw new Error('Unable to determine local IP') + + await request(app).get('/') // 1st - no delay + await request(app).get('/') // 2nd - 100ms delay + + limiter.resetKey(myIp) + await request(app).get('/') // 3rd - but no delay + }) + + it('should allow custom key generators', async () => { + const limiter = slowDown({ + delayMs: 0, + delayAfter: 2, + keyGenerator: (request) => request.query.key as string, + validate: false, + }) + const app = createServer(limiter) + + await request(app).get('/?key=1') // 1st - no delay + await request(app).get('/?key=1') // 2nd - 100ms delay + + await request(app).get('/?key=2') // 1st - no delay + + await request(app).get('/?key=1') // 3rd - 100ms delay + + await request(app).get('/?key=2') // 2nd - 100ms delay + await request(app).get('/?key=2') // 3rd - 100ms delay + }) + + it('should allow custom skip function', async () => { + const limiter = slowDown({ + delayMs: 0, + delayAfter: 2, + skip: () => true, + validate: false, + }) + const app = createServer(limiter) + + await request(app).get('/') + await request(app).get('/') + await request(app).get('/') // 3rd request would normally fail but we're skipping it + }) + + it('should decrement hits with success response and skipSuccessfulRequests', async () => { + const store = new MockStore() + const app = createServer( + slowDown({ + skipSuccessfulRequests: true, + store, + validate: false, + }), + ) + + await request(app).get('/') + expect( + store.decrementWasCalled, + '`decrement` was not called on the store', + ).toBeTruthy() + }) + + it('should decrement hits with failed response and skipFailedRequests', async () => { + const store = new MockStore() + const app = createServer( + slowDown({ + skipFailedRequests: true, + store, + validate: false, + }), + ) + + await request(app).get('/error').expect(400) + expect( + store.decrementWasCalled, + '`decrement` was not called on the store', + ).toBeTruthy() + }) + + // After upgrading super test, this one always fails, because res.finished is set to true, despite the response never actually being sent + // skip.test.js has an equivalent test hat doesn't use supertest and passes + it.skip('should decrement hits with closed response and skipFailedRequests', (done) => { + const store = new MockStore() + const app = createServer( + slowDown({ + skipFailedRequests: true, + store, + validate: false, + }), + ) + + const checkStoreDecremented = () => { + if (store.decrementWasCalled) { + done() + } else { + done(new Error('decrement was not called on the store')) + } + } + + void request(app) + .get('/long_response') + .timeout(10) + .end(checkStoreDecremented) + }) + + it('should decrement hits with response emitting error and skipFailedRequests', (done) => { + const store = new MockStore() + const app = createServer( + slowDown({ + skipFailedRequests: true, + store, + validate: false, + }), + ) + + void request(app) + .get('/crash') + .end(() => { + if (store.decrementWasCalled) done() + else done(new Error('decrement was not called on the store')) + }) + }) + + it('should not decrement hits with success response and skipFailedRequests', async () => { + const store = new MockStore() + const app = createServer( + slowDown({ + skipFailedRequests: true, + store, + validate: false, + }), + ) + + await request(app).get('/') + expect( + store.decrementWasCalled, + '`decrement` was not called on the store', + ).toBeTruthy() + }) + + it('should not excute slow down timer in case of req closed during delay', async () => { + const requestMock = {} + const resMock = new EventEmitter() + const currentLimiterMiddleWare = slowDown({ + delayAfter: 0, + delayMs: 100, + windowMs: 1000, + validate: false, + }) + const next = () => { + throw new Error('`setTimeout` should not excute!') + } + + // eslint-disable-next-line @typescript-eslint/await-thenable + await currentLimiterMiddleWare(requestMock as any, resMock as any, next) + resMock.emit('close') + + // eslint-disable-next-line no-promise-executor-return + await new Promise((resolve) => setTimeout(resolve, 200)) + }) + + // Todo: it("should not excute slow down timer in case of req closed before delay begins", async () => { +}) diff --git a/test/legacy-realtime-test.ts b/test/legacy-realtime-test.ts deleted file mode 100644 index c902ed6..0000000 --- a/test/legacy-realtime-test.ts +++ /dev/null @@ -1,520 +0,0 @@ -import assert from 'node:assert' -import EventEmitter from 'node:events' -import express, { type Application } from 'express' -import request from 'supertest' -import { describe, beforeEach, it } from '@jest/globals' -import slowDown from '../source/index.js' -import type { SlowDownRequestHandler } from '../source/types.js' -import { MockStore, InvalidStore } from './helpers/mock-stores.js' - -// These tests are callback based, but the linter doesn't understand that when the function also returns a promise -/* eslint @typescript-eslint/no-floating-promises: "off" */ - -describe('legacy realtime tests', function () { - let app!: Application - let longResponseClosed!: boolean - - beforeEach(function () { - longResponseClosed = false - }) - - function createAppWith(limit: SlowDownRequestHandler): Application { - app = express() - app.all('/', limit, function (request_, res) { - res.send('response!') - }) - // Helper endpoint to know what ip test requests come from - // set in headers so that I don't have to deal with the body being a stream - app.get('/ip', function (request_, res) { - res.setHeader('x-your-ip', request_.ip) - res.status(204).send('') - }) - - app.all('/bad_response_status', limit, function (request_, res) { - res.status(403).send() - }) - app.all('/long_response', limit, function (request_, res) { - const timerId = setTimeout(() => res.send('response!'), 100) - res.on('close', () => { - longResponseClosed = true - - clearTimeout(timerId) - }) - }) - app.all('/response_emit_error', limit, function (request_, res) { - res.on('error', () => { - res.end() - }) - res.emit('error', new Error('/response_emit_error hit')) - }) - return app - } - - function fastRequest(errorHandler: any, successHandler?: any, key?: any) { - let request_ = request(app).get('/') - // Add optional key parameter - if (key) { - request_ = request_.query({ key }) - } - - request_ - .expect(200) - .expect(/response!/) - .end(function (error, res) { - if (error) { - return errorHandler(error) - } - - if (successHandler) { - successHandler(null, res) - } - }) - } - - // For the moment, we're not checking the speed within the response. but this should make it easy to add that check later. - const slowRequest = fastRequest - - async function timedRequest() { - const start = Date.now() - await request(app) - .get('/') - .expect(200) - .expect(/response!/) - return Date.now() - start - } - - async function sleep(t: number) { - // eslint-disable-next-line no-promise-executor-return - return new Promise((resolve) => setTimeout(resolve, t)) - } - - it('should not allow the use of a store that is not valid', function (done) { - try { - slowDown({ - store: new InvalidStore() as any, - }) - } catch { - return done() - } - - done(new Error('It allowed an invalid store')) - }) - - it('should call incr on the store', async () => { - const store = new MockStore() - assert(!store.incrWasCalled) - - createAppWith( - slowDown({ - store, - validate: false, - }), - ) - - await request(app).get('/') - assert(store.incrWasCalled) - }) - - it('should call resetKey on the store', function () { - const store = new MockStore() - const limiter = slowDown({ - store, - validate: false, - }) - limiter.resetKey('key') - assert(store.resetKeyWasCalled) - }) - - it('should allow the first request with minimal delay', async function () { - createAppWith(slowDown({ validate: false })) - const delay = await timedRequest() - assert(delay < 100, 'First request took too long: ' + delay + 'ms') - }) - - it('should apply a small delay to the second request', async function () { - createAppWith( - slowDown({ - delayMs: 100, - validate: false, - }), - ) - let delay = await timedRequest() - assert(delay < 100, 'First request took too long: ' + delay + 'ms') - delay = await timedRequest() - assert(delay >= 100, 'Second request was served too fast: ' + delay + 'ms') - // Macos CI server is slow, and can add a 100-200ms of extra delay - assert(delay < 400, 'Second request took too long: ' + delay + 'ms') - }) - - it('should apply a larger delay to the subsequent request', async function () { - createAppWith( - slowDown({ - delayMs: (used) => (used - 1) * 100, - validate: false, - }), - ) - await Promise.all([ - request(app).get('/'), // No delay - request(app).get('/'), // 100ms delay - request(app).get('/'), // 200ms delay - ]) - const delay = await timedRequest() - // Should be about 300ms delay on 4th request - because the multiplier starts at 0 - // BUT, this test frequently fails with a delay in the 4-500ms range on CI. - // So, loosening up the range a bit here. - assert( - delay >= 250 && delay <= 600, - 'Fourth request was served too fast or slow: ' + delay + 'ms', - ) - }) - - it('should apply a cap of maxDelayMs on the the delay', async function () { - createAppWith( - slowDown({ - delayAfter: 1, - delayMs: (used) => (used - 1) * 100, - maxDelayMs: 200, - validate: false, - }), - ) - await Promise.all([ - request(app).get('/'), // 1st - no delay - request(app).get('/'), // 2nd - 100ms delay - request(app).get('/'), // 3rd - 200ms delay - ]) - - const delay = await timedRequest() - - // Should cap the delay so the 4th request delays about 200ms instead of 300ms - // this one also likes to fail with too much delay on macOS in CI - assert(delay >= 150, 'Fourth request was served too fast: ' + delay + 'ms') - assert(delay < 600, 'Fourth request took too long: ' + delay + 'ms') - }) - - it('should allow delayAfter requests before delaying responses', async function () { - createAppWith( - slowDown({ - delayMs: 100, - delayAfter: 2, - validate: false, - }), - ) - let delay = await timedRequest() - assert(delay < 50, 'First request took too long: ' + delay + 'ms') - - delay = await timedRequest() - assert(delay < 50, 'Second request took too long: ' + delay + 'ms') - - delay = await timedRequest() - assert( - delay > 50 && delay < 150, - 'Third request outside of range: ' + delay + 'ms', - ) - }) - - it('should allow delayAfter to be a function', async function () { - createAppWith( - slowDown({ - delayMs: 100, - delayAfter: () => 2, - validate: false, - }), - ) - let delay = await timedRequest() - assert(delay < 50, 'First request took too long: ' + delay + 'ms') - - delay = await timedRequest() - assert(delay < 50, 'Second request took too long: ' + delay + 'ms') - - delay = await timedRequest() - assert( - delay > 50 && delay < 150, - 'Third request outside of range: ' + delay + 'ms', - ) - }) - - it('should (eventually) return to full speed', async function () { - createAppWith( - slowDown({ - delayMs: 100, - delayAfter: 1, - windowMs: 50, - validate: false, - }), - ) - await Promise.all([ - request(app).get('/'), // 1st - no delay - request(app).get('/'), // 2nd - 100ms delay - request(app).get('/'), // 3rd - 200ms delay - ]) - - await sleep(500) - - const delay = await timedRequest() - assert(delay < 50, 'Fourth request took too long: ' + delay + 'ms') - }) - - it('should work repeatedly (issues #2 & #3)', async function () { - createAppWith( - slowDown({ - delayMs: 100, - delayAfter: 2, - windowMs: 50, - validate: false, - }), - ) - - await Promise.all([ - request(app).get('/'), // 1st - no delay - request(app).get('/'), // 2nd - 100ms delay - request(app).get('/'), // 3rd - 200ms delay - ]) - - await sleep(60) - - let delay = await timedRequest() - assert(delay < 50, 'Fourth request took too long: ' + delay + 'ms') - - await Promise.all([ - request(app).get('/'), // 1st - no delay - request(app).get('/'), // 2nd - 100ms delay - ]) - - await sleep(60) - - delay = await timedRequest() - assert(delay < 50, 'Eventual request took too long: ' + delay + 'ms') - }) - - it("should allow individual IP's to be reset", function (done) { - const limiter = slowDown({ - delayMs: 100, - delayAfter: 1, - windowMs: 50, - validate: false, - }) - createAppWith(limiter) - - request(app) - .get('/ip') - .expect(204) - .end(function (error, res) { - const myIp = res.headers['x-your-ip'] - if (!myIp) { - return done(new Error('unable to determine local IP')) - } - - fastRequest(done) - slowRequest(done, function (error: any) { - if (error) { - return done(error) - } - - limiter.resetKey(myIp) - fastRequest(done, done) - }) - }) - }) - - it('should allow custom key generators', function (done) { - const limiter = slowDown({ - delayMs: 0, - delayAfter: 2, - keyGenerator(request_, res) { - assert.ok(request_) - assert.ok(res) - - const { key } = request_.query - assert.ok(key) - - return key as string - }, - validate: false, - }) - - createAppWith(limiter) - fastRequest(done, null, 1) - fastRequest(done, null, 1) - fastRequest(done, null, 2) - slowRequest( - done, - function (error: any) { - if (error) { - return done(error) - } - - fastRequest(done, null, 2) - slowRequest(done, done, 2) - }, - 1, - ) - }) - - it('should allow custom skip function', function (done) { - const limiter = slowDown({ - delayMs: 0, - delayAfter: 2, - skip(request_, res) { - assert.ok(request_) - assert.ok(res) - - return true - }, - validate: false, - }) - - createAppWith(limiter) - fastRequest(done, null, 1) - fastRequest(done, null, 1) - fastRequest(done, done, 1) // 3rd request would normally fail but we're skipping it - }) - - it('should decrement hits with success response and skipSuccessfulRequests', (done) => { - const store = new MockStore() - createAppWith( - slowDown({ - skipSuccessfulRequests: true, - store, - validate: false, - }), - ) - fastRequest(done, function () { - if (store.decrementWasCalled) { - done() - } else { - done(new Error('decrement was not called on the store')) - } - }) - }) - it('should decrement hits with failed response and skipFailedRequests', (done) => { - const store = new MockStore() - createAppWith( - slowDown({ - skipFailedRequests: true, - store, - validate: false, - }), - ) - request(app) - .get('/bad_response_status') - .expect(403) - .end(() => { - if (store.decrementWasCalled) { - done() - } else { - done(new Error('decrement was not called on the store')) - } - }) - }) - // After upgrading super test, this one always fails, because res.finished is set to true, despite the response never actually being sent - // skip.test.js has an equivalent test hat doesn't use supertest and passes - it.skip('should decrement hits with closed response and skipFailedRequests', (done) => { - const store = new MockStore() - createAppWith( - slowDown({ - skipFailedRequests: true, - store, - validate: false, - }), - ) - const checkStoreDecremented = () => { - if (longResponseClosed) { - if (store.decrementWasCalled) { - done() - } else { - done(new Error('decrement was not called on the store')) - } - } else { - setImmediate(checkStoreDecremented) - } - } - - request(app).get('/long_response').timeout(10).end(checkStoreDecremented) - }) - it('should decrement hits with response emitting error and skipFailedRequests', (done) => { - const store = new MockStore() - createAppWith( - slowDown({ - skipFailedRequests: true, - store, - validate: false, - }), - ) - request(app) - .get('/response_emit_error') - .end(() => { - if (store.decrementWasCalled) { - done() - } else { - done(new Error('decrement was not called on the store')) - } - }) - }) - - it('should not decrement hits with success response and skipFailedRequests', (done) => { - const store = new MockStore() - createAppWith( - slowDown({ - skipFailedRequests: true, - store, - validate: false, - }), - ) - - fastRequest(done, function () { - if (store.decrementWasCalled) { - done(new Error('decrement was called on the store')) - } else { - done() - } - }) - }) - - it('should decrement hits with a failure and skipFailedRequests', (done) => { - const store = new MockStore() - const app = createAppWith( - slowDown({ - store, - skipFailedRequests: true, - validate: false, - }), - ) - request(app) - .get('/bad_response_status') - .expect(403) - .end(function (error: any /* , res */) { - if (error) { - return done(error) - } - - if (store.decrementWasCalled) { - done() - } else { - done(new Error('decrement was not called on the store')) - } - }) - }) - - it('should not excute slow down timer in case of req closed during delay', async () => { - const requestMock = {} - const resMock = new EventEmitter() - const currentLimiterMiddleWare = slowDown({ - delayAfter: 0, - delayMs: 100, - windowMs: 1000, - validate: false, - }) - function next() { - throw new Error('setTimeout should not excute!') - } - - // eslint-disable-next-line @typescript-eslint/await-thenable - await currentLimiterMiddleWare(requestMock as any, resMock as any, next) - resMock.emit('close') - - // eslint-disable-next-line no-promise-executor-return - await new Promise((resolve) => setTimeout(resolve, 200)) - }) - - // Todo: it("should not excute slow down timer in case of req closed before delay begins", async () => { -}) diff --git a/test/real-world-test.ts b/test/real-world-test.ts deleted file mode 100644 index bfff798..0000000 --- a/test/real-world-test.ts +++ /dev/null @@ -1,41 +0,0 @@ -// /test/real-world-test.ts -// Tests the middleware in real world scenarios - -import { describe, it } from '@jest/globals' -import createServer from 'express' -import { agent as request } from 'supertest' -import { json } from 'body-parser' -import slowDown from '../source/index.js' -import { MockStore } from './helpers/mock-stores.js' - -describe('real-world', () => { - it('should handle a req being processed before `express-slow-down` (#31 & #32)', async () => { - const app = createServer() - - // Note: in real-world usabe, body parser middleware should come AFTER - // `express-slow-down`. - app.use(json({ limit: '50mb' })) - - app.use( - slowDown({ - delayAfter: 0, - delayMs: 100, - store: new MockStore(), - }), - ) - - app.post('/upload', (req, res) => { - if (req.body.test) { - res.send('Success!') - } else { - res.status(400).send('Missing `test` key in body.') - } - }) - - await request(app) - .post('/upload') - .send({ test: true }) - .expect(200) - .expect(/Success!/) - }) -}) diff --git a/test/store-test.ts b/test/store-test.ts index b7740a0..e8bc0f5 100644 --- a/test/store-test.ts +++ b/test/store-test.ts @@ -38,13 +38,13 @@ describe('store', () => { it('should call incr on the store', async () => { const store = new MockLegacyStore() - expect(store.incrWasCalled).toBeFalsy() + expect(store.incrementWasCalled).toBeFalsy() const instance = slowDown({ store, }) await expectNoDelay(instance) - expect(store.incrWasCalled).toBeTruthy() + expect(store.incrementWasCalled).toBeTruthy() }) it('should call resetKey on the store', function () { @@ -61,11 +61,11 @@ describe('store', () => { describe('promise based store', () => { it('should call increment on the store', async () => { const store = new MockStore() - expect(store.incrWasCalled).toBeFalsy() + expect(store.incrementWasCalled).toBeFalsy() const instance = slowDown({ store }) await expectNoDelayPromise(instance) - expect(store.incrWasCalled).toBeTruthy() + expect(store.incrementWasCalled).toBeTruthy() }) it('should call resetKey on the store', function () {