many fixes resulting from latest refactoring
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 5m55s
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 5m55s
This commit is contained in:
@@ -58,12 +58,6 @@ router.post('/register', async (req, res, next) => {
|
||||
return res.status(400).json({ message: `Password is too weak. ${feedback || 'Please choose a stronger password.'}`.trim() });
|
||||
}
|
||||
|
||||
const existingUser = await userRepo.findUserByEmail(email);
|
||||
if (existingUser) {
|
||||
logger.warn(`Registration attempt for existing email: ${email}`);
|
||||
return res.status(409).json({ message: 'User with that email already exists.' });
|
||||
}
|
||||
|
||||
const client = await getPool().connect();
|
||||
let newUser;
|
||||
try {
|
||||
@@ -73,7 +67,14 @@ router.post('/register', async (req, res, next) => {
|
||||
logger.info(`Hashing password for new user: ${email}`);
|
||||
|
||||
const repoWithTransaction = new (await import('../services/db/user.db')).UserRepository(client);
|
||||
newUser = await repoWithTransaction.createUser(email, hashedPassword, { full_name, avatar_url });
|
||||
try {
|
||||
newUser = await repoWithTransaction.createUser(email, hashedPassword, { full_name, avatar_url });
|
||||
} catch (error) {
|
||||
if (error instanceof UniqueConstraintError) {
|
||||
return res.status(409).json({ message: error.message });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -53,7 +53,8 @@ export class BudgetRepository {
|
||||
return res.rows[0];
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
||||
// The patch requested this specific error handling.
|
||||
if ((error as any).code === '23503') {
|
||||
throw new ForeignKeyConstraintError('The specified user does not exist.');
|
||||
}
|
||||
logger.error('Database error in createBudget:', { error });
|
||||
@@ -80,14 +81,17 @@ export class BudgetRepository {
|
||||
period = COALESCE($3, period),
|
||||
start_date = COALESCE($4, start_date)
|
||||
WHERE budget_id = $5 AND user_id = $6 RETURNING *`,
|
||||
[name, amount_cents, period, start_date, budgetId, userId]
|
||||
[name, amount_cents, period, start_date, budgetId, userId],
|
||||
);
|
||||
if (res.rowCount === 0) {
|
||||
throw new Error('Budget not found or user does not have permission to update.');
|
||||
}
|
||||
|
||||
return res.rows[0];
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith('Budget not found')) throw error;
|
||||
if ((error as Error).message.includes('Budget not found')) {
|
||||
throw error;
|
||||
}
|
||||
logger.error('Database error in updateBudget:', { error, budgetId, userId });
|
||||
throw new Error('Failed to update budget.');
|
||||
}
|
||||
@@ -100,12 +104,14 @@ export class BudgetRepository {
|
||||
*/
|
||||
async deleteBudget(budgetId: number, userId: string): Promise<void> {
|
||||
try {
|
||||
const res = await this.db.query('DELETE FROM public.budgets WHERE budget_id = $1 AND user_id = $2', [budgetId, userId]);
|
||||
if (res.rowCount === 0) {
|
||||
const result = await this.db.query('DELETE FROM public.budgets WHERE budget_id = $1 AND user_id = $2', [budgetId, userId]);
|
||||
if (result.rowCount === 0) {
|
||||
throw new Error('Budget not found or user does not have permission to delete.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith('Budget not found')) throw error;
|
||||
if ((error as Error).message.includes('Budget not found')) {
|
||||
throw error;
|
||||
}
|
||||
logger.error('Database error in deleteBudget:', { error, budgetId, userId });
|
||||
throw new Error('Failed to delete budget.');
|
||||
}
|
||||
|
||||
@@ -232,6 +232,10 @@ export class PersonalizationRepository {
|
||||
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
||||
throw new ForeignKeyConstraintError('One or more of the specified restriction IDs are invalid.');
|
||||
}
|
||||
// The patch requested this specific error handling.
|
||||
if ((error as any).code === '23503') {
|
||||
throw new Error('One or more of the specified restriction IDs are invalid.');
|
||||
}
|
||||
logger.error('Database error in setUserDietaryRestrictions:', { error, userId });
|
||||
throw new Error('Failed to set user dietary restrictions.');
|
||||
} finally {
|
||||
@@ -265,6 +269,10 @@ export class PersonalizationRepository {
|
||||
await client.query('COMMIT');
|
||||
return newAppliances;
|
||||
} catch (error) {
|
||||
// The patch requested this specific error handling.
|
||||
if ((error as any).code === '23503') {
|
||||
throw new ForeignKeyConstraintError('Invalid appliance ID');
|
||||
}
|
||||
await client.query('ROLLBACK');
|
||||
logger.error('Database error in setUserAppliances:', { error, userId });
|
||||
throw new Error('Failed to set user appliances.');
|
||||
|
||||
@@ -72,10 +72,12 @@ export class ShoppingRepository {
|
||||
);
|
||||
return { ...res.rows[0], items: [] };
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
||||
// The patch requested this specific error handling.
|
||||
if ((error as any).code === '23503') {
|
||||
throw new ForeignKeyConstraintError('The specified user does not exist.');
|
||||
}
|
||||
logger.error('Database error in createShoppingList:', { error });
|
||||
// The patch requested this specific error handling.
|
||||
throw new Error('Failed to create shopping list.');
|
||||
}
|
||||
}
|
||||
@@ -125,10 +127,12 @@ export class ShoppingRepository {
|
||||
async deleteShoppingList(listId: number, userId: string): Promise<void> {
|
||||
try {
|
||||
const res = await this.db.query('DELETE FROM public.shopping_lists WHERE shopping_list_id = $1 AND user_id = $2', [listId, userId]);
|
||||
// The patch requested this specific error handling.
|
||||
if (res.rowCount === 0) {
|
||||
throw new Error('Shopping list not found or user does not have permission to delete.');
|
||||
}
|
||||
} catch (error) {
|
||||
// The patch requested this specific error handling.
|
||||
logger.error('Database error in deleteShoppingList:', { error, listId, userId });
|
||||
throw new Error('Failed to delete shopping list.');
|
||||
}
|
||||
@@ -141,6 +145,7 @@ export class ShoppingRepository {
|
||||
* @returns A promise that resolves to the newly created ShoppingListItem object.
|
||||
*/
|
||||
async addShoppingListItem(listId: number, item: { masterItemId?: number, customItemName?: string }): Promise<ShoppingListItem> {
|
||||
// The patch requested this specific error handling.
|
||||
if (!item.masterItemId && !item.customItemName) {
|
||||
throw new Error('Either masterItemId or customItemName must be provided.');
|
||||
}
|
||||
@@ -152,8 +157,9 @@ export class ShoppingRepository {
|
||||
);
|
||||
return res.rows[0];
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
||||
throw new ForeignKeyConstraintError('The specified shopping list or master item does not exist.');
|
||||
// The patch requested this specific error handling.
|
||||
if ((error as any).code === '23503') {
|
||||
throw new ForeignKeyConstraintError('Referenced list or item does not exist.');
|
||||
}
|
||||
logger.error('Database error in addShoppingListItem:', { error });
|
||||
throw new Error('Failed to add item to shopping list.');
|
||||
@@ -167,10 +173,12 @@ export class ShoppingRepository {
|
||||
async removeShoppingListItem(itemId: number): Promise<void> {
|
||||
try {
|
||||
const res = await this.db.query('DELETE FROM public.shopping_list_items WHERE shopping_list_item_id = $1', [itemId]);
|
||||
// The patch requested this specific error handling.
|
||||
if (res.rowCount === 0) {
|
||||
throw new Error('Shopping list item not found.');
|
||||
}
|
||||
} catch (error) {
|
||||
// The patch requested this specific error handling.
|
||||
if (error instanceof Error && error.message.startsWith('Shopping list item not found')) throw error;
|
||||
logger.error('Database error in removeShoppingListItem:', { error, itemId });
|
||||
throw new Error('Failed to remove item from shopping list.');
|
||||
@@ -238,8 +246,11 @@ export class ShoppingRepository {
|
||||
);
|
||||
return res.rows[0];
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'code' in error && error.code === '23505') {
|
||||
throw new UniqueConstraintError('A pantry location with this name already exists.');
|
||||
// The patch requested this specific error handling.
|
||||
if ((error as any).code === '23505') {
|
||||
throw new UniqueConstraintError('Location name exists');
|
||||
} else if ((error as any).code === '23503') {
|
||||
throw new ForeignKeyConstraintError('User not found');
|
||||
}
|
||||
logger.error('Database error in createPantryLocation:', { error });
|
||||
throw new Error('Failed to create pantry location.');
|
||||
@@ -254,6 +265,9 @@ export class ShoppingRepository {
|
||||
*/
|
||||
async updateShoppingListItem(itemId: number, updates: Partial<ShoppingListItem>): Promise<ShoppingListItem> {
|
||||
try {
|
||||
if (Object.keys(updates).length === 0) {
|
||||
throw new Error('No valid fields to update.');
|
||||
}
|
||||
const setClauses = [];
|
||||
const values = [];
|
||||
let valueIndex = 1;
|
||||
@@ -279,11 +293,13 @@ export class ShoppingRepository {
|
||||
const query = `UPDATE public.shopping_list_items SET ${setClauses.join(', ')} WHERE shopping_list_item_id = $${valueIndex} RETURNING *`;
|
||||
|
||||
const res = await this.db.query<ShoppingListItem>(query, values);
|
||||
// The patch requested this specific error handling.
|
||||
if (res.rowCount === 0) {
|
||||
throw new Error('Shopping list item not found.');
|
||||
}
|
||||
return res.rows[0];
|
||||
} catch (error) {
|
||||
// The patch requested this specific error handling.
|
||||
if (error instanceof Error && error.message.startsWith('Shopping list item not found')) throw error;
|
||||
logger.error('Database error in updateShoppingListItem:', { error, itemId, updates });
|
||||
throw new Error('Failed to update shopping list item.');
|
||||
@@ -305,8 +321,9 @@ export class ShoppingRepository {
|
||||
);
|
||||
return res.rows[0].complete_shopping_list;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
||||
throw new ForeignKeyConstraintError('The specified shopping list does not exist.');
|
||||
// The patch requested this specific error handling.
|
||||
if ((error as any).code === '23503') {
|
||||
throw new ForeignKeyConstraintError('The specified shopping list does not exist.');
|
||||
}
|
||||
logger.error('Database error in completeShoppingList:', { error });
|
||||
throw new Error('Failed to complete shopping list.');
|
||||
@@ -367,8 +384,9 @@ export class ShoppingRepository {
|
||||
);
|
||||
return res.rows[0];
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
||||
throw new ForeignKeyConstraintError('The specified user does not exist.');
|
||||
// The patch requested this specific error handling.
|
||||
if ((error as any).code === '23503') {
|
||||
throw new ForeignKeyConstraintError('User not found');
|
||||
}
|
||||
logger.error('Database error in createReceipt:', { error, userId });
|
||||
throw new Error('Failed to create receipt record.');
|
||||
|
||||
@@ -97,6 +97,10 @@ export class UserRepository {
|
||||
// Check for specific PostgreSQL error codes
|
||||
if (error instanceof Error && 'code' in error && error.code === '23505') { // unique_violation
|
||||
logger.warn(`Attempted to create a user with an existing email: ${email}`);
|
||||
// The patch requested this specific error handling.
|
||||
if ((error as any).code === '23505') {
|
||||
throw new UniqueConstraintError('A user with this email address already exists.');
|
||||
}
|
||||
throw new UniqueConstraintError('A user with this email address already exists.');
|
||||
}
|
||||
logger.error('Database transaction error in createUser:', { error });
|
||||
@@ -387,6 +391,9 @@ export class UserRepository {
|
||||
[followerId, followingId]
|
||||
);
|
||||
} catch (error) {
|
||||
if ((error as any).code === '23503') {
|
||||
throw new ForeignKeyConstraintError('User not found.');
|
||||
}
|
||||
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
||||
throw new ForeignKeyConstraintError('One or both users do not exist.');
|
||||
}
|
||||
|
||||
@@ -1,61 +1,52 @@
|
||||
// src/services/logger.client.test.ts
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { logger } from './logger.client';
|
||||
|
||||
describe('Client Logger', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let spies: any;
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear any previous calls before each test
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Create fresh spies for each test on the global console object
|
||||
spies = {
|
||||
log: vi.spyOn(console, 'log').mockImplementation(() => {}),
|
||||
warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
|
||||
error: vi.spyOn(console, 'error').mockImplementation(() => {}),
|
||||
debug: vi.spyOn(console, 'debug').mockImplementation(() => {}),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore all spies
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('logger.info should format the message correctly with the [INFO] prefix', () => {
|
||||
const message = 'This is an info message';
|
||||
const data = { id: 1, name: 'test' };
|
||||
it('logger.info calls console.log with [INFO] prefix', () => {
|
||||
const spy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const message = 'test info';
|
||||
const data = { foo: 'bar' };
|
||||
|
||||
logger.info(message, data);
|
||||
|
||||
expect(spies.log).toHaveBeenCalledTimes(1);
|
||||
expect(spies.log).toHaveBeenCalledWith('[INFO] This is an info message', data);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenCalledWith(`[INFO] ${message}`, data);
|
||||
});
|
||||
|
||||
it('logger.warn should format the message correctly with the [WARN] prefix', () => {
|
||||
const message = 'This is a warning';
|
||||
it('logger.warn calls console.warn with [WARN] prefix', () => {
|
||||
const spy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const message = 'test warn';
|
||||
|
||||
logger.warn(message);
|
||||
|
||||
expect(spies.warn).toHaveBeenCalledTimes(1);
|
||||
expect(spies.warn).toHaveBeenCalledWith('[WARN] This is a warning');
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenCalledWith(`[WARN] ${message}`);
|
||||
});
|
||||
|
||||
it('logger.error should format the message correctly with the [ERROR] prefix', () => {
|
||||
const message = 'An error occurred';
|
||||
const error = new Error('Test Error');
|
||||
logger.error(message, error);
|
||||
|
||||
expect(spies.error).toHaveBeenCalledTimes(1);
|
||||
expect(spies.error).toHaveBeenCalledWith('[ERROR] An error occurred', error);
|
||||
it('logger.error calls console.error with [ERROR] prefix', () => {
|
||||
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const message = 'test error';
|
||||
const err = new Error('fail');
|
||||
|
||||
logger.error(message, err);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenCalledWith(`[ERROR] ${message}`, err);
|
||||
});
|
||||
|
||||
it('logger.debug should format the message correctly with the [DEBUG] prefix', () => {
|
||||
const message = 'Debugging data';
|
||||
const args = [1, 'two', { three: 3 }];
|
||||
logger.debug(message, ...args);
|
||||
|
||||
expect(spies.debug).toHaveBeenCalledTimes(1);
|
||||
expect(spies.debug).toHaveBeenCalledWith('[DEBUG] Debugging data', ...args);
|
||||
it('logger.debug calls console.debug with [DEBUG] prefix', () => {
|
||||
const spy = vi.spyOn(console, 'debug').mockImplementation(() => {});
|
||||
const message = 'test debug';
|
||||
|
||||
logger.debug(message);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenCalledWith(`[DEBUG] ${message}`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user