From 1b982822029c1292183655ae2a71b97d0eb59a3a Mon Sep 17 00:00:00 2001 From: Torben Sorensen Date: Mon, 5 Jan 2026 15:31:01 -0800 Subject: [PATCH] more rate limiting --- src/routes/deals.routes.test.ts | 4 ++-- src/routes/flyer.routes.test.ts | 16 ++++++++-------- src/routes/gamification.routes.test.ts | 12 ++++++------ src/routes/personalization.routes.test.ts | 2 +- src/routes/price.routes.test.ts | 4 ++-- src/routes/reactions.routes.test.ts | 6 +++--- src/routes/recipe.routes.test.ts | 6 +++--- src/routes/stats.routes.test.ts | 2 +- src/routes/system.routes.test.ts | 8 ++++---- src/routes/user.routes.test.ts | 21 +++++++++++++++++---- 10 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/routes/deals.routes.test.ts b/src/routes/deals.routes.test.ts index a876d77e..d29868cd 100644 --- a/src/routes/deals.routes.test.ts +++ b/src/routes/deals.routes.test.ts @@ -113,8 +113,8 @@ describe('Deals Routes (/api/users/deals)', () => { .set('X-Test-Rate-Limit-Enable', 'true'); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(100); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(100); }); }); }); diff --git a/src/routes/flyer.routes.test.ts b/src/routes/flyer.routes.test.ts index a1d9c4db..29452258 100644 --- a/src/routes/flyer.routes.test.ts +++ b/src/routes/flyer.routes.test.ts @@ -319,8 +319,8 @@ describe('Flyer Routes (/api/flyers)', () => { .set('X-Test-Rate-Limit-Enable', 'true'); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(100); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(100); }); it('should apply batchLimiter to POST /items/batch-fetch', async () => { @@ -331,8 +331,8 @@ describe('Flyer Routes (/api/flyers)', () => { .send({ flyerIds: [1] }); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(50); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(50); }); it('should apply batchLimiter to POST /items/batch-count', async () => { @@ -343,8 +343,8 @@ describe('Flyer Routes (/api/flyers)', () => { .send({ flyerIds: [1] }); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(50); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(50); }); it('should apply trackingLimiter to POST /items/:itemId/track', async () => { @@ -357,8 +357,8 @@ describe('Flyer Routes (/api/flyers)', () => { .send({ type: 'view' }); expect(response.status).toBe(202); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(200); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(200); }); }); }); diff --git a/src/routes/gamification.routes.test.ts b/src/routes/gamification.routes.test.ts index 01ac3fdb..d1d0daa1 100644 --- a/src/routes/gamification.routes.test.ts +++ b/src/routes/gamification.routes.test.ts @@ -345,8 +345,8 @@ describe('Gamification Routes (/api/achievements)', () => { .set('X-Test-Rate-Limit-Enable', 'true'); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(100); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(100); }); it('should apply userReadLimiter to GET /me', async () => { @@ -360,8 +360,8 @@ describe('Gamification Routes (/api/achievements)', () => { .set('X-Test-Rate-Limit-Enable', 'true'); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(100); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(100); }); it('should apply adminTriggerLimiter to POST /award', async () => { @@ -378,8 +378,8 @@ describe('Gamification Routes (/api/achievements)', () => { .send({ userId: 'some-user', achievementName: 'some-achievement' }); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(30); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(30); }); }); }); diff --git a/src/routes/personalization.routes.test.ts b/src/routes/personalization.routes.test.ts index b8b3d099..f4d57b69 100644 --- a/src/routes/personalization.routes.test.ts +++ b/src/routes/personalization.routes.test.ts @@ -115,7 +115,7 @@ describe('Personalization Routes (/api/personalization)', () => { .set('X-Test-Rate-Limit-Enable', 'true'); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); + expect(response.headers).toHaveProperty('ratelimit-limit'); }); }); }); diff --git a/src/routes/price.routes.test.ts b/src/routes/price.routes.test.ts index 40ec8b69..d128e0ea 100644 --- a/src/routes/price.routes.test.ts +++ b/src/routes/price.routes.test.ts @@ -159,8 +159,8 @@ describe('Price Routes (/api/price-history)', () => { .send({ masterItemIds: [1, 2] }); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(50); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(50); }); }); }); diff --git a/src/routes/reactions.routes.test.ts b/src/routes/reactions.routes.test.ts index 43db9d77..d5c469da 100644 --- a/src/routes/reactions.routes.test.ts +++ b/src/routes/reactions.routes.test.ts @@ -218,7 +218,7 @@ describe('Reaction Routes (/api/reactions)', () => { .set('X-Test-Rate-Limit-Enable', 'true'); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); + expect(response.headers).toHaveProperty('ratelimit-limit'); }); it('should apply userUpdateLimiter to POST /toggle', async () => { @@ -236,8 +236,8 @@ describe('Reaction Routes (/api/reactions)', () => { .send({ entity_type: 'recipe', entity_id: '1', reaction_type: 'like' }); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(150); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(150); }); }); }); \ No newline at end of file diff --git a/src/routes/recipe.routes.test.ts b/src/routes/recipe.routes.test.ts index de82a6df..baddf652 100644 --- a/src/routes/recipe.routes.test.ts +++ b/src/routes/recipe.routes.test.ts @@ -350,7 +350,7 @@ describe('Recipe Routes (/api/recipes)', () => { // Assert expect(blockedResponse.status).toBe(429); - expect(blockedResponse.text).toContain('Too many recipe suggestion requests'); + expect(blockedResponse.text).toContain('Too many AI generation requests'); }); it('should NOT block requests when the opt-in header is not sent', async () => { @@ -375,8 +375,8 @@ describe('Recipe Routes (/api/recipes)', () => { .set('X-Test-Rate-Limit-Enable', 'true'); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(100); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(100); }); }); }); diff --git a/src/routes/stats.routes.test.ts b/src/routes/stats.routes.test.ts index 984f9355..1ac44e3f 100644 --- a/src/routes/stats.routes.test.ts +++ b/src/routes/stats.routes.test.ts @@ -75,7 +75,7 @@ describe('Stats Routes (/api/stats)', () => { .set('X-Test-Rate-Limit-Enable', 'true'); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); + expect(response.headers).toHaveProperty('ratelimit-limit'); }); }); }); diff --git a/src/routes/system.routes.test.ts b/src/routes/system.routes.test.ts index 20e770e2..cf15045a 100644 --- a/src/routes/system.routes.test.ts +++ b/src/routes/system.routes.test.ts @@ -171,10 +171,10 @@ describe('System Routes (/api/system)', () => { .send({ address }); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(response.headers).toHaveProperty('x-ratelimit-remaining'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(limit); - expect(parseInt(response.headers['x-ratelimit-remaining'])).toBeLessThan(limit); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(response.headers).toHaveProperty('ratelimit-remaining'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(limit); + expect(parseInt(response.headers['ratelimit-remaining'])).toBeLessThan(limit); }); }); }); diff --git a/src/routes/user.routes.test.ts b/src/routes/user.routes.test.ts index 8ee33b09..df0dce82 100644 --- a/src/routes/user.routes.test.ts +++ b/src/routes/user.routes.test.ts @@ -1237,6 +1237,19 @@ describe('User Routes (/api/users)', () => { }); // End of Recipe Routes describe('Rate Limiting', () => { + beforeAll(() => { + vi.useFakeTimers(); + }); + + beforeEach(() => { + // Advance time to ensure rate limits are reset between tests + vi.advanceTimersByTime(60 * 60 * 1000 + 1000); + }); + + afterAll(() => { + vi.useRealTimers(); + }); + it('should apply userUpdateLimiter to PUT /profile', async () => { vi.mocked(db.userRepo.updateUserProfile).mockResolvedValue(mockUserProfile); @@ -1246,8 +1259,8 @@ describe('User Routes (/api/users)', () => { .send({ full_name: 'Rate Limit Test' }); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(100); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(100); }); it('should apply userSensitiveUpdateLimiter to PUT /profile/password and block after limit', async () => { @@ -1283,8 +1296,8 @@ describe('User Routes (/api/users)', () => { .attach('avatar', Buffer.from('dummy-image-content'), dummyImagePath); expect(response.status).toBe(200); - expect(response.headers).toHaveProperty('x-ratelimit-limit'); - expect(parseInt(response.headers['x-ratelimit-limit'])).toBe(20); + expect(response.headers).toHaveProperty('ratelimit-limit'); + expect(parseInt(response.headers['ratelimit-limit'])).toBe(20); }); });