diff --git a/backend/config/redis.js b/backend/config/redis.js index a4482666e..2d0681871 100644 --- a/backend/config/redis.js +++ b/backend/config/redis.js @@ -1,35 +1,57 @@ const Redis = require("ioredis"); const dotenv = require("dotenv"); -dotenv.config() +dotenv.config(); -if (!process.env.REDIS_URL) { - if (process.env.NODE_ENV !== 'production') { - console.log("DEBUG: ENV KEYS:", Object.keys(process.env)); +/** + * TEST MODE → fake redis + */ +if (process.env.NODE_ENV === "test") { + console.warn("⚠️ Redis disabled in test environment"); + + const dummyRedis = { + status: "ready", + get: async () => null, + set: async () => null, + del: async () => null, + quit: async () => null, + on: () => {}, // some modules attach listeners + }; + + module.exports = dummyRedis; + +} else { + + /** + * DEV / PROD → real redis + */ + if (!process.env.REDIS_URL) { + if (process.env.NODE_ENV !== "production") { + console.log("DEBUG: ENV KEYS:", Object.keys(process.env)); } throw new Error("REDIS_URL is not defined in .env"); -} + } -const redis = new Redis(process.env.REDIS_URL, { + const redis = new Redis(process.env.REDIS_URL, { retryStrategy(times) { - const delay = Math.min(times * 50, 2000); - if (times > 3) { - console.warn("⚠️ Redis: Max retries reached. Caching will be disabled."); - return null; // Stop retrying - } - return delay; + const delay = Math.min(times * 50, 2000); + if (times > 3) { + console.warn("⚠️ Redis: Max retries reached. Caching will be disabled."); + return null; + } + return delay; }, - maxRetriesPerRequest: null // Allow requests to fail if not connected -}); + maxRetriesPerRequest: null, + }); -redis.on('ready', () => { - console.log('ioredis client is connected and ready.'); -}); + redis.on("ready", () => { + console.log("ioredis client is connected and ready."); + }); -redis.on('error', (err) => { - console.error('❌ Redis Connection Error:', err.message); - console.error(' -> Ensure Redis is running on localhost:6379'); - console.error(' -> Windows: Use WSL or a Memurai/Redis port.'); - // Do not throw; lets the app continue without caching if needed -}); + redis.on("error", (err) => { + console.error("❌ Redis Connection Error:", err.message); + console.error(" -> Ensure Redis is running on localhost:6379"); + console.error(" -> Windows: Use WSL or a Memurai/Redis port."); + }); -module.exports = redis; + module.exports = redis; +} diff --git a/backend/tests/auth.test.js b/backend/tests/auth.test.js index d6a0412e3..7dccf222d 100644 --- a/backend/tests/auth.test.js +++ b/backend/tests/auth.test.js @@ -1,15 +1,21 @@ const request = require('supertest'); const mongoose = require('mongoose'); +require('dotenv').config(); -const JWT_SECRET = - process.env.JWT_SECRET || - (process.env.NODE_ENV === "test" ? "ci_test_jwt_secret" : null); +/** + * CI / local fallback values + * Tests should not depend on developer machine env. + */ +process.env.TEST_MONGO_URL = + process.env.TEST_MONGO_URL || "mongodb://127.0.0.1:27017/urbackend_test"; -if (!JWT_SECRET) { - throw new Error("JWT_SECRET is not defined"); -} +process.env.JWT_SECRET = + process.env.JWT_SECRET || "ci_test_jwt_secret"; +/** + * Mock Redis + */ jest.mock('ioredis', () => { return jest.fn().mockImplementation(() => ({ on: jest.fn(), @@ -21,6 +27,9 @@ jest.mock('ioredis', () => { })); }); +/** + * Mock email service + */ jest.mock('resend', () => { return { Resend: jest.fn().mockImplementation(() => ({ @@ -34,15 +43,12 @@ jest.mock('resend', () => { const app = require('../app'); const Developer = require('../models/Developer'); const bcrypt = require('bcryptjs'); -require('dotenv').config(); - // --- SETUP beforeAll(async () => { const uri = process.env.TEST_MONGO_URL; - // Debugging (Secret is masked as *** in GitHub logs, but we can check the format) console.log("URI Length:", uri ? uri.length : 0); console.log("URI Starts with mongodb:", uri ? uri.startsWith('mongodb') : false); @@ -54,25 +60,22 @@ beforeAll(async () => { await mongoose.connect(uri); }); -const redis = require('../config/redis'); // Import redis client +const redis = require('../config/redis'); afterAll(async () => { await mongoose.connection.close(); - await redis.quit(); // Close Redis connection + await redis.quit(); }); describe('Auth API Security', () => { - // Clean DB bedore every test beforeEach(async () => { - await Developer.deleteMany({}); // Clear old data + await Developer.deleteMany({}); - // Create a dummy user const hashedPassword = await bcrypt.hash('password123', 10); await Developer.create({ email: 'test@example.com', password: hashedPassword }); }); - // Clean DB after every test afterEach(async () => { await Developer.deleteMany({}); }); @@ -93,15 +96,10 @@ describe('Auth API Security', () => { const res = await request(app) .post('/api/auth/login') .send({ - email: { "$ne": null }, // Attack Payload + email: { "$ne": null }, password: 'password123' }); - // DEBUGGING LOGS - console.log("Status Code:", res.statusCode); - console.log("Response Body:", res.body); - console.log("Response Text:", res.text); - expect(res.statusCode).toBe(400); expect(res.body.error).toBeDefined(); }); diff --git a/backend/tests/health.test.js b/backend/tests/health.test.js new file mode 100644 index 000000000..d906c14cc --- /dev/null +++ b/backend/tests/health.test.js @@ -0,0 +1,11 @@ +const request = require("supertest"); +const app = require("../app"); + +describe("Health Check API", () => { + it("should return 200 and success message", async () => { + const res = await request(app).get("/"); + + expect(res.statusCode).toBe(200); + expect(res.body.status).toBe("success"); + }); +}); diff --git a/backend/utils/validateEnv.js b/backend/utils/validateEnv.js index 7f7aec930..d61fda4c6 100644 --- a/backend/utils/validateEnv.js +++ b/backend/utils/validateEnv.js @@ -1,6 +1,7 @@ const requiredEnvVars = ["MONGO_URL", "PORT", "REDIS_URL"]; function validateEnv() { + if (process.env.NODE_ENV === "test") return; const missing = requiredEnvVars.filter((key) => !process.env[key]); if (missing.length) {