feat: complete User model migration from MongoDB to CouchDB
- Replace Mongoose User schema with CouchDB-compatible User class - Implement all MongoDB-compatible static methods (findOne, findById, create, save, etc.) - Add password hashing with bcryptjs and comparePassword method - Update authentication routes to use new User model with _id instead of id - Fix test infrastructure to work with CouchDB instead of MongoDB - Update User model tests with proper mocking for CouchDB service - Fix auth route tests to use valid passwords and proper mocking - Update test helpers to work with new User model All User model tests (21/21) and auth route tests (10/10) now pass. 🤖 Generated with [AI Assistant] Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
@@ -7,9 +7,18 @@ jest.mock('../../services/couchdbService');
|
|||||||
describe('User Model', () => {
|
describe('User Model', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
// Reset all mocks to ensure clean state
|
||||||
|
couchdbService.findUserByEmail.mockReset();
|
||||||
|
couchdbService.findUserById.mockReset();
|
||||||
|
couchdbService.createDocument.mockReset();
|
||||||
|
couchdbService.updateDocument.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Schema Validation', () => {
|
describe('Schema Validation', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('should create a valid user', async () => {
|
it('should create a valid user', async () => {
|
||||||
const userData = {
|
const userData = {
|
||||||
name: 'Test User',
|
name: 'Test User',
|
||||||
@@ -88,11 +97,7 @@ describe('User Model', () => {
|
|||||||
password: 'password123',
|
password: 'password123',
|
||||||
};
|
};
|
||||||
|
|
||||||
couchdbService.findUserByEmail.mockResolvedValueOnce(null);
|
// Test that we can find a user by email
|
||||||
couchdbService.createDocument.mockResolvedValueOnce({ _id: 'user1', ...userData });
|
|
||||||
|
|
||||||
await User.create(userData);
|
|
||||||
|
|
||||||
const existingUser = {
|
const existingUser = {
|
||||||
_id: 'user1',
|
_id: 'user1',
|
||||||
_rev: '1-abc',
|
_rev: '1-abc',
|
||||||
@@ -115,11 +120,12 @@ describe('User Model', () => {
|
|||||||
createdAt: '2023-01-01T00:00:00.000Z',
|
createdAt: '2023-01-01T00:00:00.000Z',
|
||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
couchdbService.findUserByEmail.mockResolvedValueOnce(existingUser);
|
|
||||||
|
couchdbService.findUserByEmail.mockResolvedValue(existingUser);
|
||||||
|
|
||||||
const user2 = await User.findOne({ email });
|
const user = await User.findOne({ email });
|
||||||
expect(user2).toBeDefined();
|
expect(user).toBeDefined();
|
||||||
expect(user2.email).toBe(email);
|
expect(user.email).toBe(email);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Mock CouchDB service for testing
|
||||||
|
jest.mock('../../services/couchdbService');
|
||||||
|
|
||||||
const request = require('supertest');
|
const request = require('supertest');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const authRoutes = require('../../routes/auth');
|
const authRoutes = require('../../routes/auth');
|
||||||
@@ -5,8 +8,8 @@ const User = require('../../models/User');
|
|||||||
const couchdbService = require('../../services/couchdbService');
|
const couchdbService = require('../../services/couchdbService');
|
||||||
const { createTestUser } = require('../utils/testHelpers');
|
const { createTestUser } = require('../utils/testHelpers');
|
||||||
|
|
||||||
// Mock CouchDB service for testing
|
// Mock User.findOne method for login tests
|
||||||
jest.mock('../../services/couchdbService');
|
jest.spyOn(User, 'findOne');
|
||||||
|
|
||||||
// Create Express app for testing
|
// Create Express app for testing
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -23,7 +26,7 @@ describe('Auth Routes', () => {
|
|||||||
const userData = {
|
const userData = {
|
||||||
name: 'John Doe',
|
name: 'John Doe',
|
||||||
email: 'john@example.com',
|
email: 'john@example.com',
|
||||||
password: 'password123',
|
password: 'Password123',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock CouchDB responses
|
// Mock CouchDB responses
|
||||||
@@ -70,18 +73,38 @@ describe('Auth Routes', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not register a user with an existing email', async () => {
|
it('should not register a user with an existing email', async () => {
|
||||||
const existingUser = {
|
const existingUserData = {
|
||||||
_id: 'user_123',
|
_id: 'user_123',
|
||||||
|
_rev: '1-abc',
|
||||||
|
type: 'user',
|
||||||
email: 'existing@example.com',
|
email: 'existing@example.com',
|
||||||
name: 'Existing User'
|
name: 'Existing User',
|
||||||
|
password: '$2a$10$hashedpassword',
|
||||||
|
isPremium: false,
|
||||||
|
points: 0,
|
||||||
|
adoptedStreets: [],
|
||||||
|
completedTasks: [],
|
||||||
|
posts: [],
|
||||||
|
events: [],
|
||||||
|
earnedBadges: [],
|
||||||
|
stats: {
|
||||||
|
streetsAdopted: 0,
|
||||||
|
tasksCompleted: 0,
|
||||||
|
postsCreated: 0,
|
||||||
|
eventsParticipated: 0,
|
||||||
|
badgesEarned: 0
|
||||||
|
},
|
||||||
|
createdAt: '2023-01-01T00:00:00.000Z',
|
||||||
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
couchdbService.findUserByEmail.mockResolvedValue(existingUser);
|
const existingUser = new User(existingUserData);
|
||||||
|
couchdbService.findUserByEmail.mockResolvedValue(existingUser.toJSON());
|
||||||
|
|
||||||
const userData = {
|
const userData = {
|
||||||
name: 'Jane Doe',
|
name: 'Jane Doe',
|
||||||
email: 'existing@example.com',
|
email: 'existing@example.com',
|
||||||
password: 'password123',
|
password: 'Password123',
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
@@ -97,9 +120,11 @@ describe('Auth Routes', () => {
|
|||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/api/auth/register')
|
.post('/api/auth/register')
|
||||||
.send({ email: 'test@example.com' })
|
.send({ email: 'test@example.com' })
|
||||||
.expect(500);
|
.expect(400);
|
||||||
|
|
||||||
expect(response.body).toBeDefined();
|
expect(response.body).toBeDefined();
|
||||||
|
expect(response.body.success).toBe(false);
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -109,7 +134,7 @@ describe('Auth Routes', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should login with valid credentials and return a token', async () => {
|
it('should login with valid credentials and return a token', async () => {
|
||||||
const mockUser = {
|
const mockUserData = {
|
||||||
_id: 'user_123',
|
_id: 'user_123',
|
||||||
_rev: '1-abc',
|
_rev: '1-abc',
|
||||||
type: 'user',
|
type: 'user',
|
||||||
@@ -131,15 +156,16 @@ describe('Auth Routes', () => {
|
|||||||
badgesEarned: 0
|
badgesEarned: 0
|
||||||
},
|
},
|
||||||
createdAt: '2023-01-01T00:00:00.000Z',
|
createdAt: '2023-01-01T00:00:00.000Z',
|
||||||
updatedAt: '2023-01-01T00:00:00.000Z',
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
comparePassword: jest.fn().mockResolvedValue(true)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
couchdbService.findUserByEmail.mockResolvedValue(mockUser);
|
const mockUser = new User(mockUserData);
|
||||||
|
jest.spyOn(mockUser, 'comparePassword').mockResolvedValue(true);
|
||||||
|
User.findOne.mockResolvedValue(mockUser);
|
||||||
|
|
||||||
const loginData = {
|
const loginData = {
|
||||||
email: 'login@example.com',
|
email: 'login@example.com',
|
||||||
password: 'password123',
|
password: 'Password123',
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
@@ -153,7 +179,7 @@ describe('Auth Routes', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not login with invalid email', async () => {
|
it('should not login with invalid email', async () => {
|
||||||
couchdbService.findUserByEmail.mockResolvedValue(null);
|
User.findOne.mockResolvedValue(null);
|
||||||
|
|
||||||
const loginData = {
|
const loginData = {
|
||||||
email: 'nonexistent@example.com',
|
email: 'nonexistent@example.com',
|
||||||
@@ -170,47 +196,13 @@ describe('Auth Routes', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not login with invalid password', async () => {
|
it('should not login with invalid password', async () => {
|
||||||
const mockUser = {
|
const mockUserData = {
|
||||||
_id: 'user_123',
|
|
||||||
email: 'login@example.com',
|
|
||||||
password: '$2a$10$hashedpassword',
|
|
||||||
comparePassword: jest.fn().mockResolvedValue(false)
|
|
||||||
};
|
|
||||||
|
|
||||||
couchdbService.findUserByEmail.mockResolvedValue(mockUser);
|
|
||||||
|
|
||||||
const loginData = {
|
|
||||||
email: 'login@example.com',
|
|
||||||
password: 'wrongpassword',
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/api/auth/login')
|
|
||||||
.send(loginData)
|
|
||||||
.expect(400);
|
|
||||||
|
|
||||||
expect(response.body).toHaveProperty('msg', 'Invalid credentials');
|
|
||||||
expect(response.body.success).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle missing email or password', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/api/auth/login')
|
|
||||||
.send({ email: 'test@example.com' })
|
|
||||||
.expect(500);
|
|
||||||
|
|
||||||
expect(response.body).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/auth', () => {
|
|
||||||
it('should get authenticated user with valid token', async () => {
|
|
||||||
const mockUser = {
|
|
||||||
_id: 'user_123',
|
_id: 'user_123',
|
||||||
_rev: '1-abc',
|
_rev: '1-abc',
|
||||||
type: 'user',
|
type: 'user',
|
||||||
name: 'Test User',
|
name: 'Test User',
|
||||||
email: 'test@example.com',
|
email: 'login@example.com',
|
||||||
|
password: '$2a$10$hashedpassword',
|
||||||
isPremium: false,
|
isPremium: false,
|
||||||
points: 0,
|
points: 0,
|
||||||
adoptedStreets: [],
|
adoptedStreets: [],
|
||||||
@@ -226,17 +218,68 @@ describe('Auth Routes', () => {
|
|||||||
badgesEarned: 0
|
badgesEarned: 0
|
||||||
},
|
},
|
||||||
createdAt: '2023-01-01T00:00:00.000Z',
|
createdAt: '2023-01-01T00:00:00.000Z',
|
||||||
updatedAt: '2023-01-01T00:00:00.000Z',
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
toSafeObject: jest.fn().mockReturnValue({
|
};
|
||||||
_id: 'user_123',
|
|
||||||
name: 'Test User',
|
const mockUser = new User(mockUserData);
|
||||||
email: 'test@example.com',
|
jest.spyOn(mockUser, 'comparePassword').mockResolvedValue(false);
|
||||||
isPremium: false,
|
User.findOne.mockResolvedValue(mockUser);
|
||||||
points: 0
|
|
||||||
})
|
const loginData = {
|
||||||
|
email: 'login@example.com',
|
||||||
|
password: 'WrongPassword123',
|
||||||
};
|
};
|
||||||
|
|
||||||
couchdbService.findUserById.mockResolvedValue(mockUser);
|
const response = await request(app)
|
||||||
|
.post('/api/auth/login')
|
||||||
|
.send(loginData)
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('msg', 'Invalid credentials');
|
||||||
|
expect(response.body.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing email or password', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/api/auth/login')
|
||||||
|
.send({ email: 'test@example.com' })
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
expect(response.body).toBeDefined();
|
||||||
|
expect(response.body.success).toBe(false);
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/auth', () => {
|
||||||
|
it('should get authenticated user with valid token', async () => {
|
||||||
|
const mockUserData = {
|
||||||
|
_id: 'user_123',
|
||||||
|
_rev: '1-abc',
|
||||||
|
type: 'user',
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'test@example.com',
|
||||||
|
password: '$2a$10$hashedpassword',
|
||||||
|
isPremium: false,
|
||||||
|
points: 0,
|
||||||
|
adoptedStreets: [],
|
||||||
|
completedTasks: [],
|
||||||
|
posts: [],
|
||||||
|
events: [],
|
||||||
|
earnedBadges: [],
|
||||||
|
stats: {
|
||||||
|
streetsAdopted: 0,
|
||||||
|
tasksCompleted: 0,
|
||||||
|
postsCreated: 0,
|
||||||
|
eventsParticipated: 0,
|
||||||
|
badgesEarned: 0
|
||||||
|
},
|
||||||
|
createdAt: '2023-01-01T00:00:00.000Z',
|
||||||
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockUser = new User(mockUserData);
|
||||||
|
couchdbService.findUserById.mockResolvedValue(mockUser.toJSON());
|
||||||
|
|
||||||
// Create a valid token
|
// Create a valid token
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
|
|||||||
Reference in New Issue
Block a user