- perf/run-all.sh: масштабы 1 и 2, шаги reset -> gen -> verify -> stand -> preflight -> scroll -> coauthor -> grow -> modals -> modals_profiling -> memory -> memtrend -> [control4x] -> bench -> aggregate; лог и run-all.status.json, возобновление (PERF_STEPS, PERF_SKIP_RESET), сухой режим с оценкой длительности, PERF_S2_REPEATS/PERF_S2_TARGETS, контроль CPU 4x (PERF_CONTROL_TARGETS). - Основная метрика: main_thread_ms и main_thread_ms_per_step в скролле (CDP TaskDuration), frame_excess_ms; в summary.md первыми строками. - SQL-статистика по шагам: PERF_SQLSTATS_TAG -> sql/<шаг>/ (sqlstats --out). - netlog: тела /auth/ не пишутся, поля password/token/secret вырезаются. - scroll.spec: уникальные имена целей (лист ССП с суффиксом /ssp, год в отчёте проекта) - иначе Playwright не запускал файл из-за дублей. - PERF_TRACE=0 для прогонов с одним повтором. - README: scroll-coauthor, run-all, возобновление, без CPU 4/6 в матрицах. - design.md: уточнена формулировка R-2, решения Task 10a. Refs #202 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
3.4 KiB
TypeScript
63 lines
3.4 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { attachNetlog, sanitizePost } from '../lib/netlog';
|
|
import { WEB } from '../lib/auth';
|
|
|
|
test('sanitizePost: /auth/ - тело не пишется', () => {
|
|
expect(sanitizePost('/api/v1/auth/login', JSON.stringify({ username: 'perf_rf', password: 'perf' }))).toBeNull();
|
|
expect(sanitizePost('/api/v1/auth/refresh', JSON.stringify({ refresh_token: 'abc' }))).toBeNull();
|
|
});
|
|
|
|
test('sanitizePost: поля password/token вырезаются из JSON на любой глубине', () => {
|
|
const out = sanitizePost('/api/v1/users/', JSON.stringify({
|
|
username: 'u', password: 'p', nested: { access_token: 't', Token: 'x', keep: 1 }, list: [{ secret: 's', v: 2 }],
|
|
}));
|
|
expect(out).not.toBeNull();
|
|
const v = JSON.parse(out!);
|
|
expect(v).toEqual({ username: 'u', nested: { keep: 1 }, list: [{ v: 2 }] });
|
|
expect(out).not.toMatch(/"p"|"t"|"x"|"s"/);
|
|
});
|
|
|
|
test('sanitizePost: form-urlencoded - поля password/token вырезаются', () => {
|
|
expect(sanitizePost('/api/v1/x', 'username=u&password=p&token=t&a=1')).toBe('username=u&a=1');
|
|
});
|
|
|
|
test('sanitizePost: тело без секретов не меняется, пустое - null', () => {
|
|
const body = JSON.stringify({ value: 1000, column: 'plan' });
|
|
expect(sanitizePost('/api/v1/form/7/sheet/AHR/cell', body)).toBe(body);
|
|
expect(sanitizePost('/api/v1/x', null)).toBeNull();
|
|
});
|
|
|
|
test('attachNetlog: логин не попадает в network.jsonl, остальные POST - без секретов', async ({ browser }) => {
|
|
const tmp = mkdtempSync(path.join(tmpdir(), 'perf-netlog-'));
|
|
process.env.PERF_RESULTS_DIR = tmp;
|
|
const ctx = await browser.newContext();
|
|
try {
|
|
// стенд не нужен: все запросы к WEB отвечает route
|
|
await ctx.route(WEB + '/**', (r) => r.fulfill({ status: 200, contentType: r.request().url().includes('/api/') ? 'application/json' : 'text/html', body: r.request().url().includes('/api/') ? '{}' : '<html></html>' }));
|
|
const nl = attachNetlog(ctx, { scenario: 'netlog_test', target: 't', role: 'rf' });
|
|
const page = await ctx.newPage();
|
|
await page.goto(WEB + '/');
|
|
await page.evaluate(async () => {
|
|
const post = (u: string, b: unknown) => fetch(u, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(b) });
|
|
await post('/api/v1/auth/login', { username: 'perf_rf', password: 'SECRET-PASS' });
|
|
await post('/api/v1/form/7/rows', { value: 1, token: 'SECRET-TOKEN' });
|
|
});
|
|
await page.waitForTimeout(300); // событие requestfinished приходит после завершения fetch на странице
|
|
await nl.flush();
|
|
const lines = readFileSync(path.join(tmp, 'network.jsonl'), 'utf-8').trim().split('\n').map((l) => JSON.parse(l));
|
|
const login = lines.find((l) => l.path === '/api/v1/auth/login');
|
|
const rows = lines.find((l) => l.path === '/api/v1/form/7/rows');
|
|
expect(login).toBeTruthy();
|
|
expect(login.post_data).toBeNull();
|
|
expect(JSON.parse(rows.post_data)).toEqual({ value: 1 });
|
|
expect(readFileSync(path.join(tmp, 'network.jsonl'), 'utf-8')).not.toMatch(/SECRET/);
|
|
} finally {
|
|
await ctx.close();
|
|
delete process.env.PERF_RESULTS_DIR;
|
|
rmSync(tmp, { recursive: true, force: true });
|
|
}
|
|
});
|