complete project using prettier!
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user