Skip to content

Commit 4c5a5c1

Browse files
Merge pull request #125 from amshula-05/feat/unit-test-cases-jasmine-framework
Add comprehensive Jasmine backend test suite: User model, auth routes,Passport integration.
2 parents dde1aeb + a8edf9d commit 4c5a5c1

File tree

5 files changed

+223
-0
lines changed

5 files changed

+223
-0
lines changed

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,58 @@ $ npm start
102102
<img src="https://contrib.rocks/image?repo=mehul-m-prajapati/github_tracker&&max=1000" />
103103
</a>
104104
</div>
105+
106+
## 🧪 Backend Unit & Integration Testing with Jasmine
107+
108+
This project uses the Jasmine framework for backend unit and integration tests. The tests cover:
109+
- User model (password hashing, schema, password comparison)
110+
- Authentication routes (signup, login, logout)
111+
- Passport authentication logic (via integration tests)
112+
113+
### Prerequisites
114+
- **Node.js** and **npm** installed
115+
- **MongoDB** running locally (default: `mongodb://127.0.0.1:27017`)
116+
117+
### Installation
118+
Install all required dependencies:
119+
```sh
120+
npm install
121+
npm install --save-dev jasmine @types/jasmine supertest express-session passport passport-local bcryptjs
122+
```
123+
124+
### Running the Tests
125+
1. **Start MongoDB** (if not already running):
126+
```sh
127+
mongod
128+
```
129+
2. **Run Jasmine tests:**
130+
```sh
131+
npx jasmine
132+
```
133+
134+
### Test Files
135+
- `spec/user.model.spec.cjs` — Unit tests for the User model
136+
- `spec/auth.routes.spec.cjs` — Integration tests for authentication routes
137+
138+
### Jasmine Configuration
139+
The Jasmine config (`spec/support/jasmine.mjs`) is set to recognize `.cjs`, `.js`, and `.mjs` test files:
140+
```js
141+
spec_files: [
142+
"**/*[sS]pec.?(m)js",
143+
"**/*[sS]pec.cjs"
144+
]
145+
```
146+
147+
### Troubleshooting
148+
- **No specs found:** Ensure your test files have the correct extension and are in the `spec/` directory.
149+
- **MongoDB connection errors:** Make sure MongoDB is running and accessible.
150+
- **Missing modules:** Install any missing dev dependencies with `npm install --save-dev <module>`.
151+
152+
### What Was Covered
153+
- Jasmine is set up and configured for backend testing.
154+
- All major backend modules are covered by unit/integration tests.
155+
- Tests are passing and verified.
156+
157+
---
158+
159+
For any questions or to add more tests (including frontend), see the contribution guidelines or open an issue.

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,25 @@
2626
},
2727
"devDependencies": {
2828
"@eslint/js": "^9.13.0",
29+
"@types/jasmine": "^5.1.8",
2930
"@types/node": "^22.10.1",
3031
"@types/react": "^18.3.12",
3132
"@types/react-dom": "^18.3.1",
3233
"@types/react-redux": "^7.1.34",
3334
"@vitejs/plugin-react-swc": "^3.5.0",
3435
"autoprefixer": "^10.4.20",
36+
"bcryptjs": "^3.0.2",
3537
"eslint": "^9.13.0",
3638
"eslint-plugin-react": "^7.37.2",
3739
"eslint-plugin-react-hooks": "^5.0.0",
3840
"eslint-plugin-react-refresh": "^0.4.14",
41+
"express-session": "^1.18.2",
3942
"globals": "^15.11.0",
43+
"jasmine": "^5.9.0",
44+
"passport": "^0.7.0",
45+
"passport-local": "^1.0.0",
4046
"postcss": "^8.4.47",
47+
"supertest": "^7.1.4",
4148
"tailwindcss": "^3.4.14",
4249
"vite": "^5.4.10"
4350
}

spec/auth.routes.spec.cjs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
const mongoose = require('mongoose');
2+
const express = require('express');
3+
const request = require('supertest');
4+
const session = require('express-session');
5+
const passport = require('passport');
6+
const User = require('../backend/models/User');
7+
const authRoutes = require('../backend/routes/auth');
8+
9+
// Setup Express app for testing
10+
function createTestApp() {
11+
const app = express();
12+
app.use(express.json());
13+
app.use(session({ secret: 'test', resave: false, saveUninitialized: false }));
14+
app.use(passport.initialize());
15+
app.use(passport.session());
16+
require('../backend/config/passportConfig');
17+
app.use('/auth', authRoutes);
18+
return app;
19+
}
20+
21+
describe('Auth Routes', () => {
22+
let app;
23+
24+
beforeAll(async () => {
25+
await mongoose.connect('mongodb://127.0.0.1:27017/github_tracker_test', {
26+
useNewUrlParser: true,
27+
useUnifiedTopology: true,
28+
});
29+
app = createTestApp();
30+
});
31+
32+
afterAll(async () => {
33+
await mongoose.connection.db.dropDatabase();
34+
await mongoose.disconnect();
35+
});
36+
37+
afterEach(async () => {
38+
await User.deleteMany({});
39+
});
40+
41+
it('should sign up a new user', async () => {
42+
const res = await request(app)
43+
.post('/auth/signup')
44+
.send({ username: 'testuser', email: 'test@example.com', password: 'password123' });
45+
expect(res.status).toBe(201);
46+
expect(res.body.message).toBe('User created successfully');
47+
const user = await User.findOne({ email: 'test@example.com' });
48+
expect(user).toBeTruthy();
49+
});
50+
51+
it('should not sign up a user with existing email', async () => {
52+
await new User({ username: 'testuser', email: 'test@example.com', password: 'password123' }).save();
53+
const res = await request(app)
54+
.post('/auth/signup')
55+
.send({ username: 'testuser2', email: 'test@example.com', password: 'password456' });
56+
expect(res.status).toBe(400);
57+
expect(res.body.message).toBe('User already exists');
58+
});
59+
60+
it('should login a user with correct credentials', async () => {
61+
await request(app)
62+
.post('/auth/signup')
63+
.send({ username: 'testuser', email: 'test@example.com', password: 'password123' });
64+
const agent = request.agent(app);
65+
const res = await agent
66+
.post('/auth/login')
67+
.send({ email: 'test@example.com', password: 'password123' });
68+
expect(res.status).toBe(200);
69+
expect(res.body.message).toBe('Login successful');
70+
expect(res.body.user.email).toBe('test@example.com');
71+
});
72+
73+
it('should not login a user with wrong password', async () => {
74+
await request(app)
75+
.post('/auth/signup')
76+
.send({ username: 'testuser', email: 'test@example.com', password: 'password123' });
77+
const agent = request.agent(app);
78+
const res = await agent
79+
.post('/auth/login')
80+
.send({ email: 'test@example.com', password: 'wrongpassword' });
81+
expect(res.status).toBe(401);
82+
});
83+
84+
it('should logout a logged-in user', async () => {
85+
await request(app)
86+
.post('/auth/signup')
87+
.send({ username: 'testuser', email: 'test@example.com', password: 'password123' });
88+
const agent = request.agent(app);
89+
await agent
90+
.post('/auth/login')
91+
.send({ email: 'test@example.com', password: 'password123' });
92+
const res = await agent.get('/auth/logout');
93+
expect(res.status).toBe(200);
94+
expect(res.body.message).toBe('Logged out successfully');
95+
});
96+
});

spec/support/jasmine.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export default {
2+
spec_dir: "spec",
3+
spec_files: [
4+
"**/*[sS]pec.?(m)js",
5+
"**/*[sS]pec.cjs"
6+
],
7+
helpers: [
8+
"helpers/**/*.?(m)js"
9+
],
10+
env: {
11+
stopSpecOnExpectationFailure: false,
12+
random: true,
13+
forbidDuplicateNames: true
14+
}
15+
}

spec/user.model.spec.cjs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const mongoose = require('mongoose');
2+
const bcrypt = require('bcryptjs');
3+
const User = require('../backend/models/User');
4+
5+
describe('User Model', () => {
6+
beforeAll(async () => {
7+
await mongoose.connect('mongodb://127.0.0.1:27017/github_tracker_test', {
8+
useNewUrlParser: true,
9+
useUnifiedTopology: true,
10+
});
11+
});
12+
13+
afterAll(async () => {
14+
await mongoose.connection.db.dropDatabase();
15+
await mongoose.disconnect();
16+
});
17+
18+
afterEach(async () => {
19+
await User.deleteMany({});
20+
});
21+
22+
it('should create a user with hashed password', async () => {
23+
const userData = { username: 'testuser', email: 'test@example.com', password: 'password123' };
24+
const user = new User(userData);
25+
await user.save();
26+
expect(user.password).not.toBe(userData.password);
27+
const isMatch = await bcrypt.compare('password123', user.password);
28+
expect(isMatch).toBeTrue();
29+
});
30+
31+
it('should not hash password again if not modified', async () => {
32+
const userData = { username: 'testuser2', email: 'test2@example.com', password: 'password123' };
33+
const user = new User(userData);
34+
await user.save();
35+
const originalHash = user.password;
36+
user.username = 'updateduser';
37+
await user.save();
38+
expect(user.password).toBe(originalHash);
39+
});
40+
41+
it('should compare passwords correctly', async () => {
42+
const userData = { username: 'testuser3', email: 'test3@example.com', password: 'password123' };
43+
const user = new User(userData);
44+
await user.save();
45+
const isMatch = await user.comparePassword('password123');
46+
expect(isMatch).toBeTrue();
47+
const isNotMatch = await user.comparePassword('wrongpassword');
48+
expect(isNotMatch).toBeFalse();
49+
});
50+
});

0 commit comments

Comments
 (0)