v0.5.11: зачистка мёртвого кода + новые интеграционные тесты
Удалено:
- src/routes/*.js (5 файлов) — старая монолитная архитектура
- views/dev-login.ejs — старый dev-логин
- tests/debug-session.js — тесты старой архитектуры
Добавлено:
- safeReturn в src/auth.js
- Новые интеграционные тесты tests/integration.js (67 тестов, UI+API)
Обновлено:
- tests/run-tests.js — убраны старые require, добавлены новые модули
- tests/tz-compliance.js — /exp → /api/v1/entries/export
- server.js — убран require('./src/routes/exp')
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
'use strict';
|
||||
require('dotenv').config();
|
||||
const supertest = require('supertest');
|
||||
const { start } = require('../server');
|
||||
(async () => {
|
||||
const app = await start();
|
||||
const agent = supertest.agent(app);
|
||||
|
||||
const r1 = await agent.get('/dev-login');
|
||||
console.log('GET /dev-login status:', r1.status);
|
||||
|
||||
const m = r1.text.match(/name="_csrf"\s+value="([^"]+)"/);
|
||||
console.log('csrf found:', !!m, m ? m[1].slice(0, 20) + '...' : '');
|
||||
|
||||
if (!m) { process.exit(1); }
|
||||
|
||||
const r2 = await agent.post('/dev-login').type('form').send({ preset: 'test', _csrf: m[1] });
|
||||
console.log('POST /dev-login status:', r2.status, 'location:', r2.headers.location);
|
||||
console.log('set-cookie:', r2.headers['set-cookie']);
|
||||
|
||||
const r3 = await agent.get('/');
|
||||
console.log('GET / status:', r3.status, 'location:', r3.headers.location);
|
||||
if (r3.status === 200) console.log('snippet:', r3.text.slice(0, 300));
|
||||
|
||||
process.exit(0);
|
||||
})().catch(e => { console.error(e.message, e.stack); process.exit(1); });
|
||||
+330
-822
File diff suppressed because it is too large
Load Diff
+9
-62
@@ -45,10 +45,14 @@ const modules = [
|
||||
'src/validators.js',
|
||||
'src/middleware/rateLimit.js',
|
||||
'src/middleware/csrf.js',
|
||||
'src/routes/export.js',
|
||||
'src/routes/auth.js',
|
||||
'src/routes/entries.js',
|
||||
'src/routes/admin.js',
|
||||
'src/api/index.js',
|
||||
'src/api/routes/entries.js',
|
||||
'src/api/routes/admin.js',
|
||||
'ui/index.js',
|
||||
'ui/routes/auth.js',
|
||||
'ui/routes/entries.js',
|
||||
'ui/routes/admin.js',
|
||||
'ui/routes/export.js',
|
||||
'server.js',
|
||||
];
|
||||
|
||||
@@ -133,8 +137,7 @@ test('no overlap public', overlaps('8.8.8.0/24', '9.9.9.0/24'), false);
|
||||
process.env.DEV_MODE = 'true';
|
||||
process.env.ADMIN_CLIENT_ID = 'WZ01112';
|
||||
|
||||
const { initAuth, requireAdmin } = require('../src/auth');
|
||||
const { safeReturn } = require('../src/routes/auth');
|
||||
const { initAuth, requireAdmin, safeReturn } = require('../src/auth');
|
||||
const csrfMiddleware = require('../src/middleware/csrf');
|
||||
|
||||
(async () => {
|
||||
@@ -219,63 +222,7 @@ const { doubleCsrfProtection: dp, generateCsrfToken: gct } = csrfMiddleware.init
|
||||
test('initCsrf returns doubleCsrfProtection fn', typeof dp, 'function');
|
||||
test('initCsrf returns generateCsrfToken fn', typeof gct, 'function');
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// 7. Export auth isolation — req.user влияет на фильтр компании
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
section('Export auth isolation');
|
||||
|
||||
// Тестируем логику без реального HTTP — проверяем что createRouter не бросает при require
|
||||
const exportModule = require('../src/routes/export');
|
||||
test('export module exports createRouter', typeof exportModule.createRouter, 'function');
|
||||
|
||||
// Логика изоляции: обычный пользователь получает companyId из q.getOrCreateCompany,
|
||||
// admin без ?company → companyId = null (все компании).
|
||||
// Проверяем через минимальный stub — без реальной БД.
|
||||
let exportCompanyIdCaptured = null;
|
||||
const stubQ = {
|
||||
getOrCreateCompany: async (clientId) => ({ id: 42, clientId }),
|
||||
getExportCIDRs: async (cid) => { exportCompanyIdCaptured = cid; return []; },
|
||||
};
|
||||
const exportRouter = exportModule.createRouter({
|
||||
q: stubQ,
|
||||
exportLimiter: (req, res, next) => next(),
|
||||
aggregateCIDRs: (x) => x,
|
||||
});
|
||||
|
||||
// Симулируем GET /export для обычного пользователя
|
||||
const mockReqUser = {
|
||||
user: { isAdmin: false, clientId: 'WZ01325', companyName: 'TestCo' },
|
||||
query: {},
|
||||
method: 'GET',
|
||||
path: '/export',
|
||||
};
|
||||
const mockResUser = {
|
||||
setHeader: () => {},
|
||||
send: () => {},
|
||||
};
|
||||
// Найдём обработчик /export в router.stack
|
||||
const exportLayer = exportRouter.stack.find(l => l.route && l.route.path === '/export');
|
||||
test('export route registered', !!exportLayer, true);
|
||||
|
||||
if (exportLayer) {
|
||||
// Запускаем handler напрямую
|
||||
const handlers = exportLayer.route.stack.map(l => l.handle);
|
||||
// exportLimiter (stub пропускает) + async handler
|
||||
exportCompanyIdCaptured = 'NOT_CALLED';
|
||||
await handlers[handlers.length - 1](mockReqUser, mockResUser, () => {});
|
||||
test('user export: companyId filtered (42)', exportCompanyIdCaptured, 42);
|
||||
|
||||
// Симулируем GET /export для admin без ?company
|
||||
exportCompanyIdCaptured = 'NOT_CALLED';
|
||||
const mockReqAdmin = {
|
||||
user: { isAdmin: true, clientId: 'WZ01112', companyName: 'Admin' },
|
||||
query: {},
|
||||
method: 'GET',
|
||||
path: '/export',
|
||||
};
|
||||
await handlers[handlers.length - 1](mockReqAdmin, mockResUser, () => {});
|
||||
test('admin export: no filter (null)', exportCompanyIdCaptured, null);
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Итог
|
||||
|
||||
@@ -240,15 +240,13 @@ function cidr(ip) { return ip.includes('/') ? ip : ip + '/32'; }
|
||||
// ══════════════════════════════════════════════════
|
||||
console.log('── ТЗ 4.7: Export ──');
|
||||
{
|
||||
// 4.7a: /exp публичный (временный обходной путь)
|
||||
const r = await req.get('/exp');
|
||||
log(r.status === 200, '4.7a: GET /exp → 200 (без авторизации)');
|
||||
log((r.headers['content-type'] || '').includes('text/plain'), '4.7b: Content-Type text/plain');
|
||||
|
||||
// 4.7c: /exp содержит только активные записи (проверим через добавление)
|
||||
// 4.7a: /api/v1/entries/export (требует Bearer token)
|
||||
const cr = await req.post('/api/v1/entries').set(bearer(tokUser)).send({ value: C.A, comment: 'export-test' });
|
||||
const r2 = await req.get('/exp');
|
||||
log(r2.text.includes(cidr(C.A)), '4.7c: /exp содержит добавленный CIDR');
|
||||
|
||||
const r = await req.get('/api/v1/entries/export').set(bearer(tokUser));
|
||||
log(r.status === 200, '4.7a: GET /api/v1/entries/export → 200', `status=${r.status}`);
|
||||
log((r.headers['content-type'] || '').includes('text/plain'), '4.7b: Content-Type text/plain');
|
||||
log(r.text.includes(cidr(C.A)), '4.7c: export содержит добавленный CIDR');
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user