linting done now fix unit tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 8m19s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 8m19s
This commit is contained in:
@@ -5,7 +5,7 @@ import type { Pool } from 'pg';
|
||||
import { RecipeRepository } from './recipe.db';
|
||||
|
||||
// Un-mock the module we are testing to ensure we use the real implementation.
|
||||
vi.unmock('./recipe.db');
|
||||
vi.unmock('./recipe.db'); // This line is correct.
|
||||
|
||||
const mockQuery = mockPoolInstance.query;
|
||||
import type { Recipe, FavoriteRecipe, RecipeComment } from '../../types';
|
||||
@@ -20,6 +20,7 @@ vi.mock('../logger.server', () => ({
|
||||
},
|
||||
}));
|
||||
import { logger as mockLogger } from '../logger.server';
|
||||
import { ForeignKeyConstraintError, NotFoundError, UniqueConstraintError } from './errors.db';
|
||||
|
||||
describe('Recipe DB Service', () => {
|
||||
let recipeRepo: RecipeRepository;
|
||||
@@ -119,8 +120,8 @@ describe('Recipe DB Service', () => {
|
||||
|
||||
describe('addFavoriteRecipe', () => {
|
||||
it('should execute an INSERT query and return the new favorite', async () => {
|
||||
const mockFavorite: FavoriteRecipe = { user_id: 'user-123', recipe_id: 1, created_at: new Date().toISOString() };
|
||||
mockQuery.mockResolvedValue({ rows: [mockFavorite] });
|
||||
const mockFavorite: FavoriteRecipe = { user_id: 'user-123', recipe_id: 1, created_at: new Date().toISOString() }; // This line is correct.
|
||||
mockQuery.mockResolvedValue({ rows: [mockFavorite], rowCount: 1 });
|
||||
|
||||
const result = await recipeRepo.addFavoriteRecipe('user-123', 1, mockLogger);
|
||||
|
||||
@@ -136,11 +137,12 @@ describe('Recipe DB Service', () => {
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-123', recipeId: 999 }, 'Database error in addFavoriteRecipe');
|
||||
});
|
||||
|
||||
it('should return undefined if the favorite already exists (ON CONFLICT)', async () => {
|
||||
it('should throw UniqueConstraintError if the favorite already exists (ON CONFLICT)', async () => {
|
||||
// When ON CONFLICT DO NOTHING happens, the RETURNING clause does not execute, so rows is empty.
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
const result = await recipeRepo.addFavoriteRecipe('user-123', 1, mockLogger);
|
||||
expect(result).toBeUndefined();
|
||||
// The implementation now throws an error when rowCount is 0.
|
||||
mockQuery.mockResolvedValue({ rows: [], rowCount: 0 });
|
||||
await expect(recipeRepo.addFavoriteRecipe('user-123', 1, mockLogger))
|
||||
.rejects.toThrow(UniqueConstraintError);
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/services/db/recipe.db.ts
|
||||
import type { Pool, PoolClient } from 'pg';
|
||||
import { getPool } from './connection.db';
|
||||
import { ForeignKeyConstraintError, NotFoundError } from './errors.db';
|
||||
import { ForeignKeyConstraintError, NotFoundError, UniqueConstraintError } from './errors.db';
|
||||
import type { Logger } from 'pino';
|
||||
import type { Recipe, FavoriteRecipe, RecipeComment } from '../../types';
|
||||
|
||||
@@ -80,11 +80,16 @@ export class RecipeRepository {
|
||||
* @returns A promise that resolves to the created favorite record.
|
||||
*/
|
||||
async addFavoriteRecipe(userId: string, recipeId: number, logger: Logger): Promise<FavoriteRecipe> {
|
||||
// The ON CONFLICT DO NOTHING clause prevents duplicates but also causes the query to return
|
||||
// zero rows if the favorite already exists. We need to handle this case.
|
||||
try {
|
||||
const res = await this.db.query<FavoriteRecipe>(
|
||||
'INSERT INTO public.favorite_recipes (user_id, recipe_id) VALUES ($1, $2) ON CONFLICT (user_id, recipe_id) DO NOTHING RETURNING *',
|
||||
[userId, recipeId]
|
||||
);
|
||||
if (res.rowCount === 0) {
|
||||
throw new UniqueConstraintError('This recipe is already in the user\'s favorites.');
|
||||
}
|
||||
return res.rows[0];
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
||||
|
||||
@@ -195,13 +195,13 @@ describe('User DB Service', () => {
|
||||
|
||||
describe('findUserById', () => {
|
||||
it('should query for a user by their ID', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{ user_id: '123' }] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{ user_id: '123' }], rowCount: 1 });
|
||||
await userRepo.findUserById('123', mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('FROM public.users WHERE user_id = $1'), ['123']);
|
||||
});
|
||||
|
||||
it('should throw NotFoundError if user is not found', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [], rowCount: 0 });
|
||||
await expect(userRepo.findUserById('not-found-id', mockLogger)).rejects.toThrow('User with ID not-found-id not found.');
|
||||
});
|
||||
|
||||
@@ -215,11 +215,17 @@ describe('User DB Service', () => {
|
||||
|
||||
describe('findUserWithPasswordHashById', () => {
|
||||
it('should query for a user and their password hash by ID', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{ user_id: '123', password_hash: 'hash' }] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{ user_id: '123', password_hash: 'hash' }], rowCount: 1 });
|
||||
await userRepo.findUserWithPasswordHashById('123', mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('SELECT user_id, email, password_hash'), ['123']);
|
||||
});
|
||||
|
||||
it('should throw NotFoundError if user is not found', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [], rowCount: 0 });
|
||||
await expect(userRepo.findUserWithPasswordHashById('not-found-id', mockLogger)).rejects.toThrow(NotFoundError);
|
||||
await expect(userRepo.findUserWithPasswordHashById('not-found-id', mockLogger)).rejects.toThrow('User with ID not-found-id not found.');
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Connection Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
@@ -241,11 +247,6 @@ describe('User DB Service', () => {
|
||||
await expect(userRepo.findUserProfileById('not-found-id', mockLogger)).rejects.toThrow('Profile not found for this user.');
|
||||
});
|
||||
|
||||
it('should return undefined if user is not found', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
const result = await userRepo.findUserWithPasswordHashById('not-found-id', mockLogger);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Connection Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
|
||||
@@ -172,12 +172,15 @@ export class UserRepository {
|
||||
* @returns A promise that resolves to the user object (id, email, password_hash) or undefined if not found.
|
||||
*/
|
||||
// prettier-ignore
|
||||
async findUserWithPasswordHashById(userId: string, logger: Logger): Promise<{ user_id: string; email: string; password_hash: string | null } | undefined> {
|
||||
async findUserWithPasswordHashById(userId: string, logger: Logger): Promise<{ user_id: string; email: string; password_hash: string | null }> {
|
||||
try {
|
||||
const res = await this.db.query<{ user_id: string; email: string; password_hash: string | null }>(
|
||||
'SELECT user_id, email, password_hash FROM public.users WHERE user_id = $1',
|
||||
[userId]
|
||||
);
|
||||
if (res.rowCount === 0) {
|
||||
throw new NotFoundError(`User with ID ${userId} not found.`);
|
||||
}
|
||||
return res.rows[0];
|
||||
} catch (error) {
|
||||
logger.error({ err: error, userId }, 'Database error in findUserWithPasswordHashById');
|
||||
|
||||
Reference in New Issue
Block a user