@@ -0,0 +1,99 @@
|
||||
const request = require('supertest');
|
||||
const express = require('express');
|
||||
|
||||
const buildChain = ({ firstResult, updateResult } = {}) => {
|
||||
const chain = {
|
||||
where: jest.fn().mockReturnThis(),
|
||||
whereNot: jest.fn().mockReturnThis(),
|
||||
select: jest.fn().mockReturnThis(),
|
||||
update: jest.fn().mockResolvedValue(updateResult ?? 1),
|
||||
first: jest.fn().mockResolvedValue(firstResult),
|
||||
};
|
||||
return chain;
|
||||
};
|
||||
|
||||
jest.mock('../../database/db', () => {
|
||||
const dbMock = jest.fn();
|
||||
dbMock.raw = jest.fn();
|
||||
dbMock.__setImplementations = (...chains) => {
|
||||
dbMock.mockReset();
|
||||
chains.forEach((chain) => {
|
||||
dbMock.mockImplementationOnce(() => chain);
|
||||
});
|
||||
};
|
||||
return {
|
||||
db: dbMock,
|
||||
logActivity: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../middleware/auth-enhanced-v2', () => ({
|
||||
adminAuth: (_req, _res, next) => {
|
||||
_req.admin = { id: 1, username: 'admin' };
|
||||
next();
|
||||
},
|
||||
}));
|
||||
|
||||
const { db, logActivity } = require('../../database/db');
|
||||
const adminAuthRouter = require('../adminAuth');
|
||||
|
||||
describe('adminAuth profile updates', () => {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use('/auth/admin', adminAuthRouter);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('updates the admin profile', async () => {
|
||||
const updatedUser = {
|
||||
id: 1,
|
||||
username: 'newadmin',
|
||||
email: 'newadmin@example.com',
|
||||
must_change_password: false,
|
||||
};
|
||||
|
||||
db.__setImplementations(
|
||||
buildChain({ firstResult: null }), // email check
|
||||
buildChain({ firstResult: null }), // username check
|
||||
buildChain({ updateResult: 1 }), // update
|
||||
buildChain({ firstResult: updatedUser }), // fetch updated user
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/auth/admin/profile')
|
||||
.send({ username: updatedUser.username, email: updatedUser.email })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).toEqual({ user: updatedUser });
|
||||
expect(logActivity).toHaveBeenCalledWith(
|
||||
'admin_profile_updated',
|
||||
{ admin_id: 1, updated_fields: ['username', 'email'] },
|
||||
null,
|
||||
{ type: 'admin', id: 1, name: updatedUser.username }
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects email conflicts', async () => {
|
||||
db.__setImplementations(
|
||||
buildChain({ firstResult: { id: 2 } })
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/auth/admin/profile')
|
||||
.send({ username: 'newadmin', email: 'taken@example.com' })
|
||||
.expect(409);
|
||||
|
||||
expect(response.body).toEqual({ error: 'Email is already in use by another admin' });
|
||||
});
|
||||
|
||||
it('validates input', async () => {
|
||||
const response = await request(app)
|
||||
.put('/auth/admin/profile')
|
||||
.send({ username: '', email: 'not-an-email' })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.errors).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
const request = require('supertest');
|
||||
const express = require('express');
|
||||
|
||||
jest.mock('../../database/db', () => {
|
||||
const deleteMock = jest.fn().mockResolvedValue(5);
|
||||
const chain = {
|
||||
select: jest.fn().mockReturnThis(),
|
||||
leftJoin: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
limit: jest.fn().mockReturnThis(),
|
||||
whereNull: jest.fn().mockReturnThis(),
|
||||
whereNotNull: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
update: jest.fn().mockReturnThis(),
|
||||
delete: deleteMock,
|
||||
count: jest.fn().mockReturnThis(),
|
||||
first: jest.fn().mockResolvedValue({ count: 0 }),
|
||||
};
|
||||
|
||||
const dbMock = jest.fn(() => chain);
|
||||
dbMock.raw = jest.fn();
|
||||
dbMock.__chain = chain;
|
||||
dbMock.__deleteMock = deleteMock;
|
||||
return { db: dbMock };
|
||||
});
|
||||
|
||||
jest.mock('../../middleware/auth-enhanced-v2', () => ({
|
||||
adminAuth: (_req, _res, next) => next(),
|
||||
}));
|
||||
|
||||
const { db } = require('../../database/db');
|
||||
const notificationsRouter = require('../adminNotifications');
|
||||
|
||||
describe('adminNotifications routes', () => {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use('/admin/notifications', notificationsRouter);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('clears all notifications', async () => {
|
||||
db.__deleteMock.mockResolvedValueOnce(8);
|
||||
|
||||
const response = await request(app)
|
||||
.delete('/admin/notifications/clear-all')
|
||||
.expect(200);
|
||||
|
||||
expect(db).toHaveBeenCalledWith('activity_logs');
|
||||
expect(db.__deleteMock).toHaveBeenCalledTimes(1);
|
||||
expect(response.body).toEqual({
|
||||
message: 'All notifications cleared',
|
||||
deletedCount: 8,
|
||||
});
|
||||
});
|
||||
|
||||
it('handles database errors when clearing notifications', async () => {
|
||||
db.__deleteMock.mockRejectedValueOnce(new Error('boom'));
|
||||
|
||||
const response = await request(app)
|
||||
.delete('/admin/notifications/clear-all')
|
||||
.expect(500);
|
||||
|
||||
expect(response.body).toEqual({ error: 'Failed to clear notifications' });
|
||||
});
|
||||
});
|
||||
@@ -72,6 +72,68 @@ router.post('/change-password', [
|
||||
}
|
||||
});
|
||||
|
||||
// Update admin profile
|
||||
router.put('/profile', [
|
||||
adminAuth,
|
||||
body('username').trim().notEmpty().withMessage('Username is required'),
|
||||
body('email').trim().isEmail().withMessage('Valid email is required')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { username, email } = req.body;
|
||||
const userId = req.admin.id;
|
||||
|
||||
// Check for email conflicts
|
||||
const existingEmail = await db('admin_users')
|
||||
.where('email', email)
|
||||
.whereNot('id', userId)
|
||||
.first();
|
||||
|
||||
if (existingEmail) {
|
||||
return res.status(409).json({ error: 'Email is already in use by another admin' });
|
||||
}
|
||||
|
||||
// Check username conflict (if multiple admins are supported)
|
||||
const existingUsername = await db('admin_users')
|
||||
.where('username', username)
|
||||
.whereNot('id', userId)
|
||||
.first();
|
||||
|
||||
if (existingUsername) {
|
||||
return res.status(409).json({ error: 'Username is already in use by another admin' });
|
||||
}
|
||||
|
||||
await db('admin_users')
|
||||
.where('id', userId)
|
||||
.update({
|
||||
username,
|
||||
email,
|
||||
updated_at: new Date()
|
||||
});
|
||||
|
||||
const updatedUser = await db('admin_users')
|
||||
.select('id', 'username', 'email', 'must_change_password')
|
||||
.where('id', userId)
|
||||
.first();
|
||||
|
||||
await logActivity(
|
||||
'admin_profile_updated',
|
||||
{ admin_id: userId, updated_fields: ['username', 'email'] },
|
||||
null,
|
||||
{ type: 'admin', id: userId, name: username }
|
||||
);
|
||||
|
||||
res.json({ user: updatedUser });
|
||||
} catch (error) {
|
||||
console.error('Admin profile update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update admin profile' });
|
||||
}
|
||||
});
|
||||
|
||||
// Logout
|
||||
router.post('/logout', adminAuth, async (req, res) => {
|
||||
try {
|
||||
@@ -96,4 +158,4 @@ router.post('/logout', adminAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
||||
@@ -119,4 +119,18 @@ router.delete('/clear-old', adminAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
// Delete all notifications
|
||||
router.delete('/clear-all', adminAuth, async (req, res) => {
|
||||
try {
|
||||
const deletedCount = await db('activity_logs').delete();
|
||||
res.json({
|
||||
message: 'All notifications cleared',
|
||||
deletedCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Clear all notifications error:', error);
|
||||
res.status(500).json({ error: 'Failed to clear notifications' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user