Skip to content

Commit 23c3e36

Browse files
committed
corrected express middleware testing suite with refactor redis connect util and associated mock
1 parent 5fe7133 commit 23c3e36

File tree

6 files changed

+163
-76
lines changed

6 files changed

+163
-76
lines changed

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
"main": "index.js",
66
"type": "module",
77
"scripts": {
8-
"test": "jest --passWithNoTests --coverage",
8+
"test": "jest --passWithNoTests --coverage --detectOpenHandles",
99
"lint": "eslint src test",
1010
"lint:fix": "eslint --fix src test @types",
1111
"prettier": "prettier --write .",
12-
"prepare": "husky install"
12+
"prepare": "husky install",
13+
"build": "tsc"
1314
},
1415
"repository": {
1516
"type": "git",
@@ -26,9 +27,9 @@
2627
"@babel/core": "^7.17.12",
2728
"@babel/preset-env": "^7.17.12",
2829
"@babel/preset-typescript": "^7.17.12",
30+
"@types/express": "^4.17.13",
2931
"@types/ioredis": "^4.28.10",
3032
"@types/ioredis-mock": "^5.6.0",
31-
"@types/express": "^4.17.13",
3233
"@types/jest": "^27.5.1",
3334
"@typescript-eslint/eslint-plugin": "^5.24.0",
3435
"@typescript-eslint/parser": "^5.24.0",
@@ -56,4 +57,4 @@
5657
"graphql": "^16.5.0",
5758
"ioredis": "^5.0.5"
5859
}
59-
}
60+
}

src/middleware/index.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import Redis, { RedisOptions } from 'ioredis';
21
import { parse, validate } from 'graphql';
2+
import { RedisOptions } from 'ioredis';
33
import { GraphQLSchema } from 'graphql/type/schema';
44
import { Request, Response, NextFunction, RequestHandler } from 'express';
55

@@ -8,6 +8,7 @@ import setupRateLimiter from './rateLimiterSetup';
88
import getQueryTypeComplexity from '../analysis/typeComplexityAnalysis';
99
import { RateLimiterOptions, RateLimiterSelection } from '../@types/rateLimit';
1010
import { TypeWeightConfig } from '../@types/buildTypeWeights';
11+
import { connect } from '../utils/redis';
1112

1213
// FIXME: Will the developer be responsible for first parsing the schema from a file?
1314
// Can consider accepting a string representing a the filepath to a schema
@@ -40,7 +41,9 @@ export function expressRateLimiter(
4041
// TODO: Throw ValidationError if schema is invalid
4142
const typeWeightObject = buildTypeWeightsFromSchema(schema, typeWeightConfig);
4243
// TODO: Throw error if connection is unsuccessful
43-
const redisClient = new Redis(redisClientOptions); // Default port is 6379 automatically
44+
// Default connection timeout is 10000 ms of inactivity
45+
// FIXME: Do we need to re-establish connection?
46+
const redisClient = connect(redisClientOptions); // Default port is 6379 automatically
4447
const rateLimiter = setupRateLimiter(rateLimiterAlgo, rateLimiterOptions, redisClient);
4548

4649
// return the rate limiting middleware
@@ -66,7 +69,7 @@ export function expressRateLimiter(
6669
* req.ip and req.ips will worx in express but not with other frameworks
6770
*/
6871
// check for a proxied ip address before using the ip address on request
69-
const ip: string = req.ips[0] || req.ip;
72+
const ip: string = req.ips ? req.ips[0] : req.ip;
7073

7174
// FIXME: this will only work with type complexity
7275
const queryAST = parse(query);
@@ -80,8 +83,8 @@ export function expressRateLimiter(
8083

8184
const queryComplexity = getQueryTypeComplexity(queryAST, variables, typeWeightObject);
8285
try {
83-
// process the request and conditinoally respond to client with status code 429 o
84-
// r pass the request onto the next middleware function
86+
// process the request and conditinoally respond to client with status code 429 or
87+
// pass the request onto the next middleware function
8588
const rateLimiterResponse = await rateLimiter.processRequest(
8689
ip,
8790
requestTimestamp,

src/rateLimiters/tokenBucket.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class TokenBucket implements RateLimiter {
5151
const bucketJSON = await this.client.get(uuid);
5252

5353
// if the response is null, we need to create a bucket for the user
54-
if (bucketJSON === null) {
54+
if (!bucketJSON) {
5555
const newUserBucket: RedisBucket = {
5656
// conditionally set tokens depending on how many are requested comapred to the capacity
5757
tokens: tokens > this.capacity ? this.capacity : this.capacity - tokens,

src/utils/__mocks__/redis.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Redis from 'ioredis';
2+
3+
// eslint-disable-next-line @typescript-eslint/no-var-requires
4+
const RedisMock = require('ioredis-mock');
5+
6+
const clients: Redis[] = [];
7+
8+
/**
9+
* Connects to a client returning the client and a spe
10+
* @param options
11+
*/
12+
export function connect(): Redis {
13+
const client = new RedisMock();
14+
clients.push(client);
15+
return client;
16+
}
17+
18+
/**
19+
* Shutsdown all redis client connections
20+
*/
21+
export async function shutdown(): Promise<'OK'[]> {
22+
return Promise.all(clients.map((client: Redis) => client.quit()));
23+
}

src/utils/redis.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Redis, { RedisOptions } from 'ioredis';
2+
3+
const clients: Redis[] = [];
4+
5+
/**
6+
* Connects to a client returning the client and a spe
7+
* @param options
8+
*/
9+
export function connect(options: RedisOptions): Redis {
10+
// TODO: Figure out what other options we should set (timeouts, etc)
11+
// TODO: pass on connection error
12+
const client: Redis = new Redis(options);
13+
clients.push(client);
14+
return client;
15+
}
16+
17+
/**
18+
* Shutsdown all redis client connections
19+
*/
20+
export async function shutdown(): Promise<'OK'[]> {
21+
// TODO: Add functinoality to shutdown a client by an id
22+
// TODO: Error handling
23+
return Promise.all(clients.map((client: Redis) => client.quit()));
24+
}

0 commit comments

Comments
 (0)