2fd7f6a463
- migrate.js → legacy-migrate.js (kept for rollback, delete 2026-07-01) - tests/setup.js now uses migrations-runner.run() on fresh temp DB - npm run migrate → versioned runner (was legacy init-every-start) - npm run migrate:legacy → legacy-migrate.js (emergency rollback only) After `npm run migrate:bootstrap` on prod: npm run migrate → "Nothing to apply — schema is up to date" All 32 previously-passing tests continue to pass. Pre-existing 3 auth.test.js failures (rate-limiter shared state) unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
117 lines
4.1 KiB
JavaScript
117 lines
4.1 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'));
|
|
|
|
// 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 };
|