Files
adopt-a-street/backend/__tests__/jest.preSetup.js
William Valentin 3e4c730860 feat: implement comprehensive gamification, analytics, and leaderboard system
This commit adds a complete gamification system with analytics dashboards,
leaderboards, and enhanced badge tracking functionality.

Backend Features:
- Analytics API with overview, user stats, activity trends, top contributors,
  and street statistics endpoints
- Leaderboard API supporting global, weekly, monthly, and friends views
- Profile API for viewing and managing user profiles
- Enhanced gamification service with badge progress tracking and user stats
- Comprehensive test coverage for analytics and leaderboard endpoints
- Profile validation middleware for secure profile updates

Frontend Features:
- Analytics dashboard with multiple tabs (Overview, Activity, Personal Stats)
- Interactive charts for activity trends and street statistics
- Leaderboard component with pagination and timeframe filtering
- Badge collection display with progress tracking
- Personal stats component showing user achievements
- Contributors list for top performing users
- Profile management components (View/Edit)
- Toast notifications integrated throughout
- Comprehensive test coverage for Leaderboard component

Enhancements:
- User model enhanced with stats tracking and badge management
- Fixed express.Router() capitalization bug in users route
- Badge service improvements for better criteria matching
- Removed unused imports in Profile component

This feature enables users to track their contributions, view community
analytics, compete on leaderboards, and earn badges for achievements.

🤖 Generated with OpenCode

Co-Authored-By: AI Assistant <noreply@opencode.ai>
2025-11-03 13:53:48 -08:00

85 lines
3.0 KiB
JavaScript

// This file runs before any modules are loaded
// Set test environment variables FIRST (before any module loads)
// Must be at least 32 chars for validation
process.env.NODE_ENV = 'test';
process.env.JWT_SECRET = 'test-jwt-secret-for-testing-purposes-that-is-long-enough';
process.env.COUCHDB_URL = 'http://localhost:5984';
process.env.COUCHDB_DB_NAME = 'test-adopt-a-street';
process.env.PORT = '5001';
// Mock dotenv to prevent .env file from overriding test values
jest.mock('dotenv', () => ({
config: jest.fn()
}));
// Mock axios first since couchdbService uses it
jest.mock('axios', () => ({
create: jest.fn(() => ({
get: jest.fn().mockResolvedValue({ data: {} }),
put: jest.fn().mockResolvedValue({ data: { ok: true } }),
post: jest.fn().mockResolvedValue({ data: { ok: true } }),
delete: jest.fn().mockResolvedValue({ data: { ok: true } }),
})),
get: jest.fn().mockResolvedValue({ data: {} }),
put: jest.fn().mockResolvedValue({ data: { ok: true } }),
post: jest.fn().mockResolvedValue({ data: { ok: true } }),
delete: jest.fn().mockResolvedValue({ data: { ok: true } }),
}));
// Mock CouchDB service at the module level to prevent real service from loading
jest.mock('../services/couchdbService', () => ({
initialize: jest.fn().mockResolvedValue(true),
isReady: jest.fn().mockReturnValue(true),
isConnected: true,
isConnecting: false,
create: jest.fn(),
getById: jest.fn(),
get: jest.fn(),
find: jest.fn(),
destroy: jest.fn(),
delete: jest.fn(),
createDocument: jest.fn().mockImplementation((doc) => Promise.resolve({
_id: `test_${Date.now()}`,
_rev: '1-test',
...doc
})),
updateDocument: jest.fn().mockImplementation((doc) => Promise.resolve({
...doc,
_rev: '2-test'
})),
deleteDocument: jest.fn().mockResolvedValue(true),
findByType: jest.fn().mockResolvedValue([]),
findUserById: jest.fn(),
findUserByEmail: jest.fn(),
update: jest.fn(),
updateUserPoints: jest.fn().mockResolvedValue(true),
getDocument: jest.fn(),
findDocumentById: jest.fn(),
bulkDocs: jest.fn().mockResolvedValue([{ ok: true, id: 'test', rev: '1-test' }]),
insertMany: jest.fn().mockResolvedValue([]),
deleteMany: jest.fn().mockResolvedValue(true),
findStreetsByLocation: jest.fn().mockResolvedValue([]),
generateId: jest.fn().mockImplementation((type, id) => `${type}_${id}`),
extractOriginalId: jest.fn().mockImplementation((prefixedId) => prefixedId.split('_').slice(1).join('_')),
validateDocument: jest.fn().mockReturnValue([]),
getDB: jest.fn().mockReturnValue({}),
shutdown: jest.fn().mockResolvedValue(true),
}), { virtual: true });
// Mock Cloudinary
jest.mock('cloudinary', () => ({
v2: {
config: jest.fn(),
uploader: {
upload: jest.fn().mockResolvedValue({
secure_url: 'https://cloudinary.com/test/image.jpg',
public_id: 'test_public_id',
width: 500,
height: 500,
format: 'jpg'
}),
destroy: jest.fn().mockResolvedValue({ result: 'ok' })
}
}
}));