- Projects: remove superfluous try/catch around toast; keep explicit error path - AdminUsers/Sidebar/ShareService: keep minimal catch blocks only to ignore non-JSON parse failures, without swallowing errors - Lint/format pass remains green
180 lines
7.3 KiB
JavaScript
180 lines
7.3 KiB
JavaScript
const request = require('supertest');
|
|
const app = require('../../app');
|
|
const { User, Role } = require('../../models');
|
|
const { createTestUser } = require('../helpers/testUtils');
|
|
|
|
async function loginAgent(email, password = 'password123') {
|
|
const agent = request.agent(app);
|
|
await agent.post('/api/login').send({ email, password });
|
|
return agent;
|
|
}
|
|
|
|
async function makeAdminDirect(userId) {
|
|
await Role.findOrCreate({
|
|
where: { user_id: userId },
|
|
defaults: { user_id: userId, is_admin: true },
|
|
});
|
|
}
|
|
|
|
describe('Admin Users Management API', () => {
|
|
let adminUser, adminAgent;
|
|
|
|
beforeEach(async () => {
|
|
// Create base user and elevate to admin via bootstrap path (no roles exist yet)
|
|
adminUser = await createTestUser({ email: 'admin@example.com' });
|
|
adminAgent = await loginAgent('admin@example.com');
|
|
// Ensure roles table clean for this worker between tests in this suite
|
|
await Role.destroy({ where: {} });
|
|
await makeAdminDirect(adminUser.id);
|
|
});
|
|
|
|
describe('Authentication and authorization', () => {
|
|
it('should require authentication', async () => {
|
|
const res = await request(app).get('/api/admin/users');
|
|
expect(res.status).toBe(401);
|
|
expect(res.body.error).toBe('Authentication required');
|
|
});
|
|
|
|
it('should forbid non-admin users', async () => {
|
|
const user = await createTestUser({ email: 'user@example.com' });
|
|
const agent = await loginAgent('user@example.com');
|
|
const res = await agent.get('/api/admin/users');
|
|
expect(res.status).toBe(403);
|
|
expect(res.body.error).toBe('Forbidden');
|
|
});
|
|
});
|
|
|
|
describe('GET /api/admin/users', () => {
|
|
it('should list users with role and created_at', async () => {
|
|
const res = await adminAgent.get('/api/admin/users');
|
|
expect(res.status).toBe(200);
|
|
expect(Array.isArray(res.body)).toBe(true);
|
|
const found = res.body.find((u) => u.email === 'admin@example.com');
|
|
expect(found).toBeDefined();
|
|
expect(found.role).toBe('admin');
|
|
expect(found.created_at).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('POST /api/admin/users', () => {
|
|
it('should create a regular user by default', async () => {
|
|
const res = await adminAgent
|
|
.post('/api/admin/users')
|
|
.send({ email: 'new@example.com', password: 'password123' });
|
|
expect(res.status).toBe(201);
|
|
expect(res.body.email).toBe('new@example.com');
|
|
expect(res.body.role).toBe('user');
|
|
});
|
|
|
|
it('should create an admin user when role is admin', async () => {
|
|
const res = await adminAgent.post('/api/admin/users').send({
|
|
email: 'newadmin@example.com',
|
|
password: 'password123',
|
|
role: 'admin',
|
|
});
|
|
expect(res.status).toBe(201);
|
|
expect(res.body.role).toBe('admin');
|
|
const role = await Role.findOne({
|
|
where: { user_id: res.body.id },
|
|
});
|
|
expect(role?.is_admin).toBe(true);
|
|
});
|
|
|
|
it('should validate email format', async () => {
|
|
const res = await adminAgent
|
|
.post('/api/admin/users')
|
|
.send({ email: 'invalid', password: 'password123' });
|
|
expect(res.status).toBe(400);
|
|
expect(res.body.error).toMatch(/Invalid email/i);
|
|
});
|
|
|
|
it('should validate password length', async () => {
|
|
const res = await adminAgent
|
|
.post('/api/admin/users')
|
|
.send({ email: 'shortpass@example.com', password: '123' });
|
|
expect(res.status).toBe(400);
|
|
expect(res.body.error).toMatch(
|
|
/Password must be at least 6 characters/i
|
|
);
|
|
});
|
|
|
|
it('should reject duplicate emails', async () => {
|
|
await adminAgent
|
|
.post('/api/admin/users')
|
|
.send({ email: 'dupe@example.com', password: 'password123' });
|
|
const res = await adminAgent
|
|
.post('/api/admin/users')
|
|
.send({ email: 'dupe@example.com', password: 'password123' });
|
|
expect(res.status).toBe(409);
|
|
expect(res.body.error).toMatch(/Email already exists/i);
|
|
});
|
|
});
|
|
|
|
describe('DELETE /api/admin/users/:id', () => {
|
|
it('should delete a user', async () => {
|
|
const createRes = await adminAgent.post('/api/admin/users').send({
|
|
email: 'todelete@example.com',
|
|
password: 'password123',
|
|
});
|
|
const id = createRes.body.id;
|
|
const delRes = await adminAgent.delete(`/api/admin/users/${id}`);
|
|
expect(delRes.status).toBe(204);
|
|
const list = await adminAgent.get('/api/admin/users');
|
|
expect(list.body.find((u) => u.id === id)).toBeUndefined();
|
|
});
|
|
|
|
it('should prevent deleting self', async () => {
|
|
const res = await adminAgent.delete(
|
|
`/api/admin/users/${adminUser.id}`
|
|
);
|
|
expect(res.status).toBe(400);
|
|
expect(res.body.error).toMatch(/Cannot delete your own account/i);
|
|
});
|
|
|
|
it('should return 404 for non-existent user', async () => {
|
|
const res = await adminAgent.delete('/api/admin/users/999999');
|
|
expect(res.status).toBe(404);
|
|
});
|
|
|
|
it('should allow deleting another admin when more than one admin exists', async () => {
|
|
// create another admin
|
|
const secondRes = await adminAgent.post('/api/admin/users').send({
|
|
email: 'secondadmin@example.com',
|
|
password: 'password123',
|
|
role: 'admin',
|
|
});
|
|
expect(secondRes.status).toBe(201);
|
|
const secondId = secondRes.body.id;
|
|
// delete the other admin
|
|
const delRes = await adminAgent.delete(
|
|
`/api/admin/users/${secondId}`
|
|
);
|
|
expect(delRes.status).toBe(204);
|
|
});
|
|
|
|
it('should prevent deletion of the last remaining admin', async () => {
|
|
// Try to delete the only admin (self is blocked already). Create another admin, delete first, then attempt to delete the last
|
|
const secondRes = await adminAgent.post('/api/admin/users').send({
|
|
email: 'secondadmin2@example.com',
|
|
password: 'password123',
|
|
role: 'admin',
|
|
});
|
|
const secondId = secondRes.body.id;
|
|
// Delete current admin by logging in as second admin
|
|
const secondAgent = await loginAgent('secondadmin2@example.com');
|
|
const delFirst = await secondAgent.delete(
|
|
`/api/admin/users/${adminUser.id}`
|
|
);
|
|
expect(delFirst.status).toBe(204);
|
|
// Now only one admin remains (secondId). Attempt to delete last admin should fail
|
|
const delLast = await secondAgent.delete(
|
|
`/api/admin/users/${secondId}`
|
|
);
|
|
expect(delLast.status).toBe(400);
|
|
// Depending on guard order, backend may return self-deletion or last-admin error
|
|
expect(delLast.body.error).toMatch(
|
|
/(last remaining admin|own account)/i
|
|
);
|
|
});
|
|
});
|
|
});
|