DFiP_Budget_planing/perf/analysis/svod2_search_oracle.test.mjs
2026-10-29 00:23:34 +04:00

149 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from 'node:assert/strict';
import http from 'node:http';
import test from 'node:test';
import {
SYNTHETIC_ROWS,
compareCase,
deriveCases,
runCheck,
emitNumberVectors,
emitScreenVectors,
equals,
matchesSearch,
screenFacets,
screenFound,
screenRows,
} from './svod2_search_oracle.mjs';
const mk = (id, type, dir, org) => ({ data: { line_id: id, header: { smeta_type: type, smeta_direction: dir, org_name: org } } });
const rows2 = [mk(1, 'КВ', 'Развитие', 'А'), mk(2, 'АХР', 'Поддержка', 'Б')];
const rows3 = [mk(1, 'КВ', 'Р', 'Филиал Ёлки'), mk(2, 'КВ', 'Р', 'Филиал Ёлки'), mk(3, 'КВ', 'Р', null)];
test('перекрытое поле contract_summary не ищется', () => {
const row = { data: { header: { name: 'x' }, contract_summary: { counterparty: 'ООО Ромашка' }, contract_detail: { counterparty: null } } };
assert.equal(matchesSearch(row, 'ромаш'), false);
});
test('число ищется в записи JS', () => {
const row = { data: { header: {}, contract_summary: {}, contract_detail: JSON.parse('{"ceiling_amount": 1234.50}') } };
assert.equal(matchesSearch(row, '1234.5'), true);
assert.equal(matchesSearch(row, '1234.50'), false);
});
test('screenRows: trim + lower, NBSP по краям', () => {
const rows = [{ data: { line_id: 1, header: { name: 'Аренда помещения' } } }];
assert.deepEqual(screenRows(rows, { search: ' АРЕНДА ' }).map((r) => r.data.line_id), [1]);
assert.deepEqual(screenRows(rows, { search: ' аренда ' }).map((r) => r.data.line_id), [1]);
});
test('equals: регистр и пробелы по краям, null не проходит', () => {
const r = (v) => ({ getValue: () => v });
assert.equal(equals(r('КВ'), 'smeta_type', 'кв '), true);
assert.equal(equals(r(null), 'smeta_type', 'кв'), false);
});
test('screenFacets: свой фильтр не применяется, чужие — да', () => {
const f = screenFacets(rows2, { filters: { smeta_type: 'КВ' } });
assert.deepEqual(f.smeta_type, [['АХР', 1], ['КВ', 1]]);
assert.deepEqual(f.smeta_direction, [['Развитие', 1]]);
});
test('screenFacets: одинаковые названия — один вариант, null не выдаётся', () => {
assert.deepEqual(screenFacets(rows3).org_name, [['Филиал Ёлки', 2]]);
});
test('screenFound считает только поиск', () => {
assert.deepEqual(screenFound(rows2, ''), { found: 2, total: 2 });
});
test('векторы чисел', () => {
const v = Object.fromEntries(emitNumberVectors().map(({ input, js }) => [input, js]));
assert.equal(v['1e21'], '1e+21');
assert.equal(v['-0'], '0');
assert.equal(v['1e-7'], '1e-7');
assert.equal(v['0.000001'], '0.000001');
assert.equal(v['100.00'], '100');
assert.equal(v['9007199254740993'], '9007199254740992');
});
test('векторы экрана: не меньше 12 случаев', () => {
const s = emitScreenVectors();
assert.ok(s.cases.length >= 12);
assert.ok(s.rows.length >= 10 && s.rows.length <= 15);
});
test('deriveCases: не меньше 8 случаев и есть дробное число', () => {
const cases = deriveCases(SYNTHETIC_ROWS);
assert.ok(cases.length >= 8);
for (const required of ['аренда', 'АРЕНДА', '1', ',']) {
assert.ok(cases.some((c) => c.search === required && Object.keys(c.filters).length === 0), required);
}
assert.ok(cases.some((c) => /\d\.\d|\de-\d/.test(c.search)));
});
const expectedFor = (rows, { search, filters }) => ({
line_ids: screenRows(rows, { search, filters }).map((r) => r.data.line_id),
total: screenRows(rows, { search, filters }).length,
found: screenFound(rows, search).found,
facets: screenFacets(rows, { search, filters }),
});
test('compareCase: совпадение даёт пустой массив, различия находятся', () => {
const base = expectedFor(SYNTHETIC_ROWS, { search: 'ё', filters: {} });
assert.deepEqual(compareCase(base, structuredClone(base)), []);
const swapped = structuredClone(base);
swapped.line_ids.reverse();
assert.ok(compareCase(base, swapped).length > 0);
const counted = structuredClone(base);
counted.facets.smeta_type[0][1] += 1;
assert.ok(compareCase(base, counted).length > 0);
});
const startStub = async ({ corrupt = false } = {}) => {
const server = http.createServer((req, res) => {
const url = new URL(req.url, 'http://x');
const send = (body, status = 200) => {
res.writeHead(status, { 'content-type': 'application/json' });
res.end(JSON.stringify(body));
};
if (url.pathname === '/api/v1/auth/login') return send({ access_token: 't' });
const m = url.pathname.match(/^\/api\/v1\/svod\/2\/(GO|RF)\/(\d+)(\/facets)?$/);
if (!m) return send({}, 404);
const q = url.searchParams;
const opts = {
search: q.get('search') ?? '',
filters: Object.fromEntries(['smeta_type', 'smeta_direction', 'org_name'].filter((k) => q.get(k)).map((k) => [k, q.get(k)])),
};
const hasNew = ['view', 'search', 'smeta_type', 'smeta_direction', 'org_name'].some((k) => q.has(k));
if (!hasNew) return send({ result: SYNTHETIC_ROWS.map((r) => ({ row_type: 'row', depth: 0, sort_order: null, data: r.data })) });
if (m[3]) {
const f = screenFacets(SYNTHETIC_ROWS, opts);
const out = { total: f.total, search_total: f.search_total };
for (const id of ['smeta_type', 'smeta_direction', 'org_name']) out[id] = f[id].map(([value, count]) => ({ value, count }));
return send({ result: out });
}
const all = screenRows(SYNTHETIC_ROWS, opts);
const offset = Number(q.get('offset') ?? 0);
const limit = Number(q.get('limit') ?? all.length);
let page = all.slice(offset, offset + limit);
if (corrupt && offset === 0) page = page.slice().reverse();
return send({ count: page.length, total: all.length, result: page.map((r) => ({ row_type: 'row', depth: 0, sort_order: null, data: r.data })) });
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
return { server, api: `http://127.0.0.1:${server.address().port}` };
};
test('runCheck на заглушке API: ok true', async () => {
const { server, api } = await startStub();
try {
const r = await runCheck({ api, username: 'u', password: 'p', year: 2026, pageSize: 3 });
assert.deepEqual(r.report, []);
assert.equal(r.ok, true);
} finally {
server.close();
}
});
test('runCheck: испорченная страница даёт ok false', async () => {
const { server, api } = await startStub({ corrupt: true });
try {
const r = await runCheck({ api, username: 'u', password: 'p', year: 2026, pageSize: 3 });
assert.equal(r.ok, false);
assert.ok(r.report.length > 0);
} finally {
server.close();
}
});