Skip to content

Commit 53e0d63

Browse files
Yufei WuYufei Wu
authored andcommitted
fixed error and update the test
1 parent 0e7d288 commit 53e0d63

File tree

2 files changed

+38
-26
lines changed

2 files changed

+38
-26
lines changed

src/rateLimiters/fixedWindow.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class FixedWindow implements RateLimiter {
3333
}
3434

3535
/**
36+
* @function processRequest - Fixed Window algorithm to allow or block
37+
* based on the depth/complexity (in amount of tokens) of incoming requests.
3638
* Fixed Window
3739
* _________________________________
3840
* | *full capacity |
@@ -42,6 +44,12 @@ class FixedWindow implements RateLimiter {
4244
* |<-- window size -->|
4345
*current timestamp next timestamp
4446
*
47+
* First, checks if a window exists in the redis cache.
48+
* If not, then `fixedWindowStart` is set as the current timestamp, and `currentTokens` is checked against `capacity`.
49+
* If enough room exists for the request, returns success as true and tokens as how many tokens remain in the current fixed window.
50+
*
51+
* If a window does exist in the cache, we first check if the timestamp is greater than the fixedWindowStart + windowSize.
52+
* If it isn't, we update currentToken with the incoming token until reach the capcity
4553
*
4654
* @param {string} uuid - unique identifer used to throttle requests
4755
* @param {number} timestamp - time the request was recieved
@@ -62,29 +70,40 @@ class FixedWindow implements RateLimiter {
6270

6371
if (windowJSON === null) {
6472
const newUserWindow: RedisWindow = {
65-
currentTokens: tokens <= this.capacity ? tokens : 0,
73+
currentTokens: tokens > this.capacity ? 0 : tokens,
6674
fixedWindowStart: timestamp,
6775
};
6876

69-
if (tokens <= this.capacity) {
77+
if (tokens > this.capacity) {
7078
await this.client.setex(uuid, keyExpiry, JSON.stringify(newUserWindow));
71-
return { success: true, tokens: this.capacity - newUserWindow.currentTokens };
79+
return { success: false, tokens: this.capacity };
7280
}
73-
// optional
7481
await this.client.setex(uuid, keyExpiry, JSON.stringify(newUserWindow));
75-
return { success: false, tokens: this.capacity };
82+
return { success: true, tokens: this.capacity - newUserWindow.currentTokens };
83+
}
84+
const window: RedisWindow = await JSON.parse(windowJSON);
85+
86+
// update the currentToken until reaches its capacity
87+
window.currentTokens += tokens;
88+
if (window.currentTokens > this.capacity) {
89+
window.currentTokens = this.capacity;
90+
return {
91+
success: false,
92+
tokens: this.capacity - window.currentTokens,
93+
};
7694
}
7795

78-
const window: RedisWindow = await JSON.parse(windowJSON as string);
79-
96+
// update a new time window, check the current capacity situation
8097
const updatedUserWindow = this.updateTimeWindow(window, timestamp);
8198
if (tokens > this.capacity) {
8299
await this.client.setex(uuid, keyExpiry, JSON.stringify(updatedUserWindow));
83-
return { success: false, tokens: this.capacity - updatedUserWindow.currentTokens };
100+
return { success: false, tokens: this.capacity };
84101
}
85102
await this.client.setex(uuid, keyExpiry, JSON.stringify(updatedUserWindow));
86-
87-
return { success: true, tokens: updatedUserWindow.currentTokens };
103+
return {
104+
success: true,
105+
tokens: this.capacity - updatedUserWindow.currentTokens,
106+
};
88107
}
89108

90109
/**
@@ -100,7 +119,6 @@ class FixedWindow implements RateLimiter {
100119
fixedWindowStart: window.fixedWindowStart,
101120
};
102121
if (timestamp >= window.fixedWindowStart + this.windowSize) {
103-
// TODO: should base on user's current timestamp
104122
updatedUserWindow.fixedWindowStart = window.fixedWindowStart + this.windowSize;
105123
updatedUserWindow.currentTokens = 0;
106124
}

test/rateLimiters/fixedWindow.test.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ let timestamp: number;
1414
const user1 = '1';
1515
const user2 = '2';
1616
const user3 = '3';
17-
const user4 = '4';
1817

1918
async function getWindowFromClient(redisClient: ioredis.Redis, uuid: string): Promise<RedisWindow> {
2019
const res = await redisClient.get(uuid);
@@ -59,24 +58,21 @@ describe('Test FixedWindow Rate Limiter', () => {
5958
(await limiter.processRequest(user2, timestamp, initial + partialWithdraw))
6059
.tokens
6160
).toBe(CAPACITY - initial - partialWithdraw);
62-
// TODO: using setWindowClient first and update the capacity (not working)
63-
// TODO: might need to look into why is not taking second request...
6461

6562
const tokenCountPartial = await getWindowFromClient(client, user2);
6663
expect(tokenCountPartial.currentTokens).toBe(initial + partialWithdraw);
6764
});
6865

69-
// TODO: need to fix processRequest Fn()
70-
71-
xtest('window is partially full and request has no leftover tokens', async () => {
66+
test('window is partially full and request has no leftover tokens', async () => {
7267
const initial = 6;
7368
const partialWithdraw = 5;
7469
await setTokenCountInClient(client, user2, initial, timestamp);
7570
expect(
7671
(await limiter.processRequest(user2, timestamp, partialWithdraw)).success
7772
).toBe(false);
78-
const tokenCountPartialToEmpty = await getWindowFromClient(client, user2);
79-
expect(tokenCountPartialToEmpty.currentTokens).toBe(10);
73+
expect(
74+
(await limiter.processRequest(user2, timestamp, partialWithdraw)).tokens
75+
).toBe(0);
8076
});
8177
});
8278
describe('after a BLOCKED request...', () => {
@@ -89,17 +85,15 @@ describe('Test FixedWindow Rate Limiter', () => {
8985
// expect current tokens in the window to still be 0
9086
expect((await getWindowFromClient(client, user1)).currentTokens).toBe(0);
9187
});
92-
xtest('window is partially full but not enough time elapsed to reach new window', async () => {
88+
test('window is partially full but not enough time elapsed to reach new window', async () => {
9389
const requestedTokens = 9;
9490

9591
await setTokenCountInClient(client, user2, requestedTokens, timestamp);
9692
// expect remaining tokens to be 1, b/c the 2-token-request should be blocked
97-
// TODO: fix processRequest function
9893
const result = await limiter.processRequest(user2, timestamp + WINDOW_SIZE - 1, 2);
9994

10095
expect(result.success).toBe(false);
101-
// TODO: fix this, (expect 1, but get 9 -- which is # of currentTokens)
102-
expect(result.tokens).toBe(CAPACITY - requestedTokens);
96+
expect(result.tokens).toBe(0);
10397

10498
// expect current tokens in the window to still be 9
10599
expect((await getWindowFromClient(client, user2)).currentTokens).toBe(9);
@@ -109,7 +103,7 @@ describe('Test FixedWindow Rate Limiter', () => {
109103
afterEach(() => {
110104
client.flushall();
111105
});
112-
xtest('New window is initialized after reaching the window size', async () => {
106+
test('New window is initialized after reaching the window size', async () => {
113107
const fullRequest = 10;
114108
await setTokenCountInClient(client, user3, fullRequest, timestamp);
115109
const noAccess = await limiter.processRequest(
@@ -118,16 +112,16 @@ describe('Test FixedWindow Rate Limiter', () => {
118112
1
119113
);
120114

121-
// TODO: fix processRequest function
122115
// expect cannot pass any request
116+
expect(noAccess.tokens).toBe(0);
123117
expect(noAccess.success).toBe(false);
124118

125119
const newRequest = 3;
126120
await setTokenCountInClient(client, user3, newRequest, timestamp + WINDOW_SIZE + 1);
127121
const newAccess = await limiter.processRequest(user3, timestamp, 1);
128122

129123
expect(newAccess.success).toBe(true);
130-
expect(newAccess.tokens).toBe(4);
124+
expect(newAccess.tokens).toBe(6);
131125
});
132126
});
133127
});

0 commit comments

Comments
 (0)