Files
MikrotikManager/backend/scripts/acceptance/rest-session-soak.mjs
T

70 lines
4.0 KiB
JavaScript

import fs from 'node:fs';
import assert from 'node:assert/strict';
import { chromium, expect } from '@playwright/test';
const output = 'tmp/rest262-soak.json';
assert(!fs.existsSync(output), 'Never overwrite acceptance evidence');
const credentials = JSON.parse(fs.readFileSync('tmp/portal195-private.json'));
const browser = await chromium.launch({ channel: 'msedge', headless: true });
let token;
try {
const page = await browser.newPage();
await page.goto('http://localhost:3000/certificates');
await page.getByLabel('Email', { exact: true }).fill(credentials.email);
await page.getByLabel('Пароль', { exact: true }).fill(credentials.password);
await page.getByRole('button', { name: 'Войти', exact: true }).click();
await expect(page.getByRole('navigation', { name: 'Путь страницы' })).toContainText('Сертификаты', { timeout: 30000 });
token = await page.evaluate(() => localStorage.getItem('mmapp_token'));
assert(token);
} finally { await browser.close(); }
const report = {
date: '2026-10-15', block: '12.5.262', realPortalLogin: true, mocks: false,
backend: 'ux-certificates258', endpoint: '/api/certificates/state', directBackendTunnel: true,
concurrentRequestsPerWave: 12, waveIntervalMs: 30000, plannedWindowMs: 600000,
nonAuthApiWrites: 0, nativeConfigurationWrites: 0, waves: [],
bounds: ['One backend, two CHR over HTTP REST; not a global cap for external clients or multiple backend instances', 'Concurrent normal collectors and UI acceptance may run; not maximum throughput', 'A finite successful window does not prove all historical 401/500 causes or indefinite stability'],
};
const start = Date.now();
const lastReceipts = new Map();
try {
for (let wave = 0; wave <= 20; wave++) {
const wait = start + wave * report.waveIntervalMs - Date.now();
if (wait > 0) await new Promise(resolve => setTimeout(resolve, wait));
const began = Date.now();
const samples = await Promise.all(Array.from({ length: 12 }, async () => {
const requested = Date.now();
try {
const response = await fetch('http://localhost:8000/api/certificates/state', {
headers: { Authorization: 'Bearer ' + token }, signal: AbortSignal.timeout(20000),
});
const body = await response.json();
return { status: response.status, durationMs: Date.now() - requested, servers: body.servers?.map(s => ({ serverId: s.serverId, collectedAt: s.collectedAt, error: s.error !== null })) ?? [] };
} catch (error) { return { status: null, durationMs: Date.now() - requested, failure: error.name, servers: [] }; }
}));
const item = { wave, startedAt: new Date(began).toISOString(), elapsedMs: Date.now() - start, durationMs: Date.now() - began, samples };
report.waves.push(item);
fs.writeFileSync(output, JSON.stringify(report, null, 2) + '\n');
assert(samples.every(s => s.status === 200 && s.servers.length === 2 && s.servers.every(row => !row.error) && s.servers.map(row => row.serverId).sort().join(',') === '1,2'), 'Request or device read failed; stopping without automatic replay');
for (const id of ['1', '2']) {
const receipts = samples.flatMap(s => s.servers.filter(row => row.serverId === id).map(row => Date.parse(row.collectedAt)));
assert(receipts.every(Number.isFinite));
const earliest = Math.min(...receipts);
if (lastReceipts.has(id)) assert(earliest > lastReceipts.get(id), 'A later wave returned an old receipt');
lastReceipts.set(id, Math.max(...receipts));
}
console.log(JSON.stringify({ wave, durationMs: item.durationMs, requests: samples.length, errors: 0 }));
}
report.completed = true;
report.durationMs = Date.now() - start;
assert(report.durationMs >= report.plannedWindowMs);
report.passed = true;
} catch (error) {
report.passed = false;
report.failure = { name: error.name, message: error.message.slice(0, 200) };
process.exitCode = 1;
} finally {
token = undefined;
fs.writeFileSync(output, JSON.stringify(report, null, 2) + '\n');
}