166 lines
5.8 KiB
TypeScript
166 lines
5.8 KiB
TypeScript
// src/services/gamificationService.test.ts
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { gamificationService } from './gamificationService';
|
|
import { gamificationRepo } from './db/index.db';
|
|
import { ForeignKeyConstraintError } from './db/errors.db';
|
|
import { logger as mockLogger } from './logger.server';
|
|
import {
|
|
createMockAchievement,
|
|
createMockLeaderboardUser,
|
|
createMockUserAchievement,
|
|
} from '../tests/utils/mockFactories';
|
|
|
|
// Mock dependencies
|
|
vi.mock('./db/index.db', () => ({
|
|
gamificationRepo: {
|
|
awardAchievement: vi.fn(),
|
|
getAllAchievements: vi.fn(),
|
|
getLeaderboard: vi.fn(),
|
|
getUserAchievements: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
vi.mock('./logger.server', () => ({
|
|
logger: {
|
|
info: vi.fn(),
|
|
error: vi.fn(),
|
|
warn: vi.fn(),
|
|
debug: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
// Mock the error class
|
|
vi.mock('./db/errors.db', () => ({
|
|
ForeignKeyConstraintError: class extends Error {
|
|
constructor(message: string) {
|
|
super(message);
|
|
this.name = 'ForeignKeyConstraintError';
|
|
}
|
|
},
|
|
}));
|
|
|
|
describe('GamificationService', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('awardAchievement', () => {
|
|
it('should call the repository to award an achievement', async () => {
|
|
const userId = 'user-123';
|
|
const achievementName = 'First-Upload';
|
|
vi.mocked(gamificationRepo.awardAchievement).mockResolvedValue(undefined);
|
|
|
|
await gamificationService.awardAchievement(userId, achievementName, mockLogger);
|
|
|
|
expect(gamificationRepo.awardAchievement).toHaveBeenCalledWith(userId, achievementName, mockLogger);
|
|
});
|
|
|
|
it('should re-throw ForeignKeyConstraintError without logging it as a service error', async () => {
|
|
const userId = 'user-123';
|
|
const achievementName = 'NonExistentAchievement';
|
|
const fkError = new ForeignKeyConstraintError('Achievement not found');
|
|
vi.mocked(gamificationRepo.awardAchievement).mockRejectedValue(fkError);
|
|
|
|
await expect(
|
|
gamificationService.awardAchievement(userId, achievementName, mockLogger),
|
|
).rejects.toThrow(fkError);
|
|
|
|
expect(mockLogger.error).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should log and re-throw generic errors', async () => {
|
|
const userId = 'user-123';
|
|
const achievementName = 'First-Upload';
|
|
const dbError = new Error('DB connection failed');
|
|
vi.mocked(gamificationRepo.awardAchievement).mockRejectedValue(dbError);
|
|
|
|
await expect(
|
|
gamificationService.awardAchievement(userId, achievementName, mockLogger),
|
|
).rejects.toThrow(dbError);
|
|
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
{ error: dbError, userId, achievementName },
|
|
'Error awarding achievement via admin endpoint:',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getAllAchievements', () => {
|
|
it('should return all achievements from the repository', async () => {
|
|
const mockAchievements = [
|
|
createMockAchievement({ name: 'Achieve1' }),
|
|
createMockAchievement({ name: 'Achieve2' }),
|
|
];
|
|
vi.mocked(gamificationRepo.getAllAchievements).mockResolvedValue(mockAchievements);
|
|
|
|
const result = await gamificationService.getAllAchievements(mockLogger);
|
|
|
|
expect(result).toEqual(mockAchievements);
|
|
expect(gamificationRepo.getAllAchievements).toHaveBeenCalledWith(mockLogger);
|
|
});
|
|
|
|
it('should log and re-throw an error if the repository fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
vi.mocked(gamificationRepo.getAllAchievements).mockRejectedValue(dbError);
|
|
|
|
await expect(gamificationService.getAllAchievements(mockLogger)).rejects.toThrow(dbError);
|
|
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
{ error: dbError },
|
|
'Error in getAllAchievements service method',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getLeaderboard', () => {
|
|
it('should return the leaderboard from the repository', async () => {
|
|
const mockLeaderboard = [createMockLeaderboardUser({ rank: '1' })];
|
|
vi.mocked(gamificationRepo.getLeaderboard).mockResolvedValue(mockLeaderboard);
|
|
|
|
const result = await gamificationService.getLeaderboard(10, mockLogger);
|
|
|
|
expect(result).toEqual(mockLeaderboard);
|
|
expect(gamificationRepo.getLeaderboard).toHaveBeenCalledWith(10, mockLogger);
|
|
});
|
|
|
|
it('should log and re-throw an error if the repository fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
vi.mocked(gamificationRepo.getLeaderboard).mockRejectedValue(dbError);
|
|
|
|
await expect(gamificationService.getLeaderboard(10, mockLogger)).rejects.toThrow(dbError);
|
|
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
{ error: dbError, limit: 10 },
|
|
'Error fetching leaderboard in service method.',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getUserAchievements', () => {
|
|
it("should return a user's achievements from the repository", async () => {
|
|
const userId = 'user-123';
|
|
const mockUserAchievements = [createMockUserAchievement({ user_id: userId })];
|
|
vi.mocked(gamificationRepo.getUserAchievements).mockResolvedValue(mockUserAchievements);
|
|
|
|
const result = await gamificationService.getUserAchievements(userId, mockLogger);
|
|
|
|
expect(result).toEqual(mockUserAchievements);
|
|
expect(gamificationRepo.getUserAchievements).toHaveBeenCalledWith(userId, mockLogger);
|
|
});
|
|
|
|
it('should log and re-throw an error if the repository fails', async () => {
|
|
const userId = 'user-123';
|
|
const dbError = new Error('DB Error');
|
|
vi.mocked(gamificationRepo.getUserAchievements).mockRejectedValue(dbError);
|
|
|
|
await expect(gamificationService.getUserAchievements(userId, mockLogger)).rejects.toThrow(
|
|
dbError,
|
|
);
|
|
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
{ error: dbError, userId },
|
|
'Error fetching user achievements in service method.',
|
|
);
|
|
});
|
|
});
|
|
}); |