MORE UNIT TESTS - approc 90% before - 95% now?
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 45m25s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 45m25s
This commit is contained in:
@@ -455,6 +455,13 @@ describe('Admin DB Service', () => {
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('FROM public.users u JOIN public.profiles p'));
|
||||
expect(result).toEqual(mockUsers);
|
||||
});
|
||||
|
||||
it('should throw an error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(adminRepo.getAllUsers(mockLogger)).rejects.toThrow('Failed to retrieve all users.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError }, 'Database error in getAllUsers');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateUserRole', () => {
|
||||
|
||||
@@ -184,6 +184,16 @@ describe('Personalization DB Service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDietaryRestrictions', () => {
|
||||
it('should throw an error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockQuery.mockRejectedValue(dbError);
|
||||
await expect(personalizationRepo.getDietaryRestrictions(mockLogger)).rejects.toThrow('Failed to get dietary restrictions.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError }, 'Database error in getDietaryRestrictions');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('removeWatchedItem', () => {
|
||||
it('should execute a DELETE query', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
@@ -311,6 +321,21 @@ describe('Personalization DB Service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserDietaryRestrictions', () => {
|
||||
it('should throw an error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockQuery.mockRejectedValue(dbError);
|
||||
await expect(personalizationRepo.getUserDietaryRestrictions('user-123', mockLogger)).rejects.toThrow('Failed to get user dietary restrictions.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-123' }, 'Database error in getUserDietaryRestrictions');
|
||||
});
|
||||
|
||||
it('should return an empty array if user has no restrictions', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
const result = await personalizationRepo.getUserDietaryRestrictions('user-123', mockLogger);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findPantryItemOwner', () => {
|
||||
it('should execute a SELECT query to find the owner', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [{ user_id: 'user-123' }] });
|
||||
|
||||
@@ -166,6 +166,24 @@ describe('User DB Service', () => {
|
||||
expect(withTransaction).toHaveBeenCalledTimes(1);
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith(`Attempted to create a user with an existing email: exists@example.com`);
|
||||
});
|
||||
|
||||
it('should throw an error if profile is not found after user creation', async () => {
|
||||
const mockUser = { user_id: 'new-user-id', email: 'no-profile@example.com' };
|
||||
|
||||
vi.mocked(withTransaction).mockImplementation(async (callback) => {
|
||||
const mockClient = { query: vi.fn() };
|
||||
mockClient.query
|
||||
.mockResolvedValueOnce({ rows: [] }) // set_config
|
||||
.mockResolvedValueOnce({ rows: [mockUser] }) // INSERT user succeeds
|
||||
.mockResolvedValueOnce({ rows: [] }); // SELECT profile returns nothing
|
||||
// The callback will throw, which is caught and re-thrown by withTransaction
|
||||
await expect(callback(mockClient as unknown as PoolClient)).rejects.toThrow('Failed to create or retrieve user profile after registration.');
|
||||
throw new Error('Internal failure'); // Simulate re-throw from withTransaction
|
||||
});
|
||||
|
||||
await expect(userRepo.createUser('no-profile@example.com', 'pass', {}, mockLogger)).rejects.toThrow('Failed to create user in database.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: expect.any(Error), email: 'no-profile@example.com' }, 'Error during createUser transaction');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findUserWithProfileByEmail', () => {
|
||||
@@ -383,6 +401,15 @@ describe('User DB Service', () => {
|
||||
await expect(userRepo.findUserByRefreshToken('a-token', mockLogger)).rejects.toThrow(NotFoundError);
|
||||
await expect(userRepo.findUserByRefreshToken('a-token', mockLogger)).rejects.toThrow('User not found for the given refresh token.');
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
|
||||
await expect(userRepo.findUserByRefreshToken('a-token', mockLogger)).rejects.toThrow('Failed to find user by refresh token.');
|
||||
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError }, 'Database error in findUserByRefreshToken');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteRefreshToken', () => {
|
||||
@@ -457,6 +484,23 @@ describe('User DB Service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteExpiredResetTokens', () => {
|
||||
it('should execute a DELETE query for expired tokens and return the count', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rowCount: 5 });
|
||||
const result = await userRepo.deleteExpiredResetTokens(mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('DELETE FROM public.password_reset_tokens WHERE expires_at < NOW()');
|
||||
expect(result).toBe(5);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[DB deleteExpiredResetTokens] Deleted 5 expired password reset tokens.');
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(userRepo.deleteExpiredResetTokens(mockLogger)).rejects.toThrow('Failed to delete expired password reset tokens.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError }, 'Database error in deleteExpiredResetTokens');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exportUserData', () => {
|
||||
// Import the mocked withTransaction helper
|
||||
let withTransaction: Mock;
|
||||
@@ -488,13 +532,13 @@ describe('User DB Service', () => {
|
||||
expect(getShoppingListsSpy).toHaveBeenCalledWith('123', expect.any(Object));
|
||||
});
|
||||
|
||||
it('should throw an error if the user profile is not found', async () => {
|
||||
it('should throw NotFoundError if the user profile is not found', async () => {
|
||||
// Arrange: Mock findUserProfileById to throw a NotFoundError, as per its contract (ADR-001).
|
||||
// The exportUserData function will catch this and re-throw a generic error.
|
||||
const { NotFoundError } = await import('./errors.db');
|
||||
vi.spyOn(UserRepository.prototype, 'findUserProfileById').mockRejectedValue(new NotFoundError('Profile not found'));
|
||||
|
||||
// Act & Assert: The outer function catches the NotFoundError and re-throws a generic one.
|
||||
// Act & Assert: The outer function catches the NotFoundError and re-throws it.
|
||||
await expect(exportUserData('123', mockLogger)).rejects.toThrow('Failed to export user data.');
|
||||
expect(withTransaction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -444,6 +444,23 @@ export class UserRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all expired password reset tokens from the database.
|
||||
* This is intended for a periodic cleanup job.
|
||||
* @returns A promise that resolves to the number of deleted tokens.
|
||||
*/
|
||||
async deleteExpiredResetTokens(logger: Logger): Promise<number> {
|
||||
try {
|
||||
const res = await this.db.query(
|
||||
"DELETE FROM public.password_reset_tokens WHERE expires_at < NOW()"
|
||||
);
|
||||
logger.info(`[DB deleteExpiredResetTokens] Deleted ${res.rowCount ?? 0} expired password reset tokens.`);
|
||||
return res.rowCount ?? 0;
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Database error in deleteExpiredResetTokens');
|
||||
throw new Error('Failed to delete expired password reset tokens.');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates a following relationship between two users.
|
||||
* @param followerId The ID of the user who is following.
|
||||
|
||||
Reference in New Issue
Block a user