Files
Learn_System/backend/tests/setup.js
T
Maxim Dolgolyov bdc8bef857 feat(permissions): C-4a — API конструктора ролей (/api/roles, admin)
rolesController + routes/roles (admin, inline guards): GET список (с числом
пользователей), POST создать кастомную роль (имя-идентификатор + метка + base_roles;
засев прав из функциональной базы), PUT изменить, DELETE удалить (пользователей
возвращает на базу), GET /:name/permissions (эффективная карта база+оверлей + defs).
setPermission теперь принимает кастомные роли (ключ валидируется по базе, хранится
под именем роли). Смонтировано в server.js + тест-харнесс. Тест roles-api 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:21:44 +03:00

129 lines
4.7 KiB
JavaScript

/**
* Test setup — creates a temp SQLite DB, runs migrations, boots Express app.
* Usage: const { app, db, getToken } = require('./setup');
*/
const path = require('path');
const os = require('os');
const fs = require('fs');
// Unique temp DB per test run
const tmpDb = path.join(os.tmpdir(), `ls_test_${Date.now()}_${Math.random().toString(36).slice(2)}.db`);
process.env.DB_PATH = tmpDb;
process.env.JWT_SECRET = 'test_secret_that_is_at_least_32_characters_long!!';
process.env.JWT_EXPIRES_IN = '1h';
process.env.NODE_ENV = 'test';
// Now require app modules (they will use the temp DB)
const db = require('../src/db/db');
require('../src/db/migrations-runner').run();
// Seed permissions
const { seedDefaults } = require('../src/controllers/permissionsController');
seedDefaults();
// Seed achievements
const { seedAchievements } = require('../src/controllers/gamificationController');
seedAchievements();
// Build Express app (without listen)
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json({ limit: '1mb' }));
app.use('/api/auth', require('../src/routes/auth'));
app.use('/api/sessions', require('../src/routes/sessions'));
app.use('/api/shop', require('../src/routes/shop'));
app.use('/api/classes', require('../src/routes/classes'));
app.use('/api/gamification', require('../src/routes/gamification'));
app.use('/api/admin', require('../src/routes/admin'));
app.use('/api/subjects', require('../src/routes/subjects'));
app.use('/api/questions', require('../src/routes/questions'));
// Additional routes for integration tests
app.use('/api/permissions', require('../src/routes/permissions'));
app.use('/api/access', require('../src/routes/access'));
app.use('/api/lab', require('../src/routes/lab'));
app.use('/api/courses', require('../src/routes/courses'));
app.use('/api/roles', require('../src/routes/roles'));
// Feature-gated routes (requireFeature checks app_settings in DB)
const { requireFeature } = require('../src/middleware/features');
app.use('/api/pet', requireFeature('pet'), require('../src/routes/pet'));
app.use('/api/biochem', requireFeature('biochem'), require('../src/routes/biochem'));
// Error handler
app.use((err, _req, res, _next) => {
res.status(err.status || 500).json({ error: err.message || 'Server error' });
});
/* ── Helper: register + get token ─────────────────────────────────────── */
let _userCounter = 0;
async function getToken(role = 'student', nameOverride) {
const n = ++_userCounter;
const email = `test${n}@example.com`;
const name = nameOverride || `Test User ${n}`;
const password = 'password123';
// Register via API
const res = await inject('POST', '/api/auth/register', { email, password, name });
const token = res.body.token;
const userId = res.body.user.id;
// If role is not student, set it directly in DB
if (role !== 'student') {
db.prepare('UPDATE users SET role = ? WHERE id = ?').run(role, userId);
}
return { token, userId, email, name };
}
/* ── HTTP injection (no real TCP) ─────────────────────────────────────── */
const http = require('http');
let _server;
function ensureServer() {
if (!_server) {
_server = http.createServer(app);
_server.listen(0); // random port
}
return _server;
}
async function inject(method, path, body, token) {
const server = ensureServer();
const port = server.address().port;
return new Promise((resolve, reject) => {
const data = body ? JSON.stringify(body) : null;
const headers = { 'Content-Type': 'application/json' };
if (token) headers['Authorization'] = `Bearer ${token}`;
if (data) headers['Content-Length'] = Buffer.byteLength(data);
const req = http.request({ hostname: '127.0.0.1', port, path, method, headers }, (res) => {
let chunks = [];
res.on('data', c => chunks.push(c));
res.on('end', () => {
const raw = Buffer.concat(chunks).toString();
let parsed;
try { parsed = JSON.parse(raw); } catch { parsed = raw; }
resolve({ status: res.statusCode, body: parsed, headers: res.headers });
});
});
req.on('error', reject);
if (data) req.write(data);
req.end();
});
}
/* ── Cleanup ──────────────────────────────────────────────────────────── */
function cleanup() {
if (_server) _server.close();
try { fs.unlinkSync(tmpDb); } catch {}
}
module.exports = { app, db, getToken, inject, cleanup };