feat: Migrate Street and Task models from MongoDB to CouchDB

- Replace Street model with CouchDB-based implementation
- Replace Task model with CouchDB-based implementation
- Update routes to use new model interfaces
- Handle geospatial queries with CouchDB design documents
- Maintain adoption functionality and middleware
- Use denormalized document structure with embedded data
- Update test files to work with new models
- Ensure API compatibility while using CouchDB underneath

🤖 Generated with [AI Assistant]

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
William Valentin
2025-11-01 13:12:34 -07:00
parent 2961107136
commit 7c7bc954ef
14 changed files with 1943 additions and 928 deletions

View File

@@ -2,14 +2,22 @@ const request = require('supertest');
const express = require('express');
const authRoutes = require('../../routes/auth');
const User = require('../../models/User');
const couchdbService = require('../../services/couchdbService');
const { createTestUser } = require('../utils/testHelpers');
// Mock CouchDB service for testing
jest.mock('../../services/couchdbService');
// Create Express app for testing
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
describe('Auth Routes', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('POST /api/auth/register', () => {
it('should register a new user and return a token', async () => {
const userData = {
@@ -18,6 +26,35 @@ describe('Auth Routes', () => {
password: 'password123',
};
// Mock CouchDB responses
couchdbService.findUserByEmail.mockResolvedValue(null);
const mockCreatedUser = {
_id: 'user_123',
_rev: '1-abc',
type: 'user',
...userData,
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.createDocument.mockResolvedValue(mockCreatedUser);
const response = await request(app)
.post('/api/auth/register')
.send(userData)
@@ -25,18 +62,21 @@ describe('Auth Routes', () => {
expect(response.body).toHaveProperty('token');
expect(typeof response.body.token).toBe('string');
expect(response.body.success).toBe(true);
// Verify user was created in database
const user = await User.findOne({ email: userData.email });
expect(user).toBeTruthy();
expect(user.name).toBe(userData.name);
expect(user.email).toBe(userData.email);
expect(user.password).not.toBe(userData.password); // Password should be hashed
// Verify CouchDB service was called correctly
expect(couchdbService.findUserByEmail).toHaveBeenCalledWith(userData.email);
expect(couchdbService.createDocument).toHaveBeenCalled();
});
it('should not register a user with an existing email', async () => {
// Create a user first
await createTestUser({ email: 'existing@example.com' });
const existingUser = {
_id: 'user_123',
email: 'existing@example.com',
name: 'Existing User'
};
couchdbService.findUserByEmail.mockResolvedValue(existingUser);
const userData = {
name: 'Jane Doe',
@@ -50,6 +90,7 @@ describe('Auth Routes', () => {
.expect(400);
expect(response.body).toHaveProperty('msg', 'User already exists');
expect(response.body.success).toBe(false);
});
it('should handle missing required fields', async () => {
@@ -63,15 +104,39 @@ describe('Auth Routes', () => {
});
describe('POST /api/auth/login', () => {
beforeEach(async () => {
// Create a test user before each login test
await createTestUser({
email: 'login@example.com',
password: 'password123',
});
beforeEach(() => {
jest.clearAllMocks();
});
it('should login with valid credentials and return a token', async () => {
const mockUser = {
_id: 'user_123',
_rev: '1-abc',
type: 'user',
name: 'Test User',
email: 'login@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',
comparePassword: jest.fn().mockResolvedValue(true)
};
couchdbService.findUserByEmail.mockResolvedValue(mockUser);
const loginData = {
email: 'login@example.com',
password: 'password123',
@@ -84,9 +149,12 @@ describe('Auth Routes', () => {
expect(response.body).toHaveProperty('token');
expect(typeof response.body.token).toBe('string');
expect(response.body.success).toBe(true);
});
it('should not login with invalid email', async () => {
couchdbService.findUserByEmail.mockResolvedValue(null);
const loginData = {
email: 'nonexistent@example.com',
password: 'password123',
@@ -98,9 +166,19 @@ describe('Auth Routes', () => {
.expect(400);
expect(response.body).toHaveProperty('msg', 'Invalid credentials');
expect(response.body.success).toBe(false);
});
it('should not login with invalid password', async () => {
const mockUser = {
_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',
@@ -112,6 +190,7 @@ describe('Auth Routes', () => {
.expect(400);
expect(response.body).toHaveProperty('msg', 'Invalid credentials');
expect(response.body.success).toBe(false);
});
it('should handle missing email or password', async () => {
@@ -126,16 +205,55 @@ describe('Auth Routes', () => {
describe('GET /api/auth', () => {
it('should get authenticated user with valid token', async () => {
const { user, token } = await createTestUser();
const mockUser = {
_id: 'user_123',
_rev: '1-abc',
type: 'user',
name: 'Test User',
email: 'test@example.com',
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',
toSafeObject: jest.fn().mockReturnValue({
_id: 'user_123',
name: 'Test User',
email: 'test@example.com',
isPremium: false,
points: 0
})
};
couchdbService.findUserById.mockResolvedValue(mockUser);
// Create a valid token
const jwt = require('jsonwebtoken');
const token = jwt.sign(
{ user: { id: 'user_123' } },
process.env.JWT_SECRET,
{ expiresIn: 3600 }
);
const response = await request(app)
.get('/api/auth')
.set('x-auth-token', token)
.expect(200);
expect(response.body).toHaveProperty('_id', user.id);
expect(response.body).toHaveProperty('name', user.name);
expect(response.body).toHaveProperty('email', user.email);
expect(response.body).toHaveProperty('_id', 'user_123');
expect(response.body).toHaveProperty('name', 'Test User');
expect(response.body).toHaveProperty('email', 'test@example.com');
expect(response.body).not.toHaveProperty('password');
});