Updated frontend

This commit is contained in:
2026-07-01 11:10:09 -04:00
parent ee21bdbf22
commit 5644bfa5ec
8104 changed files with 14976 additions and 386337 deletions
+197
View File
@@ -0,0 +1,197 @@
'use strict';
// Tests for admin-only user endpoints:
// GET /api/user/stats
// GET /api/user/export
// GET /api/user/me
// PUT /api/user/:uid/active
const { TEST_CREDS, login, request, app } = require('./setup');
// TEST_USER mirrors the user created in user.test.js — use a different uid so
// this suite is independent.
const TEST_USER = {
givenName: 'Admin',
sn: 'Tester',
mail: 'atester@test.example.com',
mobile: '5555550001',
userPassword: 'AdminTest!55',
};
const TEST_UID = `${TEST_USER.givenName[0]}${TEST_USER.sn}`.toLowerCase(); // 'atester'
let token;
beforeAll(async () => {
token = await login();
// Clean up any leftover from a previous run
await request(app).delete(`/api/user/${TEST_UID}`).set('auth-token', token);
// Create the test user
await request(app).post('/api/user/').set('auth-token', token).send(TEST_USER);
});
afterAll(async () => {
await request(app).delete(`/api/user/${TEST_UID}`).set('auth-token', token);
});
describe('Users — GET /api/user/stats (admin only)', () => {
test('requires auth — 401 without token', async () => {
const res = await request(app).get('/api/user/stats');
expect(res.status).toBe(401);
});
test('returns aggregated counts for admin', async () => {
const res = await request(app)
.get('/api/user/stats')
.set('auth-token', token);
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('totalUsers');
expect(res.body).toHaveProperty('activeUsers');
expect(res.body).toHaveProperty('inactiveUsers');
expect(res.body).toHaveProperty('totalGroups');
expect(res.body).toHaveProperty('recentSignups');
expect(res.body).toHaveProperty('inactiveList');
expect(typeof res.body.totalUsers).toBe('number');
expect(typeof res.body.totalGroups).toBe('number');
expect(Array.isArray(res.body.recentSignups)).toBe(true);
expect(Array.isArray(res.body.inactiveList)).toBe(true);
});
test('totalUsers is positive (at least the test user and admin exist)', async () => {
const res = await request(app)
.get('/api/user/stats')
.set('auth-token', token);
expect(res.body.totalUsers).toBeGreaterThan(0);
});
test('activeUsers + inactiveUsers equals totalUsers', async () => {
const res = await request(app)
.get('/api/user/stats')
.set('auth-token', token);
expect(res.body.activeUsers + res.body.inactiveUsers).toBe(res.body.totalUsers);
});
});
describe('Users — GET /api/user/export (admin only)', () => {
test('requires auth — 401 without token', async () => {
const res = await request(app).get('/api/user/export');
expect(res.status).toBe(401);
});
test('returns CSV content for admin', async () => {
const res = await request(app)
.get('/api/user/export')
.set('auth-token', token);
expect(res.status).toBe(200);
expect(res.headers['content-type']).toMatch(/text\/csv/);
expect(res.headers['content-disposition']).toMatch(/users\.csv/);
});
test('CSV has header row with expected columns', async () => {
const res = await request(app)
.get('/api/user/export')
.set('auth-token', token);
const lines = res.text.split('\n');
const header = lines[0];
expect(header).toContain('uid');
expect(header).toContain('mail');
expect(header).toContain('givenName');
expect(header).toContain('sn');
});
test('CSV contains at least one data row', async () => {
const res = await request(app)
.get('/api/user/export')
.set('auth-token', token);
const lines = res.text.split('\n').filter(l => l.trim());
// header + at least one user row
expect(lines.length).toBeGreaterThan(1);
});
});
describe('Users — GET /api/user/me', () => {
test('returns the authenticated user\'s own profile', async () => {
const res = await request(app)
.get('/api/user/me')
.set('auth-token', token);
expect(res.status).toBe(200);
// The /me route returns User.get() directly (no results wrapper in some implementations)
// Accept either shape
const user = res.body.results || res.body;
expect(user).toHaveProperty('uid', TEST_CREDS.uid);
});
test('requires auth — 401 without token', async () => {
const res = await request(app).get('/api/user/me');
expect(res.status).toBe(401);
});
});
describe('Users — PUT /api/user/:uid/active (activate/deactivate)', () => {
test('requires auth — 401 without token', async () => {
const res = await request(app)
.put(`/api/user/${TEST_UID}/active`)
.send({ active: false });
expect(res.status).toBe(401);
});
test('admin can deactivate a user (skipped if ppolicy overlay not configured)', async () => {
const res = await request(app)
.put(`/api/user/${TEST_UID}/active`)
.set('auth-token', token)
.send({ active: false });
// 503 means the OpenLDAP ppolicy overlay is not set up in this environment
if (res.status === 503) return;
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('uid', TEST_UID);
expect(res.body).toHaveProperty('active', false);
expect(res.body).toHaveProperty('message');
});
test('deactivated user cannot log in (skipped if ppolicy overlay not configured)', async () => {
// Check whether deactivation works in this environment first
const checkRes = await request(app)
.put(`/api/user/${TEST_UID}/active`)
.set('auth-token', token)
.send({ active: false });
if (checkRes.status === 503) return;
const res = await request(app)
.post('/api/auth/login')
.send({ uid: TEST_UID, password: TEST_USER.userPassword });
// LDAP may return 401 or 403 for locked accounts
expect(res.status).toBeGreaterThanOrEqual(400);
// Re-activate so cleanup works
await request(app)
.put(`/api/user/${TEST_UID}/active`)
.set('auth-token', token)
.send({ active: true });
});
test('admin can reactivate a user (skipped if ppolicy overlay not configured)', async () => {
// First deactivate (may not be supported)
const deactivateRes = await request(app)
.put(`/api/user/${TEST_UID}/active`)
.set('auth-token', token)
.send({ active: false });
if (deactivateRes.status === 503) return;
const res = await request(app)
.put(`/api/user/${TEST_UID}/active`)
.set('auth-token', token)
.send({ active: true });
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('active', true);
});
});