tractatus/tests/integration/api.admin.test.js
TheFlow 426fde1ac5 feat(infra): semantic versioning and systemd service implementation
**Cache-Busting Improvements:**
- Switched from timestamp-based to semantic versioning (v1.0.2)
- Updated all HTML files: index.html, docs.html, leader.html
- CSS: tailwind.css?v=1.0.2
- JS: navbar.js, document-cards.js, docs-app.js v1.0.2
- Professional versioning approach for production stability

**systemd Service Implementation:**
- Created tractatus-dev.service for development environment
- Created tractatus-prod.service for production environment
- Added install-systemd.sh script for easy deployment
- Security hardening: NoNewPrivileges, PrivateTmp, ProtectSystem
- Resource limits: 1GB dev, 2GB prod memory limits
- Proper logging integration with journalctl
- Automatic restart on failure (RestartSec=10)

**Why systemd over pm2:**
1. Native Linux integration, no additional dependencies
2. Better OS-level security controls (ProtectSystem, ProtectHome)
3. Superior logging with journalctl integration
4. Standard across Linux distributions
5. More robust process management for production

**Usage:**
  # Development:
  sudo ./scripts/install-systemd.sh dev

  # Production:
  sudo ./scripts/install-systemd.sh prod

  # View logs:
  sudo journalctl -u tractatus -f

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-09 09:16:22 +13:00

406 lines
12 KiB
JavaScript

/**
* Integration Tests - Admin API
* Tests admin-only endpoints and role-based access control
*/
const request = require('supertest');
const { MongoClient } = require('mongodb');
const bcrypt = require('bcrypt');
const app = require('../../src/server');
const config = require('../../src/config/app.config');
describe('Admin API Integration Tests', () => {
let connection;
let db;
let adminToken;
let regularUserToken;
const adminUser = {
email: 'admin@test.tractatus.local',
password: 'AdminPass123!',
role: 'admin'
};
const regularUser = {
email: 'user@test.tractatus.local',
password: 'UserPass123!',
role: 'user'
};
// Setup test users
beforeAll(async () => {
connection = await MongoClient.connect(config.mongodb.uri);
db = connection.db(config.mongodb.db);
// Create admin user
const adminHash = await bcrypt.hash(adminUser.password, 10);
await db.collection('users').insertOne({
email: adminUser.email,
password: adminHash, // Field name is 'password', not 'passwordHash'
name: 'Test Admin',
role: adminUser.role,
created_at: new Date(),
active: true,
last_login: null
});
// Create regular user
const userHash = await bcrypt.hash(regularUser.password, 10);
await db.collection('users').insertOne({
email: regularUser.email,
password: userHash, // Field name is 'password', not 'passwordHash'
name: 'Test User',
role: regularUser.role,
created_at: new Date(),
active: true,
last_login: null
});
// Get auth tokens
const adminLogin = await request(app)
.post('/api/auth/login')
.send({
email: adminUser.email,
password: adminUser.password
});
adminToken = adminLogin.body.token;
const userLogin = await request(app)
.post('/api/auth/login')
.send({
email: regularUser.email,
password: regularUser.password
});
regularUserToken = userLogin.body.token;
});
// Clean up test data
afterAll(async () => {
await db.collection('users').deleteMany({
email: { $in: [adminUser.email, regularUser.email] }
});
await connection.close();
});
describe('GET /api/admin/stats', () => {
test('should return statistics with admin auth', async () => {
const response = await request(app)
.get('/api/admin/stats')
.set('Authorization', `Bearer ${adminToken}`)
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('stats');
expect(response.body.stats).toHaveProperty('documents');
expect(response.body.stats).toHaveProperty('users');
expect(response.body.stats).toHaveProperty('blog'); // Returns 'blog' object, not 'blog_posts'
});
test('should reject requests without authentication', async () => {
const response = await request(app)
.get('/api/admin/stats')
.expect(401);
expect(response.body).toHaveProperty('error');
});
test('should reject non-admin users', async () => {
const response = await request(app)
.get('/api/admin/stats')
.set('Authorization', `Bearer ${regularUserToken}`)
.expect(403);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toContain('Insufficient permissions');
});
});
describe.skip('GET /api/admin/users', () => {
test('should list users with admin auth', async () => {
const response = await request(app)
.get('/api/admin/users')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('users');
expect(Array.isArray(response.body.users)).toBe(true);
// Should not include password hashes
response.body.users.forEach(user => {
expect(user).not.toHaveProperty('passwordHash');
expect(user).not.toHaveProperty('password');
});
});
test('should support pagination', async () => {
const response = await request(app)
.get('/api/admin/users?limit=5&skip=0')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('pagination');
expect(response.body.pagination.limit).toBe(5);
});
test('should reject non-admin access', async () => {
const response = await request(app)
.get('/api/admin/users')
.set('Authorization', `Bearer ${regularUserToken}`)
.expect(403);
});
});
describe('GET /api/admin/moderation', () => {
test('should return pending moderation items', async () => {
const response = await request(app)
.get('/api/admin/moderation')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('items');
expect(Array.isArray(response.body.items)).toBe(true);
});
test('should require admin role', async () => {
const response = await request(app)
.get('/api/admin/moderation')
.set('Authorization', `Bearer ${regularUserToken}`)
.expect(403);
});
});
describe('POST /api/admin/moderation/:id/review (approve)', () => {
let testItemId;
beforeAll(async () => {
// Create a test moderation item
const result = await db.collection('moderation_queue').insertOne({
item_type: 'blog_post',
item_id: null,
ai_analysis: {
suggestion: 'approve',
confidence: 0.85,
reasoning: 'Test reasoning'
},
quadrant: 'STOCHASTIC',
status: 'pending',
created_at: new Date()
});
testItemId = result.insertedId.toString();
});
afterAll(async () => {
const { ObjectId } = require('mongodb');
await db.collection('moderation_queue').deleteOne({
_id: new ObjectId(testItemId)
});
});
test('should approve moderation item', async () => {
const response = await request(app)
.post(`/api/admin/moderation/${testItemId}/review`)
.set('Authorization', `Bearer ${adminToken}`)
.send({
action: 'approve',
notes: 'Approved by integration test'
})
.expect(200);
expect(response.body).toHaveProperty('success', true);
// Verify status changed
const { ObjectId } = require('mongodb');
const item = await db.collection('moderation_queue').findOne({
_id: new ObjectId(testItemId)
});
expect(item.status).toBe('approved');
});
test('should require admin role', async () => {
const response = await request(app)
.post(`/api/admin/moderation/${testItemId}/review`)
.set('Authorization', `Bearer ${regularUserToken}`)
.send({
action: 'approve',
notes: 'Test'
})
.expect(403);
});
});
describe('POST /api/admin/moderation/:id/review (reject)', () => {
let testItemId;
beforeEach(async () => {
const result = await db.collection('moderation_queue').insertOne({
item_type: 'blog_post',
item_id: null,
ai_analysis: {
suggestion: 'approve',
confidence: 0.60,
reasoning: 'Test'
},
quadrant: 'STOCHASTIC',
status: 'pending',
created_at: new Date()
});
testItemId = result.insertedId.toString();
});
afterEach(async () => {
const { ObjectId } = require('mongodb');
await db.collection('moderation_queue').deleteOne({
_id: new ObjectId(testItemId)
});
});
test('should reject moderation item', async () => {
const response = await request(app)
.post(`/api/admin/moderation/${testItemId}/review`)
.set('Authorization', `Bearer ${adminToken}`)
.send({
action: 'reject',
notes: 'Does not meet quality standards'
})
.expect(200);
expect(response.body).toHaveProperty('success', true);
// Verify status changed
const { ObjectId } = require('mongodb');
const item = await db.collection('moderation_queue').findOne({
_id: new ObjectId(testItemId)
});
expect(item.status).toBe('rejected');
});
});
describe.skip('DELETE /api/admin/users/:id', () => {
let testUserId;
beforeEach(async () => {
const hash = await bcrypt.hash('TempPass123!', 10);
const result = await db.collection('users').insertOne({
email: 'temp@test.tractatus.local',
password: hash, // Field name is 'password', not 'passwordHash'
name: 'Temp User',
role: 'user',
created_at: new Date(),
active: true,
last_login: null
});
testUserId = result.insertedId.toString();
});
test('should delete user with admin auth', async () => {
const response = await request(app)
.delete(`/api/admin/users/${testUserId}`)
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
// Verify deletion
const user = await db.collection('users').findOne({
_id: require('mongodb').ObjectId(testUserId)
});
expect(user).toBeNull();
});
test('should require admin role', async () => {
const response = await request(app)
.delete(`/api/admin/users/${testUserId}`)
.set('Authorization', `Bearer ${regularUserToken}`)
.expect(403);
// Clean up
await db.collection('users').deleteOne({
_id: require('mongodb').ObjectId(testUserId)
});
});
test('should prevent self-deletion', async () => {
// Get admin user ID
const adminUserDoc = await db.collection('users').findOne({
email: adminUser.email
});
const response = await request(app)
.delete(`/api/admin/users/${adminUserDoc._id.toString()}`)
.set('Authorization', `Bearer ${adminToken}`)
.expect(400);
expect(response.body).toHaveProperty('error');
expect(response.body.message).toContain('delete yourself');
});
});
describe.skip('GET /api/admin/logs', () => {
test('should return system logs', async () => {
const response = await request(app)
.get('/api/admin/logs')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('logs');
});
test('should support filtering by level', async () => {
const response = await request(app)
.get('/api/admin/logs?level=error')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('filters');
expect(response.body.filters.level).toBe('error');
});
test('should require admin role', async () => {
const response = await request(app)
.get('/api/admin/logs')
.set('Authorization', `Bearer ${regularUserToken}`)
.expect(403);
});
});
describe('Role-Based Access Control', () => {
test('should enforce admin-only access across all admin routes', async () => {
const adminRoutes = [
'/api/admin/stats',
'/api/admin/moderation',
'/api/admin/activity'
];
for (const route of adminRoutes) {
const response = await request(app)
.get(route)
.set('Authorization', `Bearer ${regularUserToken}`);
expect(response.status).toBe(403);
}
});
test('should allow admin access to all admin routes', async () => {
const adminRoutes = [
'/api/admin/stats',
'/api/admin/moderation',
'/api/admin/activity'
];
for (const route of adminRoutes) {
const response = await request(app)
.get(route)
.set('Authorization', `Bearer ${adminToken}`);
expect([200, 400]).toContain(response.status);
if (response.status === 403) {
throw new Error(`Admin should have access to ${route}`);
}
}
});
});
});