Skip to content
Merged
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
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,58 @@ $ npm start
<img src="https://contrib.rocks/image?repo=mehul-m-prajapati/github_tracker&&max=1000" />
</a>
</div>

## 🧪 Backend Unit & Integration Testing with Jasmine

This project uses the Jasmine framework for backend unit and integration tests. The tests cover:
- User model (password hashing, schema, password comparison)
- Authentication routes (signup, login, logout)
- Passport authentication logic (via integration tests)

### Prerequisites
- **Node.js** and **npm** installed
- **MongoDB** running locally (default: `mongodb://127.0.0.1:27017`)

### Installation
Install all required dependencies:
```sh
npm install
npm install --save-dev jasmine @types/jasmine supertest express-session passport passport-local bcryptjs
```

### Running the Tests
1. **Start MongoDB** (if not already running):
```sh
mongod
```
2. **Run Jasmine tests:**
```sh
npx jasmine
```

### Test Files
- `spec/user.model.spec.cjs` — Unit tests for the User model
- `spec/auth.routes.spec.cjs` — Integration tests for authentication routes

### Jasmine Configuration
The Jasmine config (`spec/support/jasmine.mjs`) is set to recognize `.cjs`, `.js`, and `.mjs` test files:
```js
spec_files: [
"**/*[sS]pec.?(m)js",
"**/*[sS]pec.cjs"
]
```

### Troubleshooting
- **No specs found:** Ensure your test files have the correct extension and are in the `spec/` directory.
- **MongoDB connection errors:** Make sure MongoDB is running and accessible.
- **Missing modules:** Install any missing dev dependencies with `npm install --save-dev <module>`.

### What Was Covered
- Jasmine is set up and configured for backend testing.
- All major backend modules are covered by unit/integration tests.
- Tests are passing and verified.

---

For any questions or to add more tests (including frontend), see the contribution guidelines or open an issue.
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,25 @@
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@types/jasmine": "^5.1.8",
"@types/node": "^22.10.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-redux": "^7.1.34",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.20",
"bcryptjs": "^3.0.2",
"eslint": "^9.13.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"express-session": "^1.18.2",
"globals": "^15.11.0",
"jasmine": "^5.9.0",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"postcss": "^8.4.47",
"supertest": "^7.1.4",
"tailwindcss": "^3.4.14",
"vite": "^5.4.10"
}
Expand Down
96 changes: 96 additions & 0 deletions spec/auth.routes.spec.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const mongoose = require('mongoose');
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
function createTestApp() {
const app = express();
app.use(express.json());
app.use(session({ secret: 'test', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
require('../backend/config/passportConfig');
app.use('/auth', authRoutes);
return app;
}

describe('Auth Routes', () => {
let app;

beforeAll(async () => {
await mongoose.connect('mongodb://127.0.0.1:27017/github_tracker_test', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
app = createTestApp();
});

afterAll(async () => {
await mongoose.connection.db.dropDatabase();
await mongoose.disconnect();
});

afterEach(async () => {
await User.deleteMany({});
});

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' });
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');
});

it('should login a user with correct credentials', async () => {
await request(app)
.post('/auth/signup')
.send({ 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' });
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' });
const agent = request.agent(app);
const res = await agent
.post('/auth/login')
.send({ email: 'test@example.com', password: 'wrongpassword' });
expect(res.status).toBe(401);
});

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' });
const res = await agent.get('/auth/logout');
expect(res.status).toBe(200);
expect(res.body.message).toBe('Logged out successfully');
});
});
15 changes: 15 additions & 0 deletions spec/support/jasmine.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default {
spec_dir: "spec",
spec_files: [
"**/*[sS]pec.?(m)js",
"**/*[sS]pec.cjs"
],
helpers: [
"helpers/**/*.?(m)js"
],
env: {
stopSpecOnExpectationFailure: false,
random: true,
forbidDuplicateNames: true
}
}
50 changes: 50 additions & 0 deletions spec/user.model.spec.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const User = require('../backend/models/User');

describe('User Model', () => {
beforeAll(async () => {
await mongoose.connect('mongodb://127.0.0.1:27017/github_tracker_test', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
});

afterAll(async () => {
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);
await user.save();
expect(user.password).not.toBe(userData.password);
const isMatch = await bcrypt.compare('password123', user.password);
expect(isMatch).toBeTrue();
});

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();
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();
const isMatch = await user.comparePassword('password123');
expect(isMatch).toBeTrue();
const isNotMatch = await user.comparePassword('wrongpassword');
expect(isNotMatch).toBeFalse();
});
});