diff --git a/server/models/User.js b/server/models/User.js index 65b3363..9d007fa 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -203,6 +203,12 @@ class User { */ static async findByIdAndUpdate(id, updateData) { try { + // Hash password if it is being updated + if (updateData.password) { + const salt = await bcrypt.genSalt(10); + updateData.password = await bcrypt.hash(updateData.password, salt); + } + // Convert to snake_case for Supabase const snakeCaseData = {}; diff --git a/tests/server/models/user.test.js b/tests/server/models/user.test.js new file mode 100644 index 0000000..cc03237 --- /dev/null +++ b/tests/server/models/user.test.js @@ -0,0 +1,49 @@ +// Mock bcrypt functions +jest.mock('bcrypt', () => ({ + genSalt: jest.fn(() => Promise.resolve('salt')), + hash: jest.fn(() => Promise.resolve('hashedPassword')) +})); + +const bcrypt = require('bcrypt'); + +// Mock database utilities +const updateMock = jest.fn(() => ({ + eq: jest.fn(() => ({ + select: jest.fn(() => ({ + single: jest.fn().mockResolvedValue({ data: { id: '1', password: 'hashedPassword' }, error: null }) + })) + })) +})); + +const fromMock = jest.fn(() => ({ update: updateMock })); + +jest.mock('../../../server/utils/database', () => ({ + supabase: { from: fromMock }, + supabaseAdmin: { from: fromMock } +})); + +const User = require('../../../server/models/User'); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe('User.findByIdAndUpdate', () => { + it('hashes password when provided', async () => { + await User.findByIdAndUpdate('1', { password: 'secret' }); + + expect(bcrypt.genSalt).toHaveBeenCalledWith(10); + expect(bcrypt.hash).toHaveBeenCalledWith('secret', 'salt'); + expect(updateMock).toHaveBeenCalled(); + const arg = updateMock.mock.calls[0][0]; + expect(arg.password).toBe('hashedPassword'); + }); + + it('does not hash when password not provided', async () => { + await User.findByIdAndUpdate('1', { displayName: 'Test' }); + + expect(bcrypt.hash).not.toHaveBeenCalled(); + const arg = updateMock.mock.calls[0][0]; + expect(arg.display_name).toBe('Test'); + }); +}); diff --git a/tests/server/routes/index.test.js b/tests/server/routes/index.test.js index f8c613b..95b247e 100644 --- a/tests/server/routes/index.test.js +++ b/tests/server/routes/index.test.js @@ -9,10 +9,14 @@ jest.mock('../../../server/models/User', () => ({ findRecent: jest.fn().mockResolvedValue([ { username: 'testuser1', displayName: 'Test User 1' }, { username: 'testuser2', displayName: 'Test User 2' } + ]), + countDocuments: jest.fn().mockResolvedValue(2), + find: jest.fn().mockResolvedValue([ + { username: 'testuser1', lastActive: new Date() } ]) })); -jest.mock('../../../server/models/Item', () => ({ +jest.mock('../../../server/models/ScrapyardItem', () => ({ findRecent: jest.fn().mockResolvedValue([ { id: 1, title: 'Test Item 1' }, { id: 2, title: 'Test Item 2' } @@ -20,7 +24,8 @@ jest.mock('../../../server/models/Item', () => ({ findFeatured: jest.fn().mockResolvedValue([ { id: 3, title: 'Featured Item 1' }, { id: 4, title: 'Featured Item 2' } - ]) + ]), + countDocuments: jest.fn().mockResolvedValue(2) })); // Mock express-handlebars