feat: change global role on administration page (#61)

* feat: change global role on administration page

* feat: change global role on administration page test code todo

* feat: change global role on administration page test code

* feat: change global role on administration page

* feat: change global role on administration page

* feat: change global role on administration page

* feat: change global role on administration page

* feat: change global role on administration page
This commit is contained in:
Takeshi Kimata
2024-09-29 20:26:18 +09:00
committed by GitHub
parent d0fcd5e77b
commit f7cc3d918f
13 changed files with 2746 additions and 30 deletions

View File

@@ -0,0 +1,58 @@
import express from 'express';
import { DataTypes } from 'sequelize';
import defineUser from '../../models/users.js';
import { roles } from './authSettings.js';
import authMiddleware from '../../middleware/auth.js';
const router = express.Router();
export default function (sequelize) {
const { verifySignedIn, verifyAdmin } = authMiddleware(sequelize);
const User = defineUser(sequelize, DataTypes);
router.put('/:userId', verifySignedIn, verifyAdmin, async (req, res) => {
// param check
const userId = req.params.userId;
if (!userId) {
return res.status(400).send('userId is required');
}
const { newRole } = req.body;
if (newRole === undefined || newRole === null) {
return res.status(400).send('newRole is required');
}
try {
const targetUser = await User.findByPk(userId);
if (!targetUser) {
return res.status(404).send('User not found');
}
const adminRoleIndex = roles.findIndex((entry) => entry.uid === 'administrator');
const userRoleIndex = roles.findIndex((entry) => entry.uid === 'user');
// At least one administrator is required.
if (newRole === userRoleIndex) {
const adminCount = await User.count({
where: {
role: adminRoleIndex,
},
});
if (adminCount <= 1) {
return res.status(400).send('At least one administrator is required.');
}
}
await targetUser.update({
role: newRole,
});
res.json({ user: targetUser });
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
});
return router;
}

View File

@@ -0,0 +1,106 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import request from 'supertest';
import express from 'express';
import updateRoute from './update';
import { Sequelize } from 'sequelize';
import { roles } from './authSettings';
const adminRoleIndex = roles.findIndex((entry) => entry.uid === 'administrator');
const userRoleIndex = roles.findIndex((entry) => entry.uid === 'user');
// mock of authentication middleware
vi.mock('../../middleware/auth', () => ({
default: () => ({
verifySignedIn: vi.fn((req, res, next) => {
req.userId = 1; // Mock user ID
next();
}),
verifyAdmin: vi.fn((req, res, next) => {
next(); // Allow all as admin
}),
}),
}));
// mock defineUser
const mockUser = {
findByPk: vi.fn(),
count: vi.fn(),
update: vi.fn(),
};
vi.mock('../../models/users', () => ({
default: (sequelize, DataTypes) => mockUser,
}));
// test
describe('updateUserRole', () => {
let app;
const sequelize = new Sequelize({
dialect: 'sqlite',
logging: false,
});
beforeEach(() => {
app = express();
app.use(express.json());
// Mount the update route
app.use('/users', updateRoute(sequelize));
});
it('call update API without new role', async () => {
const response = await request(app).put('/users/2').send();
expect(response.status).toBe(400);
expect(response.text).toBe('newRole is required');
});
it('promote not existing user to admin will return 404', async () => {
mockUser.findByPk.mockResolvedValue(null); // No user found
const response = await request(app).put('/users/2').send({
newRole: 0,
});
expect(response.status).toBe(404);
expect(response.text).toBe('User not found');
});
it('promote existing user to admin will return 200', async () => {
const targetUser = { id: 2, role: userRoleIndex, update: vi.fn() }; // Normal user
mockUser.findByPk.mockResolvedValue(targetUser);
const response = await request(app).put('/users/2').send({
newRole: 0,
});
expect(response.status).toBe(200);
expect(targetUser.update).toHaveBeenCalledWith({ role: 0 });
});
it('should return 400 if trying to demote the only administrator', async () => {
const targetUser = { id: 1, role: adminRoleIndex, update: vi.fn() }; // Administrator
mockUser.findByPk.mockResolvedValue(targetUser);
mockUser.count.mockResolvedValue(1); // Only one admin
const response = await request(app).put('/users/1').send({
newRole: 1,
}); // Downgrading admin to user
expect(response.status).toBe(400);
expect(response.text).toBe('At least one administrator is required.');
});
it('should handle internal server errors', async () => {
// Suppress error logging to console
vi.spyOn(console, 'error').mockImplementation(() => {});
// Simulate DB error
mockUser.findByPk.mockRejectedValue(new Error('Database error'));
const response = await request(app).put('/users/1').send({
newRole: 0,
});
expect(response.status).toBe(500);
expect(response.text).toBe('Internal Server Error');
});
});