complete project using prettier!

This commit is contained in:
2025-12-22 09:45:14 -08:00
parent 621d30b84f
commit a10f84aa48
339 changed files with 18041 additions and 8969 deletions

View File

@@ -2,7 +2,12 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import supertest from 'supertest';
import type { Request, Response, NextFunction } from 'express';
import { createMockUserProfile, createMockAchievement, createMockUserAchievement, createMockLeaderboardUser } from '../tests/utils/mockFactories';
import {
createMockUserProfile,
createMockAchievement,
createMockUserAchievement,
createMockLeaderboardUser,
} from '../tests/utils/mockFactories';
import { mockLogger } from '../tests/utils/mockLogger';
import { ForeignKeyConstraintError } from '../services/db/errors.db';
import { createTestApp } from '../tests/utils/createTestApp';
@@ -14,7 +19,7 @@ vi.mock('../services/db/index.db', () => ({
getUserAchievements: vi.fn(),
awardAchievement: vi.fn(),
getLeaderboard: vi.fn(),
}
},
}));
// Import the router and mocked DB AFTER all mocks are defined.
@@ -27,7 +32,9 @@ vi.mock('../services/logger.server', () => ({
}));
// Use vi.hoisted to create mutable mock function references.
const mockedAuthMiddleware = vi.hoisted(() => vi.fn((req: Request, res: Response, next: NextFunction) => next()));
const mockedAuthMiddleware = vi.hoisted(() =>
vi.fn((req: Request, res: Response, next: NextFunction) => next()),
);
const mockedIsAdmin = vi.hoisted(() => vi.fn());
vi.mock('./passport.routes', () => ({
@@ -46,8 +53,15 @@ const expectLogger = expect.objectContaining({
});
describe('Gamification Routes (/api/achievements)', () => {
const mockUserProfile = createMockUserProfile({ user: { user_id: 'user-123', email: 'user@test.com' }, points: 100 });
const mockAdminProfile = createMockUserProfile({ user: { user_id: 'admin-456', email: 'admin@test.com' }, role: 'admin', points: 999 });
const mockUserProfile = createMockUserProfile({
user: { user_id: 'user-123', email: 'user@test.com' },
points: 100,
});
const mockAdminProfile = createMockUserProfile({
user: { user_id: 'admin-456', email: 'admin@test.com' },
role: 'admin',
points: 999,
});
beforeEach(() => {
vi.clearAllMocks();
@@ -62,8 +76,16 @@ describe('Gamification Routes (/api/achievements)', () => {
const basePath = '/api/achievements';
const unauthenticatedApp = createTestApp({ router: gamificationRouter, basePath });
const authenticatedApp = createTestApp({ router: gamificationRouter, basePath, authenticatedUser: mockUserProfile });
const adminApp = createTestApp({ router: gamificationRouter, basePath, authenticatedUser: mockAdminProfile });
const authenticatedApp = createTestApp({
router: gamificationRouter,
basePath,
authenticatedUser: mockUserProfile,
});
const adminApp = createTestApp({
router: gamificationRouter,
basePath,
authenticatedUser: mockAdminProfile,
});
const errorHandler = (err: any, req: any, res: any, next: any) => {
res.status(err.status || 500).json({ message: err.message, errors: err.errors });
};
@@ -73,7 +95,10 @@ describe('Gamification Routes (/api/achievements)', () => {
describe('GET /', () => {
it('should return a list of all achievements (public endpoint)', async () => {
const mockAchievements = [createMockAchievement({ achievement_id: 1 }), createMockAchievement({ achievement_id: 2 })];
const mockAchievements = [
createMockAchievement({ achievement_id: 1 }),
createMockAchievement({ achievement_id: 2 }),
];
vi.mocked(db.gamificationRepo.getAllAchievements).mockResolvedValue(mockAchievements);
const response = await supertest(unauthenticatedApp).get('/api/achievements');
@@ -97,9 +122,13 @@ describe('Gamification Routes (/api/achievements)', () => {
next();
});
mockedIsAdmin.mockImplementation((req: Request, res: Response, next: NextFunction) => next());
vi.mocked(db.gamificationRepo.awardAchievement).mockRejectedValue(new ForeignKeyConstraintError('User not found'));
vi.mocked(db.gamificationRepo.awardAchievement).mockRejectedValue(
new ForeignKeyConstraintError('User not found'),
);
const response = await supertest(adminApp).post('/api/achievements/award').send({ userId: 'non-existent', achievementName: 'Test Award' });
const response = await supertest(adminApp)
.post('/api/achievements/award')
.send({ userId: 'non-existent', achievementName: 'Test Award' });
expect(response.status).toBe(400);
expect(response.body.message).toBe('User not found');
});
@@ -118,14 +147,19 @@ describe('Gamification Routes (/api/achievements)', () => {
next();
});
const mockUserAchievements = [createMockUserAchievement({ achievement_id: 1, user_id: 'user-123' })];
const mockUserAchievements = [
createMockUserAchievement({ achievement_id: 1, user_id: 'user-123' }),
];
vi.mocked(db.gamificationRepo.getUserAchievements).mockResolvedValue(mockUserAchievements);
const response = await supertest(authenticatedApp).get('/api/achievements/me');
expect(response.status).toBe(200);
expect(response.body).toEqual(mockUserAchievements);
expect(db.gamificationRepo.getUserAchievements).toHaveBeenCalledWith('user-123', expectLogger);
expect(db.gamificationRepo.getUserAchievements).toHaveBeenCalledWith(
'user-123',
expectLogger,
);
});
it('should return 500 if the database call fails', async () => {
@@ -146,7 +180,9 @@ describe('Gamification Routes (/api/achievements)', () => {
const awardPayload = { userId: 'user-789', achievementName: 'Test Award' };
it('should return 401 Unauthorized if user is not authenticated', async () => {
const response = await supertest(unauthenticatedApp).post('/api/achievements/award').send(awardPayload);
const response = await supertest(unauthenticatedApp)
.post('/api/achievements/award')
.send(awardPayload);
expect(response.status).toBe(401);
});
@@ -158,7 +194,9 @@ describe('Gamification Routes (/api/achievements)', () => {
});
// Let the default isAdmin mock (set in beforeEach) run, which denies access
const response = await supertest(authenticatedApp).post('/api/achievements/award').send(awardPayload);
const response = await supertest(authenticatedApp)
.post('/api/achievements/award')
.send(awardPayload);
expect(response.status).toBe(403);
});
@@ -176,7 +214,11 @@ describe('Gamification Routes (/api/achievements)', () => {
expect(response.status).toBe(200);
expect(response.body.message).toContain('Successfully awarded');
expect(db.gamificationRepo.awardAchievement).toHaveBeenCalledTimes(1);
expect(db.gamificationRepo.awardAchievement).toHaveBeenCalledWith(awardPayload.userId, awardPayload.achievementName, expectLogger);
expect(db.gamificationRepo.awardAchievement).toHaveBeenCalledWith(
awardPayload.userId,
awardPayload.achievementName,
expectLogger,
);
});
it('should return 500 if the database call fails', async () => {
@@ -193,23 +235,35 @@ describe('Gamification Routes (/api/achievements)', () => {
});
it('should return 400 for an invalid userId or achievementName', async () => {
mockedAuthMiddleware.mockImplementation((req: Request, res: Response, next: NextFunction) => { req.user = mockAdminProfile; next(); });
mockedAuthMiddleware.mockImplementation((req: Request, res: Response, next: NextFunction) => {
req.user = mockAdminProfile;
next();
});
mockedIsAdmin.mockImplementation((req: Request, res: Response, next: NextFunction) => next());
const response = await supertest(adminApp).post('/api/achievements/award').send({ userId: '', achievementName: '' });
const response = await supertest(adminApp)
.post('/api/achievements/award')
.send({ userId: '', achievementName: '' });
expect(response.status).toBe(400);
expect(response.body.errors).toHaveLength(2);
});
it('should return 400 if userId or achievementName are missing', async () => {
mockedAuthMiddleware.mockImplementation((req: Request, res: Response, next: NextFunction) => { req.user = mockAdminProfile; next(); });
mockedAuthMiddleware.mockImplementation((req: Request, res: Response, next: NextFunction) => {
req.user = mockAdminProfile;
next();
});
mockedIsAdmin.mockImplementation((req: Request, res: Response, next: NextFunction) => next());
const response1 = await supertest(adminApp).post('/api/achievements/award').send({ achievementName: 'Test Award' });
const response1 = await supertest(adminApp)
.post('/api/achievements/award')
.send({ achievementName: 'Test Award' });
expect(response1.status).toBe(400);
expect(response1.body.errors[0].message).toBe('userId is required.');
const response2 = await supertest(adminApp).post('/api/achievements/award').send({ userId: 'user-789' });
const response2 = await supertest(adminApp)
.post('/api/achievements/award')
.send({ userId: 'user-789' });
expect(response2.status).toBe(400);
expect(response2.body.errors[0].message).toBe('achievementName is required.');
});
@@ -220,9 +274,13 @@ describe('Gamification Routes (/api/achievements)', () => {
next();
});
mockedIsAdmin.mockImplementation((req: Request, res: Response, next: NextFunction) => next());
vi.mocked(db.gamificationRepo.awardAchievement).mockRejectedValue(new ForeignKeyConstraintError('User not found'));
vi.mocked(db.gamificationRepo.awardAchievement).mockRejectedValue(
new ForeignKeyConstraintError('User not found'),
);
const response = await supertest(adminApp).post('/api/achievements/award').send({ userId: 'non-existent', achievementName: 'Test Award' });
const response = await supertest(adminApp)
.post('/api/achievements/award')
.send({ userId: 'non-existent', achievementName: 'Test Award' });
expect(response.status).toBe(400);
expect(response.body.message).toBe('User not found');
});
@@ -230,10 +288,19 @@ describe('Gamification Routes (/api/achievements)', () => {
describe('GET /leaderboard', () => {
it('should return a list of top users (public endpoint)', async () => {
const mockLeaderboard = [createMockLeaderboardUser({ user_id: 'user-1', full_name: 'Leader', points: 1000, rank: '1' })];
const mockLeaderboard = [
createMockLeaderboardUser({
user_id: 'user-1',
full_name: 'Leader',
points: 1000,
rank: '1',
}),
];
vi.mocked(db.gamificationRepo.getLeaderboard).mockResolvedValue(mockLeaderboard);
const response = await supertest(unauthenticatedApp).get('/api/achievements/leaderboard?limit=5');
const response = await supertest(unauthenticatedApp).get(
'/api/achievements/leaderboard?limit=5',
);
expect(response.status).toBe(200);
expect(response.body).toEqual(mockLeaderboard);
@@ -241,7 +308,14 @@ describe('Gamification Routes (/api/achievements)', () => {
});
it('should use the default limit of 10 when no limit is provided', async () => {
const mockLeaderboard = [createMockLeaderboardUser({ user_id: 'user-1', full_name: 'Leader', points: 1000, rank: '1' })];
const mockLeaderboard = [
createMockLeaderboardUser({
user_id: 'user-1',
full_name: 'Leader',
points: 1000,
rank: '1',
}),
];
vi.mocked(db.gamificationRepo.getLeaderboard).mockResolvedValue(mockLeaderboard);
const response = await supertest(unauthenticatedApp).get('/api/achievements/leaderboard');
@@ -259,7 +333,9 @@ describe('Gamification Routes (/api/achievements)', () => {
});
it('should return 400 for an invalid limit parameter', async () => {
const response = await supertest(unauthenticatedApp).get('/api/achievements/leaderboard?limit=100');
const response = await supertest(unauthenticatedApp).get(
'/api/achievements/leaderboard?limit=100',
);
expect(response.status).toBe(400);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toMatch(/less than or equal to 50|Too big/i);