Skip to content

Commit 5d129ea

Browse files
committed
bypass rate limiter in unit tests
1 parent 336392b commit 5d129ea

File tree

3 files changed

+69
-68
lines changed

3 files changed

+69
-68
lines changed

src/api/functions/rateLimit.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {
2+
ConditionalCheckFailedException,
3+
UpdateItemCommand,
4+
} from "@aws-sdk/client-dynamodb";
5+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
6+
import { genericConfig } from "common/config.js";
7+
8+
interface RateLimitParams {
9+
ddbClient: DynamoDBClient;
10+
rateLimitIdentifier: string;
11+
duration: number;
12+
limit: number;
13+
userIdentifier: string;
14+
}
15+
16+
export async function isAtLimit({
17+
ddbClient,
18+
rateLimitIdentifier,
19+
duration,
20+
limit,
21+
userIdentifier,
22+
}: RateLimitParams): Promise<{
23+
limited: boolean;
24+
resetTime: number;
25+
used: number;
26+
}> {
27+
const nowInSeconds = Math.floor(Date.now() / 1000);
28+
const timeWindow = Math.floor(nowInSeconds / duration) * duration;
29+
const PK = `rate-limit:${rateLimitIdentifier}:${userIdentifier}:${timeWindow}`;
30+
31+
try {
32+
const result = await ddbClient.send(
33+
new UpdateItemCommand({
34+
TableName: genericConfig.RateLimiterDynamoTableName,
35+
Key: {
36+
PK: { S: PK },
37+
SK: { S: "counter" },
38+
},
39+
UpdateExpression: "ADD #rateLimitCount :inc SET #ttl = :ttl",
40+
ConditionExpression:
41+
"attribute_not_exists(#rateLimitCount) OR #rateLimitCount <= :limit",
42+
ExpressionAttributeValues: {
43+
":inc": { N: "1" },
44+
":limit": { N: limit.toString() },
45+
":ttl": { N: (timeWindow + duration).toString() },
46+
},
47+
ExpressionAttributeNames: {
48+
"#rateLimitCount": "rateLimitCount",
49+
"#ttl": "ttl",
50+
},
51+
ReturnValues: "UPDATED_NEW",
52+
ReturnValuesOnConditionCheckFailure: "ALL_OLD",
53+
}),
54+
);
55+
return {
56+
limited: false,
57+
used: parseInt(result.Attributes?.rateLimitCount.N || "1", 10),
58+
resetTime: timeWindow + duration,
59+
};
60+
} catch (error) {
61+
if (error instanceof ConditionalCheckFailedException) {
62+
return { limited: true, resetTime: timeWindow + duration, used: limit };
63+
}
64+
throw error;
65+
}
66+
}

src/api/plugins/rateLimiter.ts

Lines changed: 1 addition & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import fp from "fastify-plugin";
2-
import {
3-
ConditionalCheckFailedException,
4-
UpdateItemCommand,
5-
} from "@aws-sdk/client-dynamodb";
6-
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
7-
import { genericConfig } from "common/config.js";
2+
import { isAtLimit } from "api/functions/rateLimit.js";
83
import { FastifyPluginAsync, FastifyRequest, FastifyReply } from "fastify";
94

105
interface RateLimiterOptions {
@@ -13,66 +8,6 @@ interface RateLimiterOptions {
138
rateLimitIdentifier?: string;
149
}
1510

16-
interface RateLimitParams {
17-
ddbClient: DynamoDBClient;
18-
rateLimitIdentifier: string;
19-
duration: number;
20-
limit: number;
21-
userIdentifier: string;
22-
}
23-
24-
async function isAtLimit({
25-
ddbClient,
26-
rateLimitIdentifier,
27-
duration,
28-
limit,
29-
userIdentifier,
30-
}: RateLimitParams): Promise<{
31-
limited: boolean;
32-
resetTime: number;
33-
used: number;
34-
}> {
35-
const nowInSeconds = Math.floor(Date.now() / 1000);
36-
const timeWindow = Math.floor(nowInSeconds / duration) * duration;
37-
const PK = `rate-limit:${rateLimitIdentifier}:${userIdentifier}:${timeWindow}`;
38-
39-
try {
40-
const result = await ddbClient.send(
41-
new UpdateItemCommand({
42-
TableName: genericConfig.RateLimiterDynamoTableName,
43-
Key: {
44-
PK: { S: PK },
45-
SK: { S: "counter" },
46-
},
47-
UpdateExpression: "ADD #rateLimitCount :inc SET #ttl = :ttl",
48-
ConditionExpression:
49-
"attribute_not_exists(#rateLimitCount) OR #rateLimitCount <= :limit",
50-
ExpressionAttributeValues: {
51-
":inc": { N: "1" },
52-
":limit": { N: limit.toString() },
53-
":ttl": { N: (timeWindow + duration).toString() },
54-
},
55-
ExpressionAttributeNames: {
56-
"#rateLimitCount": "rateLimitCount",
57-
"#ttl": "ttl",
58-
},
59-
ReturnValues: "UPDATED_NEW",
60-
ReturnValuesOnConditionCheckFailure: "ALL_OLD",
61-
}),
62-
);
63-
return {
64-
limited: false,
65-
used: parseInt(result.Attributes?.rateLimitCount.N || "1", 10),
66-
resetTime: timeWindow + duration,
67-
};
68-
} catch (error) {
69-
if (error instanceof ConditionalCheckFailedException) {
70-
return { limited: true, resetTime: timeWindow + duration, used: limit };
71-
}
72-
throw error;
73-
}
74-
}
75-
7611
const rateLimiterPlugin: FastifyPluginAsync<RateLimiterOptions> = async (
7712
fastify,
7813
options,

tests/unit/vitest.setup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { vi } from "vitest";
22
import { allAppRoles, AppRoles } from "../../src/common/roles.js";
33

44
vi.mock(
5-
import("../../src/api/plugins/rateLimiter.js"),
5+
import("../../src/api/functions/rateLimit.js"),
66
async (importOriginal) => {
77
const mod = await importOriginal();
88
return {
99
...mod,
1010
isAtLimit: vi.fn(async (_) => {
11-
return false;
11+
return { limited: false, resetTime: 0, used: 1 };
1212
}),
1313
};
1414
},

0 commit comments

Comments
 (0)