food-market/tests/e2e/scrape-moysklad.ts
nns deef65d819
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
test(money): unit (MoneyMath 25) + integration (7 на Postgres) + MoneyMath refactor
- MoneyMath: вынесены чистые формулы (статус оплаты, ожидаемая наличность,
  разница смены, остаток счёта, долг поставщика, защита от переплаты);
  сервис/SuppliesController/MoneyReportsController теперь зовут их.
- UnitTests/MoneyMathTests: 25 кейсов на формулы + money-флаги RolePermissions.
- IntegrationTests/MoneyModuleTests (реальный Postgres, 7 [Fact]):
  мультитенантность (Б не видит счета А), инвариант остатка, частичная→полная
  оплата приёмки, переплата→409, долг поставщика, продажа без смены→409 +
  после open→ok, расчёт expected/difference при закрытии смены.
- ApiActor: helpers CreateRetailPoint/CreateMoneyAccount/CreatePostedSupply.
- tests/e2e/scrape-moysklad.ts — скрейпер для docs/money-moysklad-reference.md.

Прогон: UnitTests 25/25 ✓, IntegrationTests money 7/7 ✓ (33s, Testcontainers).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-07 22:11:19 +05:00

75 lines
3.5 KiB
TypeScript
Raw Permalink 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.

// Scrape MoySklad REST API docs for sections relevant to the money module.
// Headless-Chromium нужен потому что doc SPA рендерит разделы JS-ом.
// Запуск: cd tests/e2e && npx tsx scrape-moysklad.ts
import { chromium } from "playwright";
import { writeFile, mkdir } from "node:fs/promises";
import { dirname } from "node:path";
const targets: { slug: string; label: string }[] = [
{ slug: "#/documents/payment-in", label: "Входящий платёж (безнал)" },
{ slug: "#/documents/payment-out", label: "Исходящий платёж (безнал)" },
{ slug: "#/documents/cashin", label: "Приходный ордер (наличные)" },
{ slug: "#/documents/cashout", label: "Расходный ордер (наличные)" },
{ slug: "#/documents/retaildrawercashin", label: "Внесение денег в кассу" },
{ slug: "#/documents/retaildrawercashout", label: "Выплата денег из кассы" },
{ slug: "#/documents/retailshift", label: "Розничная смена" },
{ slug: "#/documents/counterpartyadjustment", label: "Корректировка взаиморасчётов" },
{ slug: "#/reports/report-money", label: "Отчёт «Деньги»" },
{ slug: "#/reports/report-counterparty", label: "Показатели контрагентов" },
{ slug: "#/dictionaries/organization", label: "Юрлицо / счета юрлица" },
];
const BASE = "https://dev.moysklad.ru/doc/api/remap/1.2/";
const OUT = "/tmp/moysklad-dump";
async function dumpOne(page: import("playwright").Page, slug: string, label: string): Promise<string> {
const url = BASE + slug;
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45_000 });
// SPA рендер — ждём, пока в DOM появятся заголовки раздела.
await page.waitForTimeout(2500);
// Снимаем контент самой длинной «статьи» (у них разметка меняется от раздела).
const text = await page.evaluate(() => {
// Пытаемся найти самую большую семантическую зону — main/article или просто body.
const candidates = Array.from(document.querySelectorAll("main, article, section, .markdown-section, .content, body"));
let best: Element = document.body;
let bestLen = 0;
for (const el of candidates) {
const len = (el as HTMLElement).innerText?.length ?? 0;
if (len > bestLen) {
bestLen = len;
best = el;
}
}
return (best as HTMLElement).innerText;
});
return `=== ${label} (${slug}) ===\nURL: ${url}\n\n${text}\n\n`;
}
async function main() {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1400, height: 900 }, locale: "ru-RU" });
const page = await ctx.newPage();
await mkdir(OUT, { recursive: true });
for (const t of targets) {
const safe = t.slug.replace(/[^a-z0-9]+/gi, "_");
console.log("→", t.slug);
try {
const dump = await dumpOne(page, t.slug, t.label);
const path = `${OUT}/${safe}.txt`;
await mkdir(dirname(path), { recursive: true });
await writeFile(path, dump, "utf8");
console.log(" wrote", path, dump.length, "chars");
} catch (e: any) {
console.error(" FAILED", t.slug, e?.message);
}
}
await browser.close();
}
main().catch((e) => {
console.error(e);
process.exit(1);
});