'use strict';

/**
 * tests/stress.js — БЕСПОЩАДНЫЕ стресс-тесты (concurrency, race conditions, edge cases).
 *
 * Запуск: node tests/stress.js
 * Требует: .env с DEV_MODE=true и данными БД.
 *
 * Использует API-слой НАПРЯМУЮ (queries.js + auth.js), БЕЗ HTTP,
 * чтобы тестировать чистую бизнес-логику без задержек сети.
 *
 * Разделы:
 *   S1. Concurrency — параллельные вставки (FOR UPDATE lock)
 *   S2. Limit enforcement под нагрузкой
 *   S3. Mixed CRUD — concurrent create + update + delete
 *   S4. Auth — tampered/expired/wrong-alg tokens
 *   S5. Edge cases — unicode, SQL injection, XSS, нулевые значения
 *   S6. Validator — граничные CIDR маски
 *   S7. Session vs Bearer — конфликт источников пользователя
 */

require('dotenv').config();

const { pool } = require('../src/db');
const q = require('../src/queries');
const authMod = require('../src/auth');

let pass = 0, fail = 0;
const errors = [];

function log(ok, name, detail = '') {
  if (ok) { console.log('  PASS', name); pass++; }
  else { const msg = ' FAIL ' + name + (detail ? ': ' + detail : ''); console.error(msg); errors.push(msg); fail++; }
}

function cidr(ip) { return ip.includes('/') ? ip : ip + '/32'; }

// ── Тестовые CIDR (публичные) ──────────────────────────────────────────────────
const C = {
  A: '8.8.8.1', B: '8.8.8.2', C: '8.8.8.3', D: '8.8.8.4', E: '8.8.8.5',
  F: '8.8.8.6', G: '8.8.8.7', H: '8.8.8.8', I: '8.8.8.9', J: '8.8.8.10',
  SUBNET: '9.9.9.5/24',
};

// ── Cleanup ────────────────────────────────────────────────────────────────────
async function cleanupAll() {
  const all = Object.values(C).map(c => cidr(c));
  all.push('9.9.9.0/24');
  for (const c of all) {
    await pool.query(
      `UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress-cleanup'
       WHERE value_cidr=$1 AND deleted_at IS NULL`, [c]
    );
  }
}

// ═══════════════════════════════════════════════════════════════════════════════
(async () => {
  console.log('\n══════════════════════════════════════════════════════');
  console.log('  STRESS TESTS — concurrency, races, edge cases');
  console.log('══════════════════════════════════════════════════════\n');

  // ── Инициализация ────────────────────────────────────────────────────────────
  // ВАЖНО: server.start() ПЕРВЫМ — он вызывает initAuth() и устанавливает ключи.
  // Только потом вызываем initAuth() повторно (ключи уже в модульных переменных).
  const mod = require('../server');
  const app = await mod.start();
  const auth = await authMod.initAuth();
  const { MOCK_USERS } = require('../src/config');
  const admin = MOCK_USERS.find(u => u.id === 'admin');
  const user  = MOCK_USERS.find(u => u.id === 'test');

  const tokenAdmin = auth.issueMockToken({ clientId: admin.clientId, companyId: admin.companyId, companyName: admin.companyName, email: admin.email });
  const tokenUser  = auth.issueMockToken({ clientId: user.clientId, companyId: user.companyId, companyName: user.companyName, email: user.email });

  // Получаем ID компании пользователя
  const userCompany = await q.getOrCreateCompany(user.clientId, user.companyName);
  const userCompanyId = userCompany.id;
  log(!!userCompanyId, 'setup: компания user создана', `id=${userCompanyId}`);

  await cleanupAll();

  // ══════════════════════════════════════════════════════════════════════════════
  // S1. CONCURRENCY — параллельные вставки (FOR UPDATE должен сериализовать)
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S1. Concurrency: параллельные вставки ──');

  // S1.1: 10 одновременных вставок ОДНОГО CIDR — должен пройти ровно один
  {
    const PROMISES = 10;
    const results = await Promise.allSettled(
      Array(PROMISES).fill(0).map(() =>
        q.createEntry(userCompanyId, C.A, 'race-same', 'stress@test')
      )
    );
    const ok = results.filter(r => r.status === 'fulfilled').length;
    const dup = results.filter(r => r.status === 'rejected' && r.reason.message.includes('уже существует')).length;
    log(ok === 1, 'S1.1: 10× createEntry(один CIDR) → ровно 1 успех', `ok=${ok} dup=${dup}`);
    log(ok + dup === PROMISES, 'S1.2: остальные 9 — дубликат', `total=${ok + dup}`);

    // Чистка
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE value_cidr=$1 AND company_id=$2 AND deleted_at IS NULL`,
      [cidr(C.A), userCompanyId]);
  }

  // S1.3: 15 одновременных вставок разных CIDR при лимите 5
  {
    await q.setLimit(userCompanyId, 5);
    const cidrPool = [C.A, C.B, C.C, C.D, C.E, C.F, C.G, C.H, C.I, C.J,
      '8.8.9.1', '8.8.9.2', '8.8.9.3', '8.8.9.4', '8.8.9.5'];
    const results = await Promise.allSettled(
      cidrPool.map(c => q.createEntry(userCompanyId, c, 'race-limit', 'stress@test'))
    );
    const created = results.filter(r => r.status === 'fulfilled').length;
    const limited = results.filter(r => r.status === 'rejected' && r.reason.message.includes('Лимит')).length;
    log(created <= 5, 'S1.3: лимит=5, 15 потоков → не более 5 создано', `created=${created} limited=${limited}`);
    log(created + limited === 15, 'S1.4: все ответы — или 201 или лимит', `total=${created + limited}`);

    // Сброс лимита и чистка
    await q.setLimit(userCompanyId, null);
    for (const c of cidrPool) {
      await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE value_cidr=$1 AND company_id=$2 AND deleted_at IS NULL`,
        [cidr(c), userCompanyId]);
    }
  }

  // S1.5: конкурентный create + delete одного CIDR
  {
    // Сначала создаём
    const entry = (await q.createEntry(userCompanyId, C.A, 'victim', 'stress@test')).entry;
    // Параллельно: один удаляет, один пытается обновить
    const [del, upd] = await Promise.allSettled([
      q.deleteEntry(entry.id, userCompanyId, 'stress@test'),
      q.updateEntry(entry.id, userCompanyId, C.B, 'stolen', 'stress@test'),
    ]);
    const deleted = del.status === 'fulfilled';
    const updated = upd.status === 'fulfilled';
    // Должен победить хотя бы один (оба могут — если update выполнился до delete)
    const bothOk = deleted && updated;
    const oneOk = (deleted && !updated) || (!deleted && updated);
    log(oneOk || bothOk, 'S1.5: concurrent delete+update — нет краша, минимум один успех',
      `deleted=${deleted} updated=${updated}`);

    // Чистка что осталось
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE value_cidr IN ($1,$2) AND company_id=$3 AND deleted_at IS NULL`,
      [cidr(C.A), cidr(C.B), userCompanyId]);
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S2. LIMIT ENFORCEMENT — гонка на границе лимита
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S2. Limit enforcement под нагрузкой ──');

  // S2.1: лимит меняется во время создания
  {
    // Создаём 3 записи при лимите 10
    await q.setLimit(userCompanyId, 10);
    const e1 = await q.createEntry(userCompanyId, C.A, 'limit1', 'stress@test');
    const e2 = await q.createEntry(userCompanyId, C.B, 'limit2', 'stress@test');
    const e3 = await q.createEntry(userCompanyId, C.C, 'limit3', 'stress@test');

    // Параллельно: меняем лимит на 2 и пытаемся создать 4-ю запись
    const [lim, cre] = await Promise.allSettled([
      q.setLimit(userCompanyId, 2),
      q.createEntry(userCompanyId, C.D, 'limit4', 'stress@test'),
    ]);
    // Если лимит успел поменяться ДО проверки — 4-я запись не создастся
    log(true, 'S2.1: concurrent setLimit+create — нет краша', `lim=${lim.status} cre=${cre.status}`);

    // Чистка
    await q.setLimit(userCompanyId, null);
    for (const c of [C.A, C.B, C.C, C.D]) {
      await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE value_cidr=$1 AND company_id=$2 AND deleted_at IS NULL`,
        [cidr(c), userCompanyId]);
    }
  }

  // S2.2: лимит = 0, попытка создать
  {
    await q.setLimit(userCompanyId, 0);
    try {
      await q.createEntry(userCompanyId, C.A, 'nope', 'stress@test');
      log(false, 'S2.2: лимит=0 → createEntry rejected');
    } catch (e) {
      log(e.message.includes('Лимит'), 'S2.2: лимит=0 → createEntry rejected', e.message);
    }
    await q.setLimit(userCompanyId, null);
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S3. MIXED CRUD — concurrent create + update + delete разных записей
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S3. Mixed concurrent CRUD ──');

  {
    // Создаём 3 записи
    const e1 = (await q.createEntry(userCompanyId, C.A, 'mix1', 'stress@test')).entry;
    const e2 = (await q.createEntry(userCompanyId, C.B, 'mix2', 'stress@test')).entry;
    const e3 = (await q.createEntry(userCompanyId, C.C, 'mix3', 'stress@test')).entry;

    // Параллельно: обновить e1, удалить e2, создать e4
    const results = await Promise.allSettled([
      q.updateEntry(e1.id, userCompanyId, C.D, 'updated', 'stress@test'),
      q.deleteEntry(e2.id, userCompanyId, 'stress@test'),
      q.createEntry(userCompanyId, C.E, 'new', 'stress@test'),
    ]);

    const allOk = results.every(r => r.status === 'fulfilled');
    log(allOk, 'S3.1: concurrent update+delete+create — все три успешны',
      results.map(r => r.status).join(','));

    // Проверяем изоляцию: e1 должно быть обновлено, e2 удалено, e4 создано
    const rows = await q.listEntries(userCompanyId);
    const activeCidrs = rows.map(r => r.value_cidr);
    log(activeCidrs.includes(cidr(C.D)), 'S3.2: e1 value_cidr обновлён на D');
    log(!activeCidrs.includes(cidr(C.B)), 'S3.3: e2 не в списке (удалён)');
    log(activeCidrs.includes(cidr(C.E)), 'S3.4: e4 создан');

    // Чистка
    for (const c of [C.A, C.B, C.C, C.D, C.E]) {
      await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE value_cidr=$1 AND company_id=$2 AND deleted_at IS NULL`,
        [cidr(c), userCompanyId]);
    }
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S4. AUTH — tampered/expired/wrong tokens
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S4. Auth attacks ──');

  // S4.1: Token signed with different key
  {
    const crypto = require('crypto');
    const jwt = require('jsonwebtoken');
    const fakeKey = crypto.generateKeyPairSync('rsa', {
      modulusLength: 2048,
      publicKeyEncoding: { type: 'spki', format: 'pem' },
      privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
    });
    const fakeToken = jwt.sign(
      { ClientID: user.clientId, email: user.email },
      fakeKey.privateKey,
      { algorithm: 'RS256', issuer: 'mock-auth-api', expiresIn: '1h', keyid: 'fake' }
    );
    try {
      jwt.verify(fakeToken, fakeKey.publicKey, { algorithms: ['RS256'] });
      log(true, 'S4.1: фальшивый токен проходит свою же проверку (контроль)');
    } catch { log(false, 'S4.1: контроль'); }

  // S4.2: Token signed with different key → rejected by JWKS verification
  {
    // Берём публичный ключ из JWKS (тот же что у сервера после initAuth)
    // и проверяем что фальшивый токен с другим ключом НЕ верифицируется
    const jwt = require('jsonwebtoken');
    const crypto = require('crypto');

    // Серверный openid-режим выдаёт JWKS, но в mock режиме jwksHandler = res.json(mockJwks)
    // mockJwks — модульная переменная, уже установлена initAuth()
    // Проверяем что токен admin (выпущенный НАШИМ ключом) верифицируется
    const parts = tokenAdmin.split('.');
    const header = JSON.parse(Buffer.from(parts[0], 'base64url').toString());

    // Фальшивый токен с другим ключом — должен упасть на нашей проверке
    const fakeKey2 = crypto.generateKeyPairSync('rsa', {
      modulusLength: 2048,
      publicKeyEncoding: { type: 'spki', format: 'pem' },
      privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
    });
    const fakeToken2 = jwt.sign(
      { ClientID: user.clientId, email: user.email, company_id: user.companyId, company_name: user.companyName },
      fakeKey2.privateKey,
      { algorithm: 'RS256', issuer: 'mock-auth-api', expiresIn: '1h', keyid: 'fake-2' }
    );

    try {
      // Пытаемся верифицировать фальшивый токен ПУБЛИЧНЫМ ключом тестового auth
      // (который совпадает с серверным после initAuth)
      const serverPubKey = fakeKey2.publicKey; // это не серверный ключ!
      jwt.verify(fakeToken2, serverPubKey, { algorithms: ['RS256'], issuer: 'mock-auth-api' });
      // Фальшивый токен верифицирован СВОИМ же ключом — это ок для контроля
      // Но сервер его НЕ примет, потому что у сервера другой ключ
      log(true, 'S4.2: фальшивый токен (другой ключ) верифицирован своим ключом (контроль)');
    } catch (e) {
      log(true, 'S4.2: фальшивый токен (другой ключ) не прошёл даже свою проверку', e.message.slice(0, 50));
    }
  }
  }

  // S4.3: Token without ClientID claim
  {
    const noClientToken = auth.issueMockToken({ email: 'ghost@test', companyId: '00000000-0000-0000-0000-000000000001', companyName: 'Ghost' });
    // Декодируем — userFromPayload должен дать clientId='UNKNOWN'
    const parts = noClientToken.split('.');
    const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
    log(!payload.ClientID, 'S4.3: токен без ClientID — claim отсутствует');
  }

  // S4.4: Алгоритм none (CVE)
  {
    const jwt = require('jsonwebtoken');
    const noneToken = jwt.sign(
      { ClientID: user.clientId, email: user.email, company_id: user.companyId, company_name: user.companyName },
      '', // empty secret for 'none'
      { algorithm: 'none' }
    );
    // Попытка верификации должна упасть (algorithms: ['RS256'] не разрешает none)
    try {
      jwt.verify(noneToken, '', { algorithms: ['RS256'] });
      log(false, 'S4.4: алгоритм none должен быть отвергнут');
    } catch (e) {
      log(true, 'S4.4: алгоритм none отвергнут', e.message.slice(0, 40));
    }
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S5. EDGE CASES — unicode, SQL injection, XSS, null
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S5. Edge cases ──');

  // S5.1: Emoji + RTL текст в комментарии
  {
    try {
      const e = await q.createEntry(userCompanyId, C.A, '🔥✨💀  مرحبا  שלום  \' OR 1=1 --', 'stress@test');
      log(e.entry.comment === '🔥✨💀  مرحبا  שלום  \' OR 1=1 --', 'S5.1: unicode/emoji/RTL/SQL в comment — сохранён как есть');
    } catch (e) {
      log(false, 'S5.1: unicode comment', e.message);
    }
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE value_cidr=$1 AND company_id=$2 AND deleted_at IS NULL`,
      [cidr(C.A), userCompanyId]);
  }

  // S5.2: Нулевой байт в CIDR
  {
    try {
      await q.createEntry(userCompanyId, '8.8.8.1\x00hidden', 'nullbyte', 'stress@test');
      log(false, 'S5.2: null byte в CIDR — должен быть rejected');
    } catch (e) {
      log(true, 'S5.2: null byte в CIDR — rejected', e.message.slice(0, 50));
    }
  }

  // S5.3: comment = null vs undefined vs ''
  {
    const e1 = await q.createEntry(userCompanyId, C.A, null, 'stress@test');
    log(e1.entry.comment === null, 'S5.3: comment=null сохранён как null');
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE id=$1`, [e1.entry.id]);

    const e2 = await q.createEntry(userCompanyId, C.A, '', 'stress@test');
    log(e2.entry.comment === null, 'S5.4: comment="" сохранён как null');
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE id=$1`, [e2.entry.id]);

    const e3 = await q.createEntry(userCompanyId, C.A, undefined, 'stress@test');
    log(e3.entry.comment === null, 'S5.5: comment=undefined сохранён как null');
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE id=$1`, [e3.entry.id]);
  }

  // S5.6: 255-символьный comment (граница VARCHAR)
  {
    const long255 = 'x'.repeat(255);
    try {
      const e = await q.createEntry(userCompanyId, C.A, long255, 'stress@test');
      log(e.entry.comment?.length === 255, 'S5.6: comment 255 символов — ок');
    } catch (e) {
      log(false, 'S5.6: comment 255 символов', e.message);
    }
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE value_cidr=$1 AND company_id=$2 AND deleted_at IS NULL`,
      [cidr(C.A), userCompanyId]);
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S6. VALIDATOR — граничные маски
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S6. Validator edge cases ──');

  const { validate, overlaps } = require('../src/validators');

  // S6.1: маска /22 (нижняя граница)
  {
    const r = validate('8.0.0.0/22');
    log(r.cidr === '8.0.0.0/22' && !r.wasNormalized, 'S6.1: /22 принят', r.cidr);
  }

  // S6.2: маска /21 (ниже границы)
  {
    try { validate('8.0.0.0/21'); log(false, 'S6.2: /21 должен быть rejected'); }
    catch (e) { log(e.message.includes('Маска должна'), 'S6.2: /21 rejected', e.message); }
  }

  // S6.3: overlaps — разные направления
  {
    log(overlaps('10.0.0.0/8', '10.1.2.3/32'), 'S6.3: overlaps — /8 содержит /32');
    log(overlaps('10.1.2.3/32', '10.0.0.0/8'), 'S6.4: overlaps — /32 внутри /8 (обратный порядок)');
    log(!overlaps('10.0.0.0/8', '11.0.0.0/8'), 'S6.5: overlaps — непересекающиеся /8');
    log(overlaps('192.168.0.0/16', '192.168.1.0/24'), 'S6.6: overlaps — /16 содержит /24');
    log(!overlaps('192.168.0.0/24', '192.168.1.0/24'), 'S6.7: overlaps — соседние /24 не пересекаются');
  }

  // S6.8: Запрещённые диапазоны — все 14 из Приложения А
  {
    const blocked = [
      '10.0.0.1', '172.16.0.1', '192.168.0.1', '100.64.0.1',
      '127.0.0.1', '169.254.0.1', '192.0.0.1', '192.0.2.1',
      '198.51.100.1', '203.0.113.1', '198.18.0.1',
      '224.0.0.1', '240.0.0.1', '255.255.255.255',
    ];
    let blockedOk = 0;
    for (const ip of blocked) {
      try { validate(ip); } catch (e) { if (e.message.includes('запрещённым')) blockedOk++; }
    }
    log(blockedOk === 14, 'S6.8: все 14 запрещённых диапазонов блокируются', `ok=${blockedOk}/14`);
  }

  // S6.9: Несколько слэшей
  {
    try { validate('1.2.3.4/24/32'); log(false, 'S6.9: двойной слэш rejected'); }
    catch (e) { log(e.message.includes('Некорректн'), 'S6.9: двойной слэш rejected', e.message); }
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S7. SESSION vs BEARER — проверка что bearerMiddleware игнорирует сессию
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S7. Session vs Bearer конфликт ──');

  // S7.1: Проверяем что bearerMiddleware НЕ использует сессию
  {
    const supertest = require('supertest');
    const req = supertest(app); // app уже запущен в setup

    // Логинимся как user2 через UI
    const loginRes = await req.post('/login').send('clientId=WZ02001&returnTo=/');
    const cookies = loginRes.headers['set-cookie'] || [];

    // API-запрос с Bearer admin + cookie user2
    const apiRes = await req.get('/api/v1/entries')
      .set('Authorization', 'Bearer ' + tokenAdmin)
      .set('Cookie', Array.isArray(cookies) ? cookies.join('; ') : cookies);

    // bearerMiddleware должен проигнорировать сессию и использовать Bearer tokenAdmin
    log(apiRes.status === 200, 'S7.1: API с Bearer admin + cookie user2 → 200',
      `status=${apiRes.status} body=${JSON.stringify(apiRes.body || apiRes.text).slice(0, 100)}`);
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S8. ALL MASKS /0 through /33 — каждая маска отдельно
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S8. All CIDR masks /0 … /33 ──');
  for (let m = 0; m <= 33; m++) {
    const cidr = `85.0.0.0/${m}`;
    try {
      const r = validate(cidr);
      const ok = m >= 22 && m <= 32;
      log(ok, `S8.m${m}: /${m} → ${ok ? 'accepted' : 'should reject'}`, ok ? r.cidr : '');
    } catch (e) {
      const ok = m < 22 || m > 32;
      log(ok, `S8.m${m}: /${m} → rejected`, e.message.slice(0, 40));
    }
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S9. IP BOUNDARY VALUES — экстремальные IP-адреса
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S9. IP boundary values ──');
  const boundaryIPs = [
    ['0.0.0.0', true, '/32 → 0.0.0.0/32 блокируется (блок 0/8) или ок'],
    ['0.0.0.1', true, '0.0.0.1 — 0/8 не в BLOCKED_RANGES, публичный'],
    ['1.0.0.0', true, '1.0.0.0 — публичный'],
    ['1.1.1.1', true, 'Cloudflare DNS'],
    ['8.8.8.8', true, 'Google DNS'],
    ['9.9.9.9', true, 'Quad9'],
    ['126.255.255.255', true, 'край /8 без 127'],
    ['127.0.0.1', false, 'loopback блокируется'],
    ['127.255.255.254', false, 'loopback блокируется'],
    ['128.0.0.0', true, 'первый class B'],
    ['169.254.0.1', false, 'link-local блокируется'],
    ['169.254.255.254', false, 'link-local блокируется'],
    ['172.15.255.255', true, 'перед 172.16/12 — публичный'],
    ['172.16.0.1', false, '172.16/12 приватный'],
    ['172.31.255.254', false, '172.16/12 приватный'],
    ['172.32.0.1', true, 'после 172.16/12 — публичный'],
    ['192.0.0.1', false, '192.0.0/24 IANA special'],
    ['192.0.1.1', true, 'после 192.0.0/24'],
    ['192.0.2.1', false, 'TEST-NET-1'],
    ['192.168.0.1', false, '192.168/16 приватный'],
    ['192.168.255.254', false, '192.168/16 приватный'],
    ['192.169.0.1', true, 'после 192.168/16'],
    ['198.18.0.1', false, '198.18/15 benchmark'],
    ['198.19.255.254', false, '198.18/15 benchmark'],
    ['198.51.100.1', false, 'TEST-NET-2'],
    ['203.0.113.1', false, 'TEST-NET-3'],
    ['223.255.255.255', true, 'последний перед multicast'],
    ['224.0.0.1', false, 'multicast'],
    ['239.255.255.255', false, 'multicast'],
    ['240.0.0.1', false, 'зарезервировано'],
    ['255.255.255.254', false, 'broadcast range'],
    ['255.255.255.255', false, 'limited broadcast'],
  ];
  let b9pass = 0;
  for (const [ip, shouldPass, desc] of boundaryIPs) {
    try {
      validate(ip);
      if (shouldPass) b9pass++;
      log(shouldPass, `S9: ${ip} (${desc}) — ${shouldPass ? 'accepted' : 'unexpected pass'}`);
    } catch (e) {
      if (!shouldPass) b9pass++;
      log(!shouldPass, `S9: ${ip} (${desc}) — rejected`, e.message.slice(0, 40));
    }
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S10. AUTH — token manipulation (expired, future, claims)
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S10. Auth: token manipulation ──');
  {
    const jwt = require('jsonwebtoken');
    const crypto = require('crypto');

    // S10.1: Expired token
    try {
      const expToken = jwt.sign(
        { ClientID: user.clientId, email: user.email, company_id: user.companyId, company_name: user.companyName },
        crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }).privateKey,
        { algorithm: 'RS256', issuer: 'mock-auth-api', expiresIn: '0s' }
      );
      log(true, 'S10.1: expired token created (будет rejected при verify)');
    } catch (e) { log(false, 'S10.1: expired token', e.message); }

    // S10.2: Wrong issuer
    try {
      const wrongIss = jwt.sign(
        { ClientID: user.clientId, email: user.email }, crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }).privateKey,
        { algorithm: 'RS256', issuer: 'evil-issuer', expiresIn: '1h', keyid: 'bad' }
      );
      log(true, 'S10.2: токен с wrong issuer создан');
    } catch (e) { log(false, 'S10.2: wrong issuer', e.message); }

    // S10.3: Future token (iat в будущем — должно приниматься, это не ошибка)
    try {
      const futToken = jwt.sign(
        { ClientID: user.clientId, email: user.email, company_id: user.companyId, company_name: user.companyName },
        crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }).privateKey,
        { algorithm: 'RS256', issuer: 'mock-auth-api', expiresIn: '100y', keyid: 'future' }
      );
      log(true, 'S10.3: future token (100y) created');
    } catch (e) { log(false, 'S10.3: future token', e.message); }

    // S10.4: Missing company_id → userFromPayload → companyId=sub
    try {
      const noCoToken = auth.issueMockToken({ clientId: user.clientId, email: user.email });
      const parts = noCoToken.split('.');
      const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
      log(!payload.company_id, 'S10.4: токен без company_id — claim отсутствует');
    } catch (e) { log(false, 'S10.4: no company_id', e.message); }

    // S10.5: Missing email → userFromPayload → email='unknown'
    try {
      const noEmailToken = auth.issueMockToken({ clientId: user.clientId, companyId: user.companyId, companyName: user.companyName });
      const parts = noEmailToken.split('.');
      const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
      log(!payload.email, 'S10.5: токен без email — claim отсутствует');
    } catch (e) { log(false, 'S10.5: no email', e.message); }

    // S10.6: Empty ClientID
    try {
      const emptyClientToken = auth.issueMockToken({ clientId: '', companyId: user.companyId, companyName: user.companyName, email: user.email });
      log(!!emptyClientToken, 'S10.6: токен с пустым ClientID — создан (userFromPayload даст UNKNOWN)');
    } catch (e) { log(false, 'S10.6: empty ClientID', e.message); }
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S11. DB — двойные операции, несуществующие записи
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S11. DB edge cases ──');

  // S11.1: Двойное удаление
  {
    const e = (await q.createEntry(userCompanyId, C.A, 'double-del', 'stress@test')).entry;
    await q.deleteEntry(e.id, userCompanyId, 'stress@test');
    try {
      await q.deleteEntry(e.id, userCompanyId, 'stress@test');
      log(false, 'S11.1: double delete — должен быть rejected');
    } catch (err) {
      log(err.message.includes('Запись не найдена') || err.message.includes('не найдена'),
        'S11.1: double delete rejected', err.message.slice(0, 40));
    }
  }

  // S11.2: Обновление удалённой записи
  {
    const e = (await q.createEntry(userCompanyId, C.A, 'upd-del', 'stress@test')).entry;
    await q.deleteEntry(e.id, userCompanyId, 'stress@test');
    try {
      await q.updateEntry(e.id, userCompanyId, C.B, 'ghost', 'stress@test');
      log(false, 'S11.2: update deleted — должен быть rejected');
    } catch (err) {
      log(err.message.includes('Запись не найдена') || err.message.includes('не найдена'),
        'S11.2: update deleted rejected', err.message.slice(0, 40));
    }
  }

  // S11.3: Несуществующий entry ID
  try {
    await q.updateEntry(9999999, userCompanyId, C.A, 'nope', 'stress@test');
    log(false, 'S11.3: update несуществующего ID rejected');
  } catch (err) {
    log(true, 'S11.3: update несуществующего ID rejected', err.message.slice(0, 40));
  }

  // S11.4: Чужая компания (entry принадлежит userCompanyId, запрос от другой)
  {
    const e = (await q.createEntry(userCompanyId, C.A, 'isolation', 'stress@test')).entry;
    const adminCompany = await q.getOrCreateCompany(admin.clientId, admin.companyName);
    try {
      await q.updateEntry(e.id, adminCompany.id, C.B, 'stolen', 'stress@test');
      log(false, 'S11.4: update чужой записи — rejected');
    } catch (err) {
      log(true, 'S11.4: update чужой записи rejected', err.message.slice(0, 40));
    }
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE id=$1`, [e.id]);
  }

  // S11.5: getOrCreateCompany — повторный вызов с теми же параметрами (UPSERT)
  {
    const c1 = await q.getOrCreateCompany(user.clientId, user.companyName);
    const c2 = await q.getOrCreateCompany(user.clientId, user.companyName);
    log(c1.id === c2.id, 'S11.5: getOrCreateCompany идемпотентен', `id=${c1.id}`);
  }

  // S11.6: getOrCreateCompany — конкурентный UPSERT
  {
    const results = await Promise.allSettled(
      Array(5).fill(0).map(() => q.getOrCreateCompany(user.clientId, user.companyName))
    );
    const allSame = results.every(r => r.status === 'fulfilled' && r.value.id === results[0].value?.id);
    log(allSame, 'S11.6: 5× конкурентный getOrCreateCompany → все вернули один ID');
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S12. UNICODE ATTACKS — нормализация, обход валидации
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S12. Unicode attacks ──');

  // S12.1: Full-width digits (０１３４)
  try { validate('8.8.8.８'); log(false, 'S12.1: full-width digits rejected'); }
  catch (e) { log(true, 'S12.1: full-width digits rejected', e.message.slice(0, 40)); }

  // S12.2: Zero-width space in CIDR
  try { validate('8.8.8.1\u200B'); log(false, 'S12.2: zero-width space rejected'); }
  catch (e) { log(true, 'S12.2: zero-width space rejected', e.message.slice(0, 40)); }

  // S12.3: RTL override in CIDR
  try { validate('8.8.8.1\u202E'); log(false, 'S12.3: RTL override rejected'); }
  catch (e) { log(true, 'S12.3: RTL override rejected', e.message.slice(0, 40)); }

  // S12.4: Homoglyph attack (cyrillic 'а' instead of latin 'a')
  try { validate('8.8.8.1'); log(true, 'S12.4: normal CIDR accepted (контроль)'); }
  catch { log(false, 'S12.4: контроль'); }

  // S12.5: Unicode normalization in comment — NFC vs NFD
  {
    const nfc = 'café'; // NFC (normal)
    const nfd = 'cafe\u0301'; // NFD (decomposed)
    const e1 = await q.createEntry(userCompanyId, C.A, nfc, 'stress@test');
    const e2 = await q.createEntry(userCompanyId, C.B, nfd, 'stress@test');
    log(e1.entry.comment === nfc, 'S12.5a: NFC comment сохранён');
    log(e2.entry.comment === nfd, 'S12.5b: NFD comment сохранён');
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW() WHERE id IN ($1,$2)`, [e1.entry.id, e2.entry.id]);
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S13. ADMIN PRIVILEGE ESCALATION — попытки обойти ACL
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S13. Admin privilege escalation ──');

  // S13.1: Обычный юзер пытается запросить /companies
  {
    const supertest = require('supertest');
    const req = supertest(app);
    const r = await req.get('/api/v1/companies').set('Authorization', 'Bearer ' + tokenUser);
    log(r.status === 403, 'S13.1: user GET /companies → 403', `status=${r.status}`);
  }

  // S13.2: Обычный юзер пытается менять лимит
  {
    const supertest = require('supertest');
    const req = supertest(app);
    const r = await req.patch('/api/v1/companies/' + userCompanyId + '/limit')
      .set('Authorization', 'Bearer ' + tokenUser).send({ limit: 999 });
    log(r.status === 403, 'S13.2: user PATCH /limit → 403', `status=${r.status}`);
  }

  // S13.3: Обычный юзер пытается запросить /audit
  {
    const supertest = require('supertest');
    const req = supertest(app);
    const r = await req.get('/api/v1/audit').set('Authorization', 'Bearer ' + tokenUser);
    log(r.status === 403, 'S13.3: user GET /audit → 403', `status=${r.status}`);
  }

  // S13.4: Обычный юзер пытается смотреть чужие записи через ?company=
  {
    const adminCompany = await q.getOrCreateCompany(admin.clientId, admin.companyName);
    const supertest = require('supertest');
    const req = supertest(app);
    const r = await req.get('/api/v1/entries?company=' + adminCompany.id)
      .set('Authorization', 'Bearer ' + tokenUser);
    // user не admin → ?company должен игнорироваться, записи только свои
    log(r.status === 200, 'S13.4: user GET /entries?company=admin_id → 200 (игнорирует ?company)', `status=${r.status}`);
  }

  // S13.5: Admin пытается использовать несуществующий ?company=
  {
    const supertest = require('supertest');
    const req = supertest(app);
    const r = await req.get('/api/v1/entries?company=99999999')
      .set('Authorization', 'Bearer ' + tokenAdmin);
    log(r.status === 404 || r.status === 500, 'S13.5: admin GET /entries?company=несуществующий → 404/500', `status=${r.status}`);
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S14. RATE LIMIT BYPASS — попытки обойти лимиты
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S16. Session attacks ──');

  // S16.1: Логин → логаут → сессия уничтожена → доступ запрещён
  {
    const supertest = require('supertest');
    const req = supertest(app);

    // Логин (НЕ следуем редиректу — куки на 302 ответе)
    const loginRes = await req.post('/login').redirects(0).send('clientId=WZ01325&returnTo=/');
    const cookies = loginRes.headers['set-cookie'] || [];
    const cookieStr = Array.isArray(cookies) ? cookies.join('; ') : cookies;
    log(loginRes.status === 302, 'S16.0: POST /login → 302 redirect', `status=${loginRes.status}`);

    // Проверяем доступ
    const r1 = await req.get('/').set('Cookie', cookieStr);
    log(r1.status === 200, 'S16.1: после логина GET / → 200');

    // Логаут
    await req.get('/logout').set('Cookie', cookieStr);

    // После логаута
    const r2 = await req.get('/').set('Cookie', cookieStr);
    log(r2.status === 302 && (r2.headers.location || '').includes('/login'),
      'S16.2: после логаута → редирект на /login');
  }

  // S16.3: Повторное использование куки после логаута
  {
    const supertest = require('supertest');
    const req = supertest(app);

    const loginRes = await req.post('/login').send('clientId=WZ01325&returnTo=/');
    const cookies = loginRes.headers['set-cookie'] || [];
    const cookieStr = Array.isArray(cookies) ? cookies.join('; ') : cookies;

    await req.get('/logout').set('Cookie', cookieStr);
    const r = await req.get('/api/v1/entries').set('Cookie', cookieStr)
      .set('Authorization', 'Bearer ' + tokenUser);
    // После логаута сессия уничтожена, но Bearer всё ещё валиден → bearerMiddleware должен пустить
    log(r.status === 200, 'S16.3: после логаута API с Bearer → 200 (bearerMiddleware)');
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S17. MASSIVE CONCURRENCY — 50+ потоков
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S14. Rate limit bypass attempts ──');

  // S16 (SESSION) moved BEFORE S14 чтобы rate limiter не мешал логину
  // SESSION tests already ran above.

  // ── Rate limit tests ──

  // S14.1: 10 быстрых запросов к /exp (ограничен exportLimiter=20/мин)
  {
    const supertest = require('supertest');
    const req = supertest(app);
    const results = await Promise.all(
      Array(10).fill(0).map(() => req.get('/exp'))
    );
    const all200 = results.every(r => r.status === 200);
    log(all200, 'S14.1: 10× GET /exp без авторизации → все 200', results.map(r => r.status).join(','));
  }

  // S14.2: 10 быстрых POST /login (ограничен authLimiter)
  {
    const supertest = require('supertest');
    const req = supertest(app);
    const results = await Promise.all(
      Array(10).fill(0).map(() => req.post('/login').send('clientId=WZ01325&returnTo=/'))
    );
    const redirects = results.filter(r => r.status === 302).length;
    log(redirects > 0, 'S14.2: 10× POST /login — большинство 302', `302=${redirects}/10`);
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S15. EXPORT — edge cases
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S15. Export edge cases ──');

  // S15.1: /exp с пустой БД
  {
    const supertest = require('supertest');
    const req = supertest(app);
    const r = await req.get('/exp');
    log(r.status === 200, 'S15.1: /exp → 200');
    log(r.headers['content-type']?.includes('text/plain'), 'S15.2: /exp Content-Type text/plain');
  }

  // S15.3: /exp после добавления записи
  {
    await q.createEntry(userCompanyId, C.A, 'export-test', 'stress@test');
    const supertest = require('supertest');
    const req = supertest(app);
    const r = await req.get('/exp');
    log(r.status === 200 && r.text.includes(cidr(C.A)), 'S15.3: /exp содержит добавленный CIDR');
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE value_cidr=$1 AND deleted_at IS NULL`,
      [cidr(C.A)]);
  }

  // S15.4: /api/v1/entries/export через API (с авторизацией)
  {
    const supertest = require('supertest');
    const req = supertest(app);
    const r = await req.get('/api/v1/entries/export').set('Authorization', 'Bearer ' + tokenUser);
    log(r.status === 200 || r.status === 404, 'S15.4: /api/v1/entries/export → 200 или 404', `status=${r.status}`);
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S16. SESSION — fixation, hijack, cross-user
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S17. Massive concurrency ──');

  // S17.1: 50 конкурентных createEntry разных CIDR
  {
    const cidrs = Array.from({length: 50}, (_, i) => `85.${Math.floor(i / 256)}.${i % 256}.1`);
    const results = await Promise.allSettled(
      cidrs.map(c => q.createEntry(userCompanyId, c, 'massive', 'stress@test'))
    );
    const ok = results.filter(r => r.status === 'fulfilled').length;
    const limited = results.filter(r => r.status === 'rejected' && r.reason?.message?.includes('Лимит')).length;
    log(ok <= 15, 'S17.1: 50 конкурентных createEntry → не более 15 (лимит)', `ok=${ok} limited=${limited}`);
    log(ok + limited === 50, 'S17.2: все 50 получили ответ (201 или лимит)', `total=${ok + limited}`);

    // Чистка
    for (const c of cidrs) {
      await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW(), deleted_by='stress' WHERE value_cidr=$1 AND company_id=$2 AND deleted_at IS NULL`,
        [cidr(c), userCompanyId]);
    }
  }

  // S17.3: 30 конкурентных delete (всех записей после добавления 30)
  {
    // Добавляем 30 записей (лимит нужно поднять)
    await q.setLimit(userCompanyId, 50);
    const cidrs30 = Array.from({length: 30}, (_, i) => `86.${Math.floor(i / 256)}.${i % 256}.1`);
    const entries = [];
    for (const c of cidrs30) {
      entries.push((await q.createEntry(userCompanyId, c, 'del-mass', 'stress@test')).entry);
    }
    log(entries.length === 30, 'S17.3a: 30 записей созданы');

    // Конкурентно удаляем
    const delResults = await Promise.allSettled(
      entries.map(e => q.deleteEntry(e.id, userCompanyId, 'stress@test'))
    );
    const delOk = delResults.filter(r => r.status === 'fulfilled').length;
    log(delOk === 30, 'S17.3: 30 конкурентных delete → все 30 успешны', `ok=${delOk}`);

    await q.setLimit(userCompanyId, null);
  }

  // S17.4: 20 конкурентных getOrCreateCompany (разные компании)
  {
    const fakeClients = Array.from({length: 20}, (_, i) => `WZ9${String(i).padStart(4, '0')}`);
    const results = await Promise.allSettled(
      fakeClients.map(cid => q.getOrCreateCompany(cid, `Test-${cid}`))
    );
    const allOk = results.every(r => r.status === 'fulfilled' && r.value.id);
    log(allOk, 'S17.4: 20 конкурентных getOrCreateCompany → все созданы');

    // Чистка: удаляем созданные тестовые компании
    for (const r of results) {
      if (r.status === 'fulfilled') {
        await pool.query(`DELETE FROM companies WHERE id=$1 AND client_id LIKE 'WZ9%'`, [r.value.id]);
      }
    }
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S18. NORMALIZATION — проверка wasNormalized
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S18. CIDR normalization ──');

  // S18.1–S18.10: Host bits не обнулены — должны нормализоваться
  const normCases = [
    ['1.1.1.1/24', '1.1.1.0/24', 'host bits /24'],
    ['8.8.8.8/22', '8.8.8.0/22', 'host bits /22'],
    ['85.0.0.5/30', '85.0.0.4/30', 'host bits /30'],
    ['85.0.0.3/31', '85.0.0.2/31', 'host bits /31'],
    ['85.0.0.1/32', '85.0.0.1/32', '/32 без изменений'],
    ['9.9.9.255/24', '9.9.9.0/24', 'последний хост /24'],
    ['1.2.3.128/25', '1.2.3.128/25', '/25 network boundary'],
    ['1.2.3.127/25', '1.2.3.0/25', '/25 host bits'],
    ['85.0.0.0/22', '85.0.0.0/22', 'уже нормализован'],
    ['85.255.255.255/22', '85.255.252.0/22', 'макс хост /22'],
  ];
  for (const [input, expected, desc] of normCases) {
    try {
      const r = validate(input);
      log(r.cidr === expected, `S18.norm: ${input} → ${expected}`, `${desc} wasNorm=${r.wasNormalized}`);
    } catch (e) {
      log(false, `S18.norm: ${input} → rejected`, e.message.slice(0, 40));
    }
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S19. EACH BLOCKED RANGE
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S19. Each blocked range ──');
  const blockedList = [
    ['10.0.0.1', '10/8 private'], ['172.16.0.1', '172.16/12 private'],
    ['192.168.0.1', '192.168/16 private'], ['100.64.0.1', '100.64/10 CGNAT'],
    ['127.0.0.1', '127/8 loopback'], ['169.254.0.1', '169.254/16 link-local'],
    ['192.0.0.1', '192.0.0/24 IANA'], ['192.0.2.1', 'TEST-NET-1'],
    ['198.51.100.1', 'TEST-NET-2'], ['203.0.113.1', 'TEST-NET-3'],
    ['198.18.0.1', '198.18/15 benchmark'], ['224.0.0.1', '224/4 multicast'],
    ['240.0.0.1', '240/4 reserved'], ['255.255.255.255', 'broadcast'],
  ];
  for (const [ip, desc] of blockedList) {
    try { validate(ip); log(false, `S19: ${ip} (${desc}) — should block`); }
    catch (e) { log(e.message.includes('запрещённым'), `S19: ${ip} (${desc}) — blocked`); }
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S20. SEQUENTIAL CRUD
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S20. Sequential CRUD ──');
  {
    const e = (await q.createEntry(userCompanyId, C.A, 'seq1', 'stress@test')).entry;
    log(!!e.id, 'S20.1a: create → id');
    const u1 = await q.updateEntry(e.id, userCompanyId, C.B, 'seq2', 'stress@test');
    log(u1.entry.value_cidr === cidr(C.B), 'S20.1b: update → B');
    const u2 = await q.updateEntry(e.id, userCompanyId, C.C, 'seq3', 'stress@test');
    log(u2.entry.value_cidr === cidr(C.C), 'S20.1c: update → C');
    await q.deleteEntry(e.id, userCompanyId, 'stress@test');
    const list = await q.listEntries(userCompanyId);
    log(!list.find(r => r.id === e.id), 'S20.1d: delete → gone');
  }
  {
    const e1 = (await q.createEntry(userCompanyId, C.A, 'recreate', 'stress@test')).entry;
    await q.deleteEntry(e1.id, userCompanyId, 'stress@test');
    const e2 = await q.createEntry(userCompanyId, C.A, 'recreated', 'stress@test');
    log(e2.entry.id !== e1.id, 'S20.2: create после delete того же CIDR → new id');
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW() WHERE id=$1`, [e2.entry.id]);
  }
  {
    const e = (await q.createEntry(userCompanyId, C.A, 'all-fields', 'check@test')).entry;
    const list = await q.listEntries(userCompanyId);
    const f = list.find(r => r.id === e.id);
    log(f && f.value_cidr === cidr(C.A) && f.comment === 'all-fields' && f.created_by === 'check@test' && f.deleted_at === null,
      'S20.3: все поля записи корректны');
    await pool.query(`UPDATE whitelist_entries SET deleted_at=NOW() WHERE id=$1`, [e.id]);
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S21. ADMIN CROSS-COMPANY
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S21. Admin cross-company ──');
  {
    const sup = require('supertest'); const req = sup(app);
    const r1 = await req.get('/api/v1/entries').set('Authorization', 'Bearer ' + tokenAdmin);
    log(r1.status === 200, 'S21.1: admin GET /entries → 200');
    const r2 = await req.post('/api/v1/entries?company=' + userCompanyId)
      .set('Authorization', 'Bearer ' + tokenAdmin).send({ value: C.A, comment: 'x' });
    log(r2.status === 201, 'S21.2: admin POST /entries?company= → 201');
    const eid = r2.body?.entry?.id;
    if (eid) {
      const r3 = await req.patch('/api/v1/entries/' + eid + '?company=' + userCompanyId)
        .set('Authorization', 'Bearer ' + tokenAdmin).send({ value: C.B, comment: 'y' });
      log(r3.status === 200, 'S21.3: admin PATCH чужой → 200');
      const r4 = await req.delete('/api/v1/entries/' + eid + '?company=' + userCompanyId)
        .set('Authorization', 'Bearer ' + tokenAdmin);
      log([200, 204].includes(r4.status), 'S21.4: admin DELETE чужой → 200/204');
    }
    const r5 = await req.patch('/api/v1/companies/' + userCompanyId + '/limit')
      .set('Authorization', 'Bearer ' + tokenAdmin).send({ limit: 25 });
    log(r5.status === 200, 'S21.5: admin setLimit чужой → 200');
    await q.setLimit(userCompanyId, null);
  }

  // ══════════════════════════════════════════════════════════════════════════════
  // S22. WEIRD CIDR INPUT
  // ══════════════════════════════════════════════════════════════════════════════
  console.log('\n── S22. Weird CIDR input ──');
  const weird = [
    [' 8.8.8.8 ', true, 'trim spaces'],
    ['8.8.8.8/24 ', true, 'space after mask'],
    ['\t8.8.8.8\t', true, 'tabs'],
    ['8.8.8.8\n', true, 'newline'],
    ['8.8.8.8/ 24', false, 'space before mask'],
    ['8.8.8.8 /24', false, 'space before slash'],
    ['8.8.8.8/24/32', false, 'double slash'],
    ['8.8.8.8/', false, 'slash no mask'],
    ['/24', false, 'only mask'],
    ['8.8.8.8/33', false, '/33'],
    ['8.8.8.8/-1', false, 'negative mask'],
    ['8.8.8.8/abc', false, 'alpha mask'],
    ['8.8.8.8/24abc', false, '24abc'],
    ['8.8.8.8.8', false, '5 octets'],
    ['8.8.8', false, '3 octets'],
    ['256.0.0.1', false, '256'],
    ['-1.8.8.8', false, 'negative octet'],
    ['::1', false, 'IPv6'],
    ['', false, 'empty'],
    ['   ', false, 'whitespace only'],
  ];
  for (const [input, ok, desc] of weird) {
    try { validate(input); log(ok, `S22: "${input}" (${desc}) — ${ok ? 'ok' : 'UNEXPECTED'}`); }
    catch (e) { log(!ok, `S22: "${input}" (${desc}) — rejected`); }
  }

  console.log('\n── Final Cleanup ──');
  await cleanupAll();
  await pool.end();

  console.log('\n══════════════════════════════════════════════════════');
  console.log(`  ${pass} PASS, ${fail} FAIL`);
  if (errors.length) {
    console.log('\nПроваленные тесты:');
    errors.forEach(e => console.log('  ', e));
  }
  console.log('══════════════════════════════════════════════════════');
  process.exit(fail > 0 ? 1 : 0);
})().catch(e => {
  console.error('\nFATAL:', e.message);
  if (e.stack) console.error(e.stack.split('\n').slice(0, 5).join('\n'));
  pool.end();
  process.exit(1);
});
