All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 13m4s
181 lines
6.5 KiB
TypeScript
181 lines
6.5 KiB
TypeScript
// src/services/db/gamification.db.test.ts
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import type { Pool } from 'pg';
|
|
import { mockPoolInstance } from '../../tests/setup/tests-setup-unit';
|
|
|
|
// FIX 2: Un-mock the module we are testing.
|
|
vi.unmock('./gamification.db');
|
|
|
|
import { GamificationRepository } from './gamification.db';
|
|
import type { Achievement, UserAchievement, LeaderboardUser } from '../../types';
|
|
|
|
// Mock the logger. This is a server-side DB test.
|
|
vi.mock('../logger.server', () => ({
|
|
logger: {
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
debug: vi.fn(),
|
|
},
|
|
}));
|
|
import { logger as mockLogger } from '../logger.server';
|
|
|
|
describe('Gamification DB Service', () => {
|
|
let gamificationRepo: GamificationRepository;
|
|
const mockDb = {
|
|
query: vi.fn(),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
// Reset the global mock's call history before each test.
|
|
vi.clearAllMocks();
|
|
|
|
// Instantiate the repository with the mock pool for each test
|
|
gamificationRepo = new GamificationRepository(mockDb);
|
|
});
|
|
|
|
describe('getAllAchievements', () => {
|
|
it('should execute the correct SELECT query and return achievements', async () => {
|
|
const mockAchievements: Achievement[] = [
|
|
{
|
|
achievement_id: 1,
|
|
name: 'First Steps',
|
|
description: '...',
|
|
icon: 'footprints',
|
|
points_value: 10,
|
|
created_at: new Date().toISOString(),
|
|
},
|
|
];
|
|
mockDb.query.mockResolvedValue({ rows: mockAchievements });
|
|
|
|
const result = await gamificationRepo.getAllAchievements(mockLogger);
|
|
|
|
expect(mockDb.query).toHaveBeenCalledWith(
|
|
'SELECT * FROM public.achievements ORDER BY points_value ASC, name ASC',
|
|
);
|
|
expect(result).toEqual(mockAchievements);
|
|
});
|
|
|
|
it('should throw an error if the database query fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
mockDb.query.mockRejectedValue(dbError);
|
|
await expect(gamificationRepo.getAllAchievements(mockLogger)).rejects.toThrow(
|
|
'Failed to retrieve achievements.',
|
|
);
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
{ err: dbError },
|
|
'Database error in getAllAchievements',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getUserAchievements', () => {
|
|
it('should execute the correct SELECT query with a JOIN and return user achievements', async () => {
|
|
const mockUserAchievements: (UserAchievement & Achievement)[] = [
|
|
{
|
|
achievement_id: 1,
|
|
user_id: 'user-123',
|
|
achieved_at: '2024-01-01',
|
|
name: 'First Steps',
|
|
description: '...',
|
|
icon: 'footprints',
|
|
points_value: 10,
|
|
created_at: new Date().toISOString(),
|
|
},
|
|
];
|
|
mockDb.query.mockResolvedValue({ rows: mockUserAchievements });
|
|
|
|
const result = await gamificationRepo.getUserAchievements('user-123', mockLogger);
|
|
|
|
expect(mockDb.query).toHaveBeenCalledWith(
|
|
expect.stringContaining('FROM public.user_achievements ua'),
|
|
['user-123'],
|
|
);
|
|
expect(result).toEqual(mockUserAchievements);
|
|
});
|
|
|
|
it('should throw an error if the database query fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
mockDb.query.mockRejectedValue(dbError);
|
|
await expect(gamificationRepo.getUserAchievements('user-123', mockLogger)).rejects.toThrow(
|
|
'Failed to retrieve user achievements.',
|
|
);
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
{ err: dbError, userId: 'user-123' },
|
|
'Database error in getUserAchievements',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('awardAchievement', () => {
|
|
it('should call the award_achievement database function with the correct parameters', async () => {
|
|
mockDb.query.mockResolvedValue({ rows: [] }); // The function returns void
|
|
await gamificationRepo.awardAchievement('user-123', 'Test Achievement', mockLogger);
|
|
|
|
expect(mockDb.query).toHaveBeenCalledWith(
|
|
'SELECT public.award_achievement($1, $2)',
|
|
['user-123', 'Test Achievement'],
|
|
);
|
|
});
|
|
|
|
it('should throw ForeignKeyConstraintError if user or achievement does not exist', async () => {
|
|
const dbError = new Error('violates foreign key constraint');
|
|
(dbError as Error & { code: string }).code = '23503';
|
|
mockDb.query.mockRejectedValue(dbError);
|
|
await expect(
|
|
gamificationRepo.awardAchievement(
|
|
'non-existent-user',
|
|
'Non-existent Achievement',
|
|
mockLogger,
|
|
),
|
|
).rejects.toThrow('The specified user or achievement does not exist.');
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
{ err: dbError, userId: 'non-existent-user', achievementName: 'Non-existent Achievement' },
|
|
'Database error in awardAchievement',
|
|
);
|
|
});
|
|
|
|
it('should throw a generic error if the database query fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
mockDb.query.mockRejectedValue(dbError);
|
|
await expect(
|
|
gamificationRepo.awardAchievement('user-123', 'Test Achievement', mockLogger),
|
|
).rejects.toThrow('Failed to award achievement.');
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
{ err: dbError, userId: 'user-123', achievementName: 'Test Achievement' },
|
|
'Database error in awardAchievement',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getLeaderboard', () => {
|
|
it('should execute the correct SELECT query with a LIMIT and return leaderboard users', async () => {
|
|
const mockLeaderboard: LeaderboardUser[] = [
|
|
{ user_id: 'user-1', full_name: 'User One', avatar_url: null, points: 500, rank: '1' },
|
|
{ user_id: 'user-2', full_name: 'User Two', avatar_url: null, points: 450, rank: '2' }
|
|
];
|
|
mockDb.query.mockResolvedValue({ rows: mockLeaderboard });
|
|
|
|
const result = await gamificationRepo.getLeaderboard(10, mockLogger);
|
|
expect(mockDb.query).toHaveBeenCalledTimes(1);
|
|
expect(mockDb.query).toHaveBeenCalledWith(
|
|
expect.stringContaining('RANK() OVER (ORDER BY points DESC)'),
|
|
[10],
|
|
);
|
|
expect(result).toEqual(mockLeaderboard);
|
|
});
|
|
|
|
it('should throw an error if the database query fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
mockDb.query.mockRejectedValue(dbError);
|
|
await expect(gamificationRepo.getLeaderboard(10, mockLogger)).rejects.toThrow(
|
|
'Failed to retrieve leaderboard.',
|
|
);
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
{ err: dbError, limit: 10 },
|
|
'Database error in getLeaderboard',
|
|
);
|
|
});
|
|
});
|
|
});
|