diff --git a/backend/models/User.js b/backend/models/User.js index aeb0951..eb506ed 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -2,33 +2,33 @@ const mongoose = require("mongoose"); const bcrypt = require("bcryptjs"); const UserSchema = new mongoose.Schema({ - username: { - type: String, - required: true, - unique: true, - }, - email: { - type: String, - required: true, - unique: true, - }, - password: { - type: String, - required: true, - }, + username: { + type: String, + required: true, + unique: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, }); +// ✅ FIXED: no next() UserSchema.pre('save', async function () { - if (!this.isModified('password')) - return; + if (!this.isModified('password')) return; - const salt = await bcrypt.genSalt(10); - this.password = await bcrypt.hash(this.password, salt); + const salt = await bcrypt.genSalt(10); + this.password = await bcrypt.hash(this.password, salt); }); -// Compare passwords during login +// ✅ password comparison UserSchema.methods.comparePassword = async function (enteredPassword) { - return await bcrypt.compare(enteredPassword, this.password); + return bcrypt.compare(enteredPassword, this.password); }; -module.exports = mongoose.model("User", UserSchema); +module.exports = mongoose.model("User", UserSchema); \ No newline at end of file diff --git a/package.json b/package.json index cfb43ca..c9bc260 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test:backend": "jasmine spec/**/*.spec.cjs", "preview": "vite preview", "docker:dev": "docker compose --profile dev up --build", - "docker:prod": "docker compose --profile prod up -d --build" + "docker:prod": "docker compose --profile prod up -d --build", + "test": "jasmine" }, "dependencies": { "@emotion/react": "^11.11.3", @@ -21,8 +22,10 @@ "@primer/octicons-react": "^19.25.0", "@vitejs/plugin-react": "^4.3.3", "axios": "^1.7.7", + "express": "^5.2.1", "framer-motion": "^12.23.12", "lucide-react": "^0.525.0", + "mongoose": "^9.6.2", "octokit": "^4.0.2", "postcss": "^8.4.47", "react": "^18.3.1", @@ -45,7 +48,7 @@ "@types/react-router-dom": "^5.3.3", "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.20", - "bcryptjs": "^3.0.2", + "bcryptjs": "^3.0.3", "eslint": "^9.13.0", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", diff --git a/spec/auth.routes.spec.cjs b/spec/auth.routes.spec.cjs index 926b5c7..2b29c03 100644 --- a/spec/auth.routes.spec.cjs +++ b/spec/auth.routes.spec.cjs @@ -3,18 +3,32 @@ const express = require('express'); const request = require('supertest'); const session = require('express-session'); const passport = require('passport'); + const User = require('../backend/models/User'); const authRoutes = require('../backend/routes/auth'); -// Setup Express app for testing +// Create test app function createTestApp() { const app = express(); + app.use(express.json()); - app.use(session({ secret: 'test', resave: false, saveUninitialized: false })); + + app.use( + session({ + secret: 'test-secret', + resave: false, + saveUninitialized: false, + }) + ); + app.use(passport.initialize()); app.use(passport.session()); + + // Load passport config AFTER initializing passport require('../backend/config/passportConfig'); + app.use('/auth', authRoutes); + return app; } @@ -27,76 +41,107 @@ describe('Auth Routes', () => { }); afterAll(async () => { - await mongoose.connection.db.dropDatabase(); - await mongoose.disconnect(); + if (mongoose.connection.readyState === 1) { + await mongoose.connection.db.dropDatabase(); + await mongoose.disconnect(); + } }); afterEach(async () => { await User.deleteMany({}); }); + // ---------------- SIGNUP ---------------- it('should sign up a new user', async () => { const res = await request(app) .post('/auth/signup') - .send({ username: 'testuser', email: 'test@example.com', password: 'password123' }); + .send({ + username: 'testuser', + email: 'test@example.com', + password: 'password123', + }); + expect(res.status).toBe(201); expect(res.body.message).toBe('User created successfully'); + const user = await User.findOne({ email: 'test@example.com' }); expect(user).toBeTruthy(); }); it('should not sign up a user with existing email', async () => { - await new User({ username: 'testuser', email: 'test@example.com', password: 'password123' }).save(); - const res = await request(app) - .post('/auth/signup') - .send({ username: 'testuser2', email: 'test@example.com', password: 'password456' }); - expect(res.status).toBe(400); - expect(res.body.message).toBe('User already exists'); - }); + await User.create({ + username: 'testuser', + email: 'test@example.com', + password: 'password123', + }); - it('should not sign up a user with existing username', async () => { - await new User({ username: 'testuser', email: 'test@example.com', password: 'password123' }).save(); const res = await request(app) .post('/auth/signup') - .send({ username: 'testuser', email: 'test2@example.com', password: 'password456' }); + .send({ + username: 'testuser2', + email: 'test@example.com', + password: 'password456', + }); + expect(res.status).toBe(400); expect(res.body.message).toBe('User already exists'); }); + // ---------------- LOGIN ---------------- it('should login a user with correct credentials', async () => { - await request(app) - .post('/auth/signup') - .send({ username: 'testuser', email: 'test@example.com', password: 'password123' }); + await User.create({ + username: 'testuser', + email: 'test@example.com', + password: 'password123', + }); + const agent = request.agent(app); - const res = await agent - .post('/auth/login') - .send({ email: 'test@example.com', password: 'password123' }); + + const res = await agent.post('/auth/login').send({ + email: 'test@example.com', + password: 'password123', + }); + expect(res.status).toBe(200); expect(res.body.message).toBe('Login successful'); expect(res.body.user.email).toBe('test@example.com'); }); it('should not login a user with wrong password', async () => { - await request(app) - .post('/auth/signup') - .send({ username: 'testuser', email: 'test@example.com', password: 'password123' }); + await User.create({ + username: 'testuser', + email: 'test@example.com', + password: 'password123', + }); + const agent = request.agent(app); - const res = await agent - .post('/auth/login') - .send({ email: 'test@example.com', password: 'wrongpassword' }); + + const res = await agent.post('/auth/login').send({ + email: 'test@example.com', + password: 'wrongpassword', + }); + expect(res.status).toBe(401); }); + // ---------------- LOGOUT ---------------- it('should logout a logged-in user', async () => { - await request(app) - .post('/auth/signup') - .send({ username: 'testuser', email: 'test@example.com', password: 'password123' }); const agent = request.agent(app); - await agent - .post('/auth/login') - .send({ email: 'test@example.com', password: 'password123' }); + + await agent.post('/auth/signup').send({ + username: 'testuser', + email: 'test@example.com', + password: 'password123', + }); + + await agent.post('/auth/login').send({ + email: 'test@example.com', + password: 'password123', + }); + const res = await agent.get('/auth/logout'); + expect(res.status).toBe(200); expect(res.body.message).toBe('Logged out successfully'); }); -}); \ No newline at end of file +}); \ No newline at end of file diff --git a/spec/user.model.spec.cjs b/spec/user.model.spec.cjs index 79932bb..e256e63 100644 --- a/spec/user.model.spec.cjs +++ b/spec/user.model.spec.cjs @@ -8,40 +8,61 @@ describe('User Model', () => { }); afterAll(async () => { - await mongoose.connection.db.dropDatabase(); - await mongoose.disconnect(); + if (mongoose.connection.readyState === 1) { + await mongoose.connection.db.dropDatabase(); + await mongoose.disconnect(); + } }); afterEach(async () => { await User.deleteMany({}); }); - it('should create a user with hashed password', async () => { - const userData = { username: 'testuser', email: 'test@example.com', password: 'password123' }; - const user = new User(userData); + // -------- CREATE USER -------- + it('should hash password before saving user', async () => { + const user = new User({ + username: 'testuser', + email: 'test@example.com', + password: 'password123', + }); + await user.save(); - expect(user.password).not.toBe(userData.password); + + // password should not be plain text + expect(user.password).not.toBe('password123'); + const isMatch = await bcrypt.compare('password123', user.password); - expect(isMatch).toBeTrue(); + expect(isMatch).toBe(true); }); - it('should not hash password again if not modified', async () => { - const userData = { username: 'testuser2', email: 'test2@example.com', password: 'password123' }; - const user = new User(userData); - await user.save(); + // -------- PASSWORD NOT RE-HASHED -------- + it('should not re-hash password if not modified', async () => { + const user = await User.create({ + username: 'testuser2', + email: 'test2@example.com', + password: 'password123', + }); + const originalHash = user.password; + user.username = 'updateduser'; await user.save(); + expect(user.password).toBe(originalHash); }); - it('should compare passwords correctly', async () => { - const userData = { username: 'testuser3', email: 'test3@example.com', password: 'password123' }; - const user = new User(userData); - await user.save(); + // -------- COMPARE PASSWORD -------- + it('should correctly compare passwords', async () => { + const user = await User.create({ + username: 'testuser3', + email: 'test3@example.com', + password: 'password123', + }); + const isMatch = await user.comparePassword('password123'); - expect(isMatch).toBeTrue(); const isNotMatch = await user.comparePassword('wrongpassword'); - expect(isNotMatch).toBeFalse(); + + expect(isMatch).toBe(true); + expect(isNotMatch).toBe(false); }); -}); \ No newline at end of file +}); \ No newline at end of file