Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 46 additions & 24 deletions backend/config/redis.js
Original file line number Diff line number Diff line change
@@ -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");
Comment thread
yash-pouranik marked this conversation as resolved.
}
}

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;
}
40 changes: 19 additions & 21 deletions backend/tests/auth.test.js
Original file line number Diff line number Diff line change
@@ -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(),
Expand All @@ -21,6 +27,9 @@ jest.mock('ioredis', () => {
}));
});

/**
* Mock email service
*/
jest.mock('resend', () => {
return {
Resend: jest.fn().mockImplementation(() => ({
Expand All @@ -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);

Expand All @@ -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({});
});
Expand All @@ -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();
});
Expand Down
11 changes: 11 additions & 0 deletions backend/tests/health.test.js
Original file line number Diff line number Diff line change
@@ -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");
});
});
Comment on lines +1 to +11

@coderabbitai coderabbitai Bot Feb 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find all test files in backend/tests
fd '.test.js' --base-directory backend/tests 2>/dev/null || find backend/tests -name '*.test.js' 2>/dev/null

# Check app.js for app.listen and exports
echo "=== Checking app.js for listen/exports ==="
rg -n 'app\.listen|module\.exports\s*=|exports\s*=' backend/app.js

# Look for auth-related test files
echo "=== Looking for auth test files ==="
fd -e js --base-directory backend/tests | grep -i auth

Repository: yash-pouranik/urBackend

Length of output: 260


🏁 Script executed:

# Check the context around app.listen() call
sed -n '125,145p' backend/app.js

# Check if there's any conditional wrapping around listen
sed -n '120,170p' backend/app.js | head -50

# Read auth.test.js to see what it tests
cat backend/tests/auth.test.js

Repository: yash-pouranik/urBackend

Length of output: 5194


Add afterAll hook to close the server and prevent port conflicts.

The health check test is correct, but it leaves the server running. Since app.js unconditionally calls app.listen(PORT) on require (line 132), running multiple test files in the same suite can cause port binding conflicts or open-handle warnings in Jest.

Add an afterAll hook to close the server:

Required addition
 const request = require("supertest");
 const app = require("../app");
 
 describe("Health Check API", () => {
+  let server;
+  afterAll((done) => {
+    if (server) server.close(done);
+    else done();
+  });
+
   it("should return 200 and success message", async () => {
-    const res = await request(app).get("/");
+    server = app._server || app;
+    const res = await request(app).get("/");
 
     expect(res.statusCode).toBe(200);
     expect(res.body.status).toBe("success");
   });
 });

Alternatively, refactor app.js to defer app.listen() until explicitly called (e.g., only in production or when not being required by tests), which is a cleaner long-term fix.

🧰 Tools
🪛 GitHub Actions: CI (Continuous Integration)

[error] 1-1: Test suite failed: Auth API login expected 200 but received 500 during CI run. Command: 'cd backend && npm test'.

🤖 Prompt for AI Agents
In `@backend/tests/health.test.js` around lines 1 - 11, The test leaves the server
running because app.js calls app.listen on require; add an afterAll hook that
closes the server to avoid port conflicts. Change the test to require the server
instance (e.g., const server = require("../app");) or adjust app.js to export
the server returned by app.listen (keep symbol name server), then add
afterAll(async () => server.close()); (or call done/return a promise) to
properly close the server after tests; reference afterAll, app, server, and
app.listen when making the change.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alok-kumar0421 resolve this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

1 change: 1 addition & 0 deletions backend/utils/validateEnv.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const requiredEnvVars = ["MONGO_URL", "PORT", "REDIS_URL"];

function validateEnv() {
if (process.env.NODE_ENV === "test") return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alok-kumar0421 look into ./backend/app.js i prevented the validateEnv function call if env is test
so i think you can remove this. hmm??

const missing = requiredEnvVars.filter((key) => !process.env[key]);

if (missing.length) {
Expand Down