From 7cf2aa0aa4001a5ae5e413422178ceb521d4e6e5 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Sat, 7 Mar 2026 04:21:48 +0000 Subject: [PATCH] test: add rate limiting tests for auth endpoints --- .../src/__tests__/auth.rate-limiting.test.js | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 backend/src/__tests__/auth.rate-limiting.test.js diff --git a/backend/src/__tests__/auth.rate-limiting.test.js b/backend/src/__tests__/auth.rate-limiting.test.js new file mode 100644 index 0000000..79c0bcc --- /dev/null +++ b/backend/src/__tests__/auth.rate-limiting.test.js @@ -0,0 +1,108 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; +import bcrypt from 'bcryptjs'; +import jwt from 'jsonwebtoken'; +import { pool } from '../db/connection.js'; + +// Mock the database pool +const mockPool = { + query: (sql, params) => { + if (sql.includes('INSERT INTO users')) { + return [{ insertId: 1 }]; + } + if (sql.includes('SELECT id, email, password_hash FROM users')) { + // Simulate a valid user with hashed password + const hashedPassword = '$2a$12$O5y0793j8K4h6g7f9e8d7c6b5a4z3y2x1w0v9u8t7s6r5q4p3o2n1m'; + return [[{ id: 1, email: 'test@example.com', password_hash: hashedPassword }]]; + } + return []; + } +}; + +// Replace the actual pool with mock +const originalPool = pool; +pool.query = mockPool.query; + +// Mock bcrypt.compare to always return true for valid password +const originalBcryptCompare = bcrypt.compare; +bcrypt.compare = async (password, hash) => { + if (password === 'validpassword') { + return true; + } + return false; +}; + +// Mock jwt.sign to return a fixed token +const originalJwtSign = jwt.sign; +jwt.sign = () => 'mock-jwt-token'; + +test('login should implement rate limiting for failed attempts', async () => { + const req = { + body: { + email: 'test@example.com', + password: 'wrongpassword' + } + }; + + const res = { + status: (code) => { + res.statusCode = code; + return res; + }, + json: (data) => { + res.data = data; + } + }; + + // First 4 failed attempts should not be rate limited + for (let i = 0; i < 4; i++) { + await require('../routes/auth').default.post('/login', req, res); + assert.strictEqual(res.statusCode, 401); + } + + // 5th attempt should be rate limited + await require('../routes/auth').default.post('/login', req, res); + assert.strictEqual(res.statusCode, 429); + assert.ok(res.data.error.includes('Too many login attempts')); +}); + +test('login should reset rate limit after successful authentication', async () => { + const req = { + body: { + email: 'test@example.com', + password: 'validpassword' + } + }; + + const res = { + status: (code) => { + res.statusCode = code; + return res; + }, + json: (data) => { + res.data = data; + } + }; + + // Successful login should reset the rate limit + await require('../routes/auth').default.post('/login', req, res); + assert.strictEqual(res.statusCode, 200); + + // Now try to fail again - should not be rate limited immediately + const failedReq = { + body: { + email: 'test@example.com', + password: 'wrongpassword' + } + }; + + await require('../routes/auth').default.post('/login', failedReq, res); + assert.strictEqual(res.statusCode, 401); +}); + +// Restore the original functions +bcrypt.compare = originalBcryptCompare; +jwt.sign = originalJwtSign; +pool.query = originalPool; + +export default {}; \ No newline at end of file