test: тестово покритие ≥95% за всички workspaces (надгражда #216) - #254
test: тестово покритие ≥95% за всички workspaces (надгражда #216)#254lyubomir-bozhinov wants to merge 39 commits into
Conversation
Vendored from midt-bg#216 (feat/coverage-ratchet) to put the coverage machinery in place ahead of midt-bg#217, while midt-bg#216 is pending merge upstream. Squashes ydimitrof's two harness commits into one; original authorship preserved via the commit author. @vitest/coverage-v8 through a shared vitest preset, per-workspace coverage configs with explicit include, committed coverage-baseline.json, the scripts/check-coverage.mjs ratchet gate (+ node:test self-test), and the CI wiring. Ref midt-bg#93, midt-bg#216.
regionByName had zero tests (both lines uncovered). Adds its full branch matrix (valid, whitespace-trim, null/undefined/empty/unknown, verbatim round-trip of all 28 regions) plus edge inputs for categoryForDivision and procedureGroup, and taxonomy-integrity invariants (unique CPV codes, single procedure-type ownership, classified = competitive∪non-competitive, 28 unique NUTS3 regions). config: 92.85/72.22 -> 100/100 lines/branches.
… periodRange) Adds eik/unp passthrough, one-sided and empty periodRange, date/monthYear/ longDate no-match + datetime-prefix + out-of-range-month fallbacks, count sign/absence, pct/signedPct dp + non-finite, entityName non-collapsing paths, cleanName unbalanced-quote drop, ЕТ/ET latin detection. shared branches 78.1 -> 98.3; residual is signedPct's provably-unreachable defensive return. Caught: count(-0.4) emits '−0' (missing money()'s rounded-zero sign guard); latent only (count takes non-negative integers), logged not patched.
New staging.test (0%->100%): scoped DELETE, chunked INSERT at CHUNK=100 boundaries (100/101/250 rows), null-fill of absent columns, table+column routing per target. New refresh.test: SQL splitter (escaped '' in literals, -- comments inside/outside literals, trailing statement), @refresh-batch grouping, transient-table drop order, D1 orchestration. base/ocds additions: toBool, Date.parse date fallback, annexes mapping, baseSqlLiteral numeric/ text/null branches, secured_inverse/variants coercions, party/lot/amendment and catch-up-window branches. ingest lines 83.8->100, branches 78.9->95.1.
…dology, flows identity slug fallbacks + undecodable name slug; keyset decodeCursor malformed/oversized/bad-type-guard paths; home zero single-offer aggregate; regions year + EU/national funding predicates; methodology absent-count coalesce with positive total; flows sankey sort tiebreak on shared authority.
streamAuthority/Company/Contract sitemaps via a paginating fake D1: XML escaping + C0 stripping, lastmod fallbacks (row date -> as_of -> none), natural-person filtering, empty-chunk skip loop, CHUNK-boundary pagination, contract page rowid windowing, and contractSitemapPages math. sitemaps branch 34.9 -> 95.3; residual is the defensive post-close pull guard.
getCompany + getAuthority were entirely untested. Adds DTO assembly, share math (won/spent denominators, zero-guards), avg-bids rounding, consortium membership (list->participants, prose->note), hasEik, sector top6+tail rollup, and getContract subcontractor (EUR/BGN/null/blank), framework call-off detection, eurFromNative currency paths (EUR/BGN peg/FX/no-rate), deltaPct suspect+zero-base guards, lot dedup/totals, and not-found. details branch 43.6 -> 88.5, lines 53 -> 100.
Adds the company-centre direction, null-param default (top authority) + its empty fallback, includeCenterOptions=false, loadCenter sample-name fallback (authority + company), hop-2 top-1-per-neighbour dedup, centre self-skip, and edgeless-node weighting. network branch 50 -> 88, lines 86.7 -> 100.
… guards search: empty/punctuation query -> empty shape, searchMoreHref unknown-kind fallback. trend: YoY guarded against a zero prior year, coverage pct when nothing is dated (no divide-by-zero).
Add coverage for the base-aggregation source (year/EU/single- vs multi-sector primary_sector), the entity WHERE type/text filters, sort normalization, facet label fallback and sort, page overflow, and the CSV stream body across the CHUNK boundary. Branch 55%->96%, lines 100%.
Add coverage for sort normalization, the base-aggregation source (year/EU/ single- vs multi-sector), the kind/text entity WHERE, facet kind mapping and sector sort, page overflow, missing total row, and the CSV body across the CHUNK boundary. Branch 61%->95%, lines 100%.
Add coverage for the authority-detail wrappers (getAuthoritySingleOffer, getAuthorityProcedureCompetition), getCompetitionSummary (both the qualifying and null-topConcentration paths), the MAX_TOP cap, EU/national funding scope, and a degenerate corpus exercising the zero-guard fallbacks. Branch 70%->95%.
Add coverage for buildFilters (every year/sector/procedure/value-bucket/EU/ bids/authority/bidder/text predicate), summary override, page overflow, contractsSummary null row, listSingleOfferContracts modes, facet procedure folding / sector sort / year ordering, and the streamed CSV body across the CHUNK boundary. Branch 66%->96%, lines 100%.
Add degenerate-input coverage for getCompany (absent metadata/bids/suspect rows, null primary sector and procedure value), getAuthority (spent-nothing authority with an unknown CPV division, zeroed tail share, null bids/suspect), and a getContract lot defaulting to the BGN peg. Branch 88%->98%, lines 100%.
Add coverage for an unresolvable company centre (null name → empty network), the company-kind fallback when neither rollup nor sample carries a kind, the empty-default path with includeCenterOptions off, and deduping a hop-1 neighbour that appears twice. Branch 88%->98%, lines 100%.
flows: EU/national/all funding scope + long-label truncation (branch 89->100). search: nullish raw-query coalescing (branch ->97). trend: EU/national funding, includeSectors=false, and an empty series with an absent coverage row (branch 89->100).
index.ts: full RefreshWorkflow.run coverage (staging lifecycle, capped and zero-ingest branches, derive-slice loop, finally-drop on success and error) and the scheduled cron entrypoint, via mocked platform/ingest/eop seams. eop.ts: bucket-key parse/classify, catch-up planning (uncapped/capped/default), bucket listing status + redirect guard, OCDS/base staging, and the window walk. Workspace 19%->98% branch, lines 100%.
Add coverage for singleSelectFilters (unknown sector/year flags, funding/top defaults), buildSectorGroup (category grouping, summed vs absent counts, uncategorised skip), sortHref, and the withParams/pageNav null-override, array, empty-result, and page-default branches. filters.ts branch 47%->97%.
agent.ts: SDK-wiring coverage (model/base-URL resolution, tool-set assembly, stream Response + onError) via mocked ai/@ai-sdk. report-schema.ts: flows block, facts sub-line, unknown-handle, empty title, 0-row and null chart edges (branch 78->94). tools.ts: run_sql AST-reject/error/meta-less paths, semantic hits, eop_fetch, source_link (branch 57->95). Also exclude test fixtures and type-only declarations from coverage (permanent 0% data files, not code).
render-format null-date; tool-results missing-cell + truncation flag; eop-fetch null-date/non-array/invalid-JSON/thrown-fetch; rag embed mismatch + metadata mapping + empty-vector guards; validateEmitShape callout/flows/timeseries and the object/question/items/columns negatives.
Add coverage for the v0 freshness fallback, non-string/null q classification, empty and empty-chunk multipart bodies, the abort-on-part-failure path, and suffix (bytes=-N) / open-ended (bytes=A-) R2 range shapes (the fake now emits R2-native range objects). csv-export lines 87->95, branch 76->90.
The v8 provider instruments every file matched by a workspace's include globs. Markdown docs colocated in src (e.g. the assistant README) carry no coverable statements, report a permanent 0%, and — being non-JS — make the reporter's remap step throw a parse error. Exclude **/*.md alongside the existing JSON and type-only exclusions so the ratchet total reflects executable code only.
Cover the remaining thin spots in the web workspace with real behavioural tests, no code changes: - cache.publicCache: default and explicit stale-while-revalidate windows - eopSource: missing/malformed dates, DD.MM.YYYY key shape, OCDS cutoff boundary - search.suggest: trimGroup cap + loader query/trim/headers, empty-q default - app.ts hardening: nonce re-read path, OPTIONS short-circuit, no-Content-Type - retry: non-Error rejection logging + default backoff past the table - security: nonce-less headers omit the CSP outside production - riskLogic: unknown bid count (null) and missing bidsRejected fallback - ScrollToTop: rAF coalescing of a scroll burst Web branch total 81.3% -> 95.47%, lines 89.3% -> 99.25%.
With the new tests in place, every workspace clears 95% on both lines and branches. Regenerate the ratchet baseline from current coverage so the gate now enforces the 95 floor going forward: etl 18.7/19.4 -> 100/98.5 web 89.3/81.3 -> 99.2/95.4 config 88.2/58.3 -> 100/100 db 82/65.5 -> 100/97.3 ingest 83.8/78.9 -> 100/95 shared 94.8/78.1 -> 98.8/98.3
buildSectorGroup always returns categories, but the group type marks it optional. vitest transpiles without type-checking so this passed locally; tsc --noEmit under noUncheckedIndexedAccess (CI typecheck) rejected the possibly-undefined access. Assert non-null at the three call sites.
Add branch-completion tests for releaseToContracts/Amendments/Lots on releases with absent optional fields: missing tag/contracts keys, id-less and identifier-less parties, a scheme-less CPV classification, an empty-string value amount, a blank date, and an ocid/tender-id-less lot. ocds.ts branch 92.17% -> 98.26%, ingest workspace 95.06% -> 98.7%. Residual uncovered branches are unreachable defensive code: the validDateOnly regex reject (day is always pre-normalized to YYYY-MM-DD) and the `rel.contracts ?? []` / `c.id ?? null` right-sides the length/id guards above them make impossible.
- keyset: unsafe-direction guard, before-cursor with an ascending sort, and the empty before-page (both cursors null) - flows: two-authority sankey so the authority-column sort comparator runs - regions: empty dataset → the total==0 coverage-pct guard (no divide-by-zero) - companies: base aggregation from a non-sector filter (no CPV predicate) - contracts: the „Неизвестна" year bucket sinking below real years regardless of input order - authorities/companies/contracts: backward pagination — page forward for a cursor, back for a before-cursor, then feed it back so keyset's reverse path runs db branch 97.35% -> 98.39%. Residual gaps are unreachable defensive code: CSV/sitemap `if(done)` re-entry (a stream never pulls after close), the minContracts `?? DEFAULT` the orchestrator already normalises, split().pop() `?? ` fallbacks (pop is always defined), the homoglyph map (every regex-matched char is mapped), and cross-namespace network self-edges.
- assertReadOnlySelect: empty/comment-only query, a forbidden keyword hidden in a single CTE-prefixed statement (cheap keyword layer, not just the AST guard), and the sqlite_master/sqlite_schema catalog-table rejection - run_sql: a driver returning no results array (the results ?? [] fallback) - eop-fetch: a non-Error thrown value → the generic fetch-error label web branch 95.47% -> 96.0%. Residual gaps are deep AST-shape defenses (sql-ast-guard), schema-validation guards (report-schema), and unreachable code: regex capture-group ?? fallbacks (csp, always matched), PROD-gated redirect/OPTIONS paths under vitest, and the module-init Date.now tag.
Coverage rose across web/db/ingest with the new branch tests; raise the ratchet floors to match (never down): web 99.2/95.4 -> 99.4/96 db 100/97.3 -> 100/98.3 ingest 100/95 -> 100/98.7 etl (100/98.5), config (100/100) and shared (98.8/98.3) unchanged. Monorepo total 99.73% lines / 97.51% branches.
ydimitrof
left a comment
There was a problem hiding this comment.
Обобщен преглед на PR — тест: тестово покритие ≥95% за всички workspaces (надгражда #216)
Вердикт: COMMENT — одобрение при зелен CI. Няма блокиращи проблеми по сигурност или коректност; само незадължителни бележки за хигиена и втвърдяване.
Какво прави PR-ът
Добавя обширно тестово покритие (цел ≥95%) за всички workspaces, надграждайки #216, заедно с инфраструктурата за неговото прилагане:
- Нови и разширени тестови файлове за
apps/etl,packages/db,packages/configи др. (заявки за договори, детайли, потоци, начална страница, идентичност, keyset пагинация, методология, мрежи, региони, търсене; sitemaps, trend, ocds, refresh, staging, format). - Конфигурация за покритие:
vitest.config.tsфайлове,vitest.shared,coverage-baseline.json, devDependency@vitest/coverage-v8. - Инструментариум за ratchet gate (#93):
scripts/check-coverage.mjs+ тестове,turbo.json. - CI workflow за коментар с покритие,
.gitignoreи документация (docs/review-testing.md).
Почти всички промени са тестове/конфигурация — няма промени в продукционен рънтайм код.
Сигурност (Фаза 0) — ЧИСТО във всичките 5 партиди
- Няма твърдо кодирани тайни; използваните ключове/ЕИК (
'k',111111111,123456789) са фиктивни фикстури. - Няма нови реални URL адреси или разширяване на allowlist; новите хостове са само в тестови стъбове. Тестът за redirect към
evil.exampleпотвърждава, че защитата срещу SSRF остава активна. - GitHub Actions: външните actions са закачени по commit SHA; тригерът е
pull_request(неpull_request_target);coverage-commentе ограничен до PR-и от същото repo, аpull-requests: writeе scoped само за този job. Съответства на OWASP CI/CD. check-coverage.mjsе с fail-closed дизайн, стриктна валидация срещу path traversal (WORKSPACE_KEYregex) и защита срещу prototype pollution (hasOwnProperty.call).- Няма злонамерен код, обфускация, бекдори или подозрителни зависимости.
@vitest/coverage-v8и транзитивните ѝ пакети са легитимни dev зависимости, само вdevDependencies. - Тестовете дори засилват сигурността, като проверяват параметризирано свързване (
?,WHERE source = ?), екраниране (О''Брайън,<&>→<&>) и отхвърляне на prototype ключове.
Качество на тестовете — високо
Тестовете са смислени и целят разкриване на дефекти, а не тривиални „cheater" тестове: покриват гранични случаи, null-клонове, HTTP 403/404/500, пагинация през CHUNK граница, fallback вериги за дати, безопасно деление, UTF-8 BOM, BG десетична запетая, taxonomy интегритет и адверсариални случаи (traversal/proto). Числените очаквания са проверени за вътрешна консистентност. Инструментариумът за покритие е с чисто разделение на pure функции и монотонен ratchet.
Незадължителни бележки (не блокиращи)
- Слабост в покритието (партида 3): фалшивите D1 обекти изхвърлят bind-аргументите, затова тестовете не проверяват реалните стойности на параметрите, техния ред или
LIMIT(напр. лимитът5вlistSingleOfferContractsне се верифицира). Препоръка: фалшивите обекти да записват bind-аргументите и поне един тест на заявка да ги утвърждава. - Крехкост/дублиране: много асерции са плътно свързани с точния SQL текст (рефактор би ги счупил); конструкторите на фалшиви D1 обекти се повтарят между файловете — да се извлече споделен помощен модул за фикстури.
- Несъответствие на версии:
@vitest/coverage-v8(4.1.10) изисква peervitest 4.1.10, а инсталираният е 4.1.7 — вероятен peer-warning; изравнете версиите за консистентен lock файл. - Дребни хигиенни:
ScrollToTop.test.tsxпрезаписваwindow.requestAnimationFrameбез възстановяване;coverage-commentобновява само първия коментар с маркера; правописна грешка „областти"; ред на зависимостите вpackage.json; неувитиJSON.parse/предположения за наличие наapps//packages/в скрипта.
Условие преди merge
Твърдението за ≥95% покритие и 100% преминаване на тестовете не е потвърдено емпирично в нито една партида (тестовете не можаха да се изпълнят локално). Финалното одобрение зависи от зелен CI с отчет за покритие.
| // Hold the rAF callback instead of running it synchronously so a second scroll lands while a | ||
| // frame is still pending — exercising the `if (ticking) return` early-out. | ||
| let pending: FrameRequestCallback | null = null; | ||
| window.requestAnimationFrame = (cb: FrameRequestCallback) => { |
There was a problem hiding this comment.
Тук window.requestAnimationFrame се презаписва глобално, но не се възстановява след теста (няма afterEach/съхранена оригинална референция). Стъбът само задържа callback-а и не го изпълнява, така че всеки следващ тест във файла, който разчита на rAF да задейства scroll-логиката, може да се повлияе в зависимост от реда на изпълнение. Препоръка: запазете оригинала и го върнете в afterEach (или използвайте vi.stubGlobal('requestAnimationFrame', ...) + vi.unstubAllGlobals()), за да гарантирате изолация между тестовете.
| }); | ||
|
|
||
| it('emits no competition flag when the bid count is unknown (bidsReceived null)', () => { | ||
| // admitted is null → neither competition branch fires (the `bidsReceived != null` false path). |
There was a problem hiding this comment.
Коментарът гласи „admitted is null“, но входът е bidsReceived: null, bidsRejected: 0. admitted не е null — по-скоро bidsReceived != null е false, така че нито един competition клон не се задейства. Предложение: изравнете текста на коментара с реалния клон, който се тества, за да е ясно намерението.
|
|
||
| describe('procedureGroup (edge inputs)', () => { | ||
| it('trims surrounding whitespace before lookup', () => { | ||
| expect(procedureGroup(' Пряко договаряне ').key).toBe('direct'); |
There was a problem hiding this comment.
Коментарът си противоречи: ' '.trim() дава '', което е falsy, а не „truthy“. С типична имплементация (пазач if (!key) return UNKNOWN) този вход попада в falsy-пазача — точно както undefined и null на следващите редове — и НЕ минава през map-miss клона (?? UNKNOWN). Тоест твърдението „takes the map-miss path“ е невярно и map-miss клонът може да остане непокрит. Предложение: добавете вход с непразен, но неизвестен ключ (напр. procedureGroup('няма такава процедура').key), за да покриете реално map-miss клона, и коригирайте коментара.
| const db = { | ||
| prepare() { | ||
| return { | ||
| bind() { |
There was a problem hiding this comment.
Този (и всички останали фалшиви D1 обекти в партидата) дефинира bind() { return this } и изхвърля подадените аргументи. Резултат: тестовете проверяват само SQL текста, но никога реалните стойности/реда на bind-параметрите. Клас грешки (грешно свързана стойност или ред) остава непокрит. Препоръка: фалшивият обект да записва аргументите на bind() и поне един тест да ги утвърждава.
| }); | ||
| }); | ||
|
|
||
| // A SQL-capturing spy over the standard fakeDb rows — for asserting which predicates buildFilters emits. |
There was a problem hiding this comment.
Асерциите тук са плътно свързани с точния SQL низ (напр. substr(c.signed_at, 1, 4) IN, c.amount_eur >= ? AND c.amount_eur < ?). Поведенчески еквивалентен рефактор ще счупи теста без реален дефект. Където е възможно, проверявайте резултатното поведение вместо суровия SQL, за да намалите крехкостта.
| }, | ||
| }; | ||
| }, | ||
| } as unknown as D1Database; |
There was a problem hiding this comment.
listSingleOfferContracts(v.db, 'value', 5) подава лимит 5, но фалшивият обект връща един ред независимо от него, а тестът не утвърждава, че LIMIT/лимитът достига до заявката. Затова toHaveLength(1) е тривиално удовлетворено. Добавете проверка, че лимитът се появява в SQL или се подава чрез bind.
| version: 25.9.1 | ||
| '@vitest/coverage-v8': | ||
| specifier: ^4.1.7 | ||
| version: 4.1.10(vitest@4.1.7) |
There was a problem hiding this comment.
Незадължителна бележка: @vitest/coverage-v8@4.1.10 декларира peer vitest: 4.1.10, но резолвнатата версия на vitest в този lock файл е 4.1.7 (4.1.10(vitest@4.1.7)). Разминаването е малко (patch ниво) и вероятно работи, но ще предизвика peer-dependency warning при pnpm install. Препоръчвам да изравните версиите — напр. да вдигнете vitest до 4.1.10 — за да остане lock файлът консистентен. Не е блокиращо.
| function findTestWorkspaces() { | ||
| const found = []; | ||
| for (const group of ['apps', 'packages']) { | ||
| for (const entry of readdirSync(join(ROOT, group), { withFileTypes: true })) { |
There was a problem hiding this comment.
Незадължително втвърдяване: readdirSync(join(ROOT, group)) ще хвърли ENOENT, ако директорията apps/ или packages/ липсва в репото. Това е единственият изход, който не дава ясното, насочващо съобщение, характерно за останалата част от скрипта. Предложение: if (!existsSync(join(ROOT, group))) continue; преди readdirSync, за да остане поведението fail-safe и съгласувано.
| if (!entry.isDirectory()) continue; | ||
| const pkgPath = join(ROOT, group, entry.name, 'package.json'); | ||
| if (!existsSync(pkgPath)) continue; | ||
| const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')); |
There was a problem hiding this comment.
Дребно: JSON.parse(readFileSync(pkgPath, 'utf8')) не е защитен — един малформиран package.json в произволна работна директория ще срине откриването на работни директории с неясна грешка от JSON.parse вместо с посочване на виновния файл. Обвиване в try/catch с добавяне на пътя към съобщението би било в духа на останалите точни съобщения за грешка тук.
| provider: 'v8', | ||
| // Explicit include: without it the v8 provider only reports files loaded | ||
| // by tests, so a new untested module would be invisible to the ratchet. | ||
| include, |
There was a problem hiding this comment.
Коментарът твърди, че самото include прави нетестван модул видим за ratchet-а. Реално това работи, защото vitest подразбиращо използва coverage.all: true — при all: false включените, но неимпортирани файлове НЕ биха се отчели и новият нетестван модул би бил невидим. Тъй като целият гейт зависи от това поведение, препоръчвам да зададете all: true изрично тук, за да сте устойчиви на бъдеща промяна на подразбиращите се стойности на vitest. Незадължително, но премахва скрита зависимост.
An adversarial mutation audit found six added tests that lit up a branch for the ratchet without asserting its behaviour (each survived deleting the very line it claimed to cover). Strengthened so the assertion fails under the targeted mutation: - db backward pagination (authorities/companies/contracts): pageSize 1 made slice+reverse a no-op; now pageSize 2 over 3 rows and asserts the page comes back in reversed fetch order (fails if rows.reverse() is dropped) - db flows ribbon order: input was pre-sorted so the comparator was unguarded; now feeds unsorted pairs and asserts ranked toName order - web app.harden nonce swap: unobservable under dev PROD=false; now stubs PROD=true and asserts the CSP nonce is replaced by a sha256 hash - web retry backoff: asserts setTimeout was called with [50,150,150], pinning the BACKOFF_MS[i] ?? 150 fallback value Coverage unchanged (db 98.39%, web 96%); same branches, real assertions.
ydimitrof
left a comment
There was a problem hiding this comment.
Обобщен преглед на PR — test: тестово покритие ≥95% за всички workspaces (надгражда #216)
Какво прави PR-ът
Този PR въвежда механизъм за налагане на минимум ≥95% тестово покритие във всички workspaces на monorepo-то. Промените са почти изцяло тестове и конфигурация — без промени по продукционен код:
- CI/инструментариум: нов
coverage-commentjob в.github/workflows/ci.yml(sticky коментар с обобщение на покритието),scripts/check-coverage.mjs+ тестове (ratchet гейт срещуcoverage-baseline.json),vitest.shared.tsс явенinclude, промени вturbo.json(outputs: coverage/**) и.gitignore. - Coverage провайдър: добавен
@vitest/coverage-v8и съответнитеvitest.config.tsзаapps/etl,db,ingest,sharedи др. - Тестове: голям набор нови/разширени
*.test.ts(x)заapps/etl,apps/web,packages/*(query-builder-и, нормализатори, sitemaps, OCDS, staging/refresh, retry, риск-логика, таксономия и т.н.).
Сигурност (Фаза 0) — ЧИСТО във всички партиди
- Няма твърдо кодирани тайни/креденшъли — само тестови фикстури (
BGGPT_API_KEY: 'k',NONCE,https://x.testи подобни). - Няма зловреден код, бекдори, обфускация,
evalили мрежови изтичания. - CI правата са минимални:
contents: readпо подразбиране,pull-requests: writeе ограничено само доcoverage-comment, а fork PR-ите са коректно изключени; GitHub Actions са пинати по commit SHA. check-coverage.mjsе защитен срещу path traversal (WORKSPACE_KEYregex, валидация преди цикъла) и prototype pollution (hasOwnProperty.call, отхвърляне на__proto__) — тестово покрито.- Проверена е и подозрителната транзитивна зависимост
obug@2.1.1— това е легитимен форк наdebug, а не typosquat.
Качество на тестовете — високо
Тестовете са смислени и адверсариални (не „cheater“ тестове): целят конкретни клонове, гранични случаи и реални инварианти (напр. пагинация през CHUNK граници, ?? null разклонения, chunk=100 разделяне при DELETE+INSERT, коректно сумиране covered/total вместо осредняване на проценти, fail-closed поведение при празен/остарял baseline). Ресурсите се почистват коректно (unstubAllGlobals, clearAllMocks, mockRestore). Ratchet-логиката е коректна и не позволява тиха регресия.
Незблокиращи забележки (по желание)
- Supply-chain верификация:
upload-artifact@v7.0.1иdownload-artifact@v8.0.1са с необичайно високи мажорни версии — потвърдете, че пинатите SHA отговарят на официалните тагове. - Peer/версийно разминаване:
@vitest/coverage-v8@4.1.10очакваvitest 4.1.10, а репото фиксира4.1.7(влизат две версии на@vitest/utils) — потвърдете, чеpnpm install --frozen-lockfileминава чисто в CI. - Лимит на PR коментар: sticky коментарът праща цялата таблица; при много workspaces може да надхвърли ~65 KB лимита на GitHub (смекчено с
continue-on-error, но обмислете съкращаване/линк към артефакта). - Детерминизъм: тестове, разчитащи на реалния часовник („defaults today…“, „defaults fetchedAt to now“) и
retry.test.ts(реални таймери ~350ms) биха били по-стабилни сvi.useFakeTimers(). - Козметика:
@vitest/coverage-v8нарушава азбучния ред вpackage.json;findTestWorkspaces()нямаexistsSyncпредпазка за липсващаapps/packagesдиректория; информационният „Total“ ред може леко да занижи при summary безcovered/total(не влияе на самия ратчет).
Блокиращи проблеми
Няма. Сигурността е чиста, а промените са безопасни за merge.
Вердикт
COMMENT — кодът и тестовете са с високо качество и без блокиращи находки. Единственото условие преди пълно APPROVE е потвърждение от CI, че тестовете, check:coverage:test и целият pipeline минават зелени и че реалното покритие ≥95% е достигнато — това не можа да бъде изпълнено в средата на прегледа.
| pnpm check:coverage | ||
| - name: Upload coverage report | ||
| if: ${{ !cancelled() }} | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 |
There was a problem hiding this comment.
Supply-chain проверка: екшънът е закачен на таг v7.0.1 (SHA 043fb46...), което е необичайно висока мажорна версия за actions/upload-artifact (познатите upstream издания са в порядъка v4.x). Моля потвърдете, че пинатият SHA наистина сочи официалния тег, а не подменен/typosquat екшън. SHA-пинът е добра практика — това е само верификация.
| pull-requests: write | ||
| continue-on-error: true | ||
| steps: | ||
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 |
There was a problem hiding this comment.
Същата бележка за actions/download-artifact@... (таг v8.0.1): потвърдете съответствието SHA ↔ официален upstream таг. Иначе job-ът coverage-comment е коректно защитен — има само pull-requests: write, ограничен е до same-repo PR-и и е continue-on-error, така че евентуален проблем с коментара не блокира merge.
| GH_TOKEN: ${{ github.token }} | ||
| GH_REPO: ${{ github.repository }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| run: | |
There was a problem hiding this comment.
-F body=@coverage-summary.md праща цялото съдържание на файла като тяло на коментар. GitHub има лимит ~65 KB за тяло на коментар; при много workspaces рендерираната таблица може да го надхвърли и POST/PATCH да върне 422. Смекчено е чрез continue-on-error: true, но обмислете съкращаване или линк към артефакта, за да не изчезва тихо коментарът.
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20260521.1", | ||
| "@vitest/coverage-v8": "^4.1.7", |
There was a problem hiding this comment.
Незначителна забележка: @vitest/coverage-v8 е поставен преди @types/node, което нарушава азбучното подреждане на devDependencies (@types предхожда @vitest). Ако в проекта е активен sort-package-json, това ще падне на pnpm lint. Препоръка: преместете реда след @types/node.
| // A non-Error thrown value exercises the `: error` log branch, and a 4th attempt indexes past | ||
| // the two-entry BACKOFF_MS table, exercising the `?? 150` fallback. | ||
| const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); | ||
| const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout'); |
There was a problem hiding this comment.
vi.spyOn(globalThis, 'setTimeout') без mockImplementation вика оригиналния таймер, така че тестът реално изчаква 50+150+150≈350ms. Коректно е, но добавя реално забавяне към суита. Обмислете vi.useFakeTimers() + ръчно придвижване на времето за по-бърз и по-детерминиран тест, като пак се проверяват delay стойностите.
…ive ones Adversarial audit pass over the coverage-ratchet suite: each finding was confirmed by breaking the exact production line and watching the test stay green, then fixed and re-verified so the mutation now fails. - etl stagedRows: fixture now sets baseAmendments/ocdsAmendments non-zero so both amendment terms of the sum are guarded (were unexercised). - db details: procedureMix assertions go from .length>0 to the folded DTO shape (contracts/valueEur/sharePct); numeric lot sort now fed out of order with multi-digit labels; consortium lot contractor name asserted through the row kind. - db sitemaps: page math now guards the upper rowid bound (hi), and lastmod precedence proves signed_at wins over published_at when both are present. - db network: centre-picker test asserts the full mapped shape for both the authority and (previously unexercised) company branch. - db trend: sector options asserted through the includeSectors=true path. - db keyset: oversized-cursor test uses a genuinely decodable payload so the length guard is exercised, not the JSON.parse catch; plus a near-limit pass. - db contracts: listSingleOfferContracts asserts the limit reaches LIMIT ?. - web retry: fake timers; ScrollToTop: rAF coalescing counted.
ydimitrof
left a comment
There was a problem hiding this comment.
Ревю на PR — тестово покритие ≥95% за всички workspaces (надгражда #216)
ВЕРДИКТ: COMMENT — няма блокиращи проблеми със сигурността или целостта на данните. Преди merge изравнете версиите на vitest / @vitest/coverage-v8 и потвърдете зелен CI (минаващи тестове + реално потвърдено покритие ≥95%).
Какво прави PR-ът
PR-ът вдига тестовото покритие до ≥95% за всички workspaces и въвежда механизъм за налагане на прага. Промените са почти изцяло тестове (*.test.ts / *.test.tsx / *.mjs) плюс поддържаща инфраструктура:
- Vitest конфигурация за покритие (
vitest.shared.ts, per-workspacevitest.config.ts,packages/config/vitest.config.ts); - coverage ratchet с baseline и толеранс (
coverage-baseline.json,scripts/check-coverage.mjs+ тестове,turbo.json); - CI задание
coverage-commentв.github/workflows/ci.yml; - документация (
docs/review-testing.md); - нова devDependency
@vitest/coverage-v8.
Практически няма промени в продукционен код, което присъщо ограничава риска до качеството на тестовете и коректността на конфигурацията.
Сигурност (Фаза 0 — скан по всички партиди): ЧИСТО
- Няма твърдо кодирани тайни — стойности като
BGGPT_API_KEY: 'k', ЕИК-ове и имена на фирми са очевидно фиктивни тестови данни. - URL адреси — само тестови фикстури или вече одобрени домейни; тестовете дори утвърждават allowlist-а (отхвърляне на пренасочване към
evil.example). - Няма злонамерен код — backdoor, инжекция, обфускация,
evalили динамично изпълнение. - Зависимости — нови са само
@vitest/coverage-v8(легитимен официален пакет) и транзитивнатаobug@2.1.1(проверена — легитимен форк наdebug, не typosquat). GitHub Action-ите са закачени към конкретни commit SHA. - CI (
coverage-comment) — коректно защитено: използваpull_request(неpull_request_target), ограничено е до PR от същото repo, не прави checkout на неоторизиран код, подаваPR_NUMBER/GH_REPOпрезenv:(без script injection) и еcontinue-on-error. Съответства на принципа за най-малки привилегии. - SQL / OWASP — тестовете не изпълняват реален SQL; продукционните заявки са параметризирани (
?+.bind()), аkeyset/assertReadOnlySelectизрично отхвърлят неразрешени sortCol/dir (потвърдено с тестwon_eur; DROP TABLE contracts). Има и изрична защита срещу prototype pollution (normalizeAuthoritySort('toString')→ стойност по подразбиране).
Качество на тестовете: високо
Тестовете са смислени и целенасочено покриват клонове и грешкови пътища, а не тривиални минавания: гранични и грешкови случаи (non-array payload, invalid JSON, HTTP 403/404/500, деление на нула, YoY срещу нулева година), rAF coalescing и abort при multipart R2 неуспех, CSV детайли (BOM, keyset chunking), пагинация с разкриващи дефекти курсори, taxonomy integrity карти. Изчистването на ресурси е добро (възстановяване на глобали, fake timers, spy-ове). check-coverage.mjs е добре разделен на чисти функции + main(), с fail-closed валидация и строг whitelist срещу path traversal.
Основни забележки (незадължителни за блокиране)
- Несъответствие на peer dependency —
@vitest/coverage-v8@4.1.10изискваvitest@4.1.10, но проектът остава на4.1.7. Изравнете версиите (закачете coverage-v8 на4.1.7или вдигнете vitest на4.1.10), за да избегнете фини несъвместимости в инструментацията на покритието. - Кеширане в
turbo.json—"outputs": ["coverage/**"]може да върне cache hit безcoverage/директория, акоtestбъде пуснат веднъж без--coverage, което ще сринеcheck-coverage.mjs. Уверете се, че CI винаги подава--coverageпоследователно. - Отслабена инвариантна проверка — в
apps/etl/src/eop.test.tsновиятfakeDbFromFreshnessвече игнорира SQL-а и винаги връща freshness реда; старата версия хвърляше грешка при четене наraw_contractsи така пазеше инварианта, че планировчикът чете обслужената freshness, а не изтекло raw staging. Тази негативна проверка за целост на данните вече не се налага. - Прагът ≥95% трябва да е реално наложен — той се дефинира в
vitest.shared.ts; потвърдете, че конфигурацията реално fail-ва при под прага, иначе целта „≥95%“ не е гарантирана.
Дребни бележки
- Крехки тестове, обвързани с точни SQL фрагменти и
sql.includes(...)в fake D1 — работят коректно, но са чувствителни към бъдещ рефакторинг на заявките (риск от тихо минаване по грешен клон). - Фиктивният D1
bind()връщаthisи игнорира аргументите — тестовете доказват текста на SQL, но не и че потребителският вход достигаbind(...)като параметър; препоръчва се поне един такъв тест. getContractFacetsразчита на реалния часовник — инжектиране на фиксирана дата би било по-издръжливо.package.json:@vitest/coverage-v8нарушава азбучната подредба наdevDependencies.- Правопис: „28 областти“ → „28 области“ (
packages/config/src/index.test.ts). mergedTotalвръщаpct: 100приtotal === 0(козметично).
Заключение
Чист, добре структуриран PR, изцяло насочен към тестово покритие, без проблеми със сигурността или целостта на данните. Одобрението зависи от зелен CI (минаващи тестове + потвърдено покритие ≥95%), който не може да се провери на ниво диф, и от изравняване на версиите vitest / @vitest/coverage-v8.
| vi.unstubAllGlobals(); | ||
| }); | ||
|
|
||
| function fakeDbFromFreshness(maxLoadedDate: string | null): D1Database { |
There was a problem hiding this comment.
Новият fakeDbFromFreshness игнорира подадения SQL и винаги връща реда за freshness. Предишната версия хвърляше грешка при sql.includes('raw_contracts') (raw staging should not be read for planning), с което тестът налагаше инварианта, че планировчикът чете обслужената freshness, а не изтекло raw staging. Сега тестът „plans an uncapped window straight from served freshness“ ще мине дори ако продукционният код регресира и започне да чете raw_contracts. Предложение: запазете негативната проверка (хвърляне при четене на raw staging) в мока, за да не се губи тази проверка за целост на данните.
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20260521.1", | ||
| "@vitest/coverage-v8": "^4.1.7", |
There was a problem hiding this comment.
Дребна бележка (не блокираща): @vitest/coverage-v8 е добавен преди @types/node, което нарушава азбучната подредба на devDependencies (@types/* следва да е преди @vitest/*). За консистентност с останалите записи преместете реда след @types/node.
| resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==} | ||
| peerDependencies: | ||
| '@vitest/browser': 4.1.10 | ||
| vitest: 4.1.10 |
There was a problem hiding this comment.
Несъответствие в peer dependency: @vitest/coverage-v8@4.1.10 изисква peer vitest@4.1.10 (виж реда тук), но проектът остава на vitest@4.1.7 — lockfile-ът я резолвва като 4.1.10(vitest@4.1.7). Тази комбинация не е официално поддържана и може да доведе до фини несъвместимости в инструментацията на покритието.
Препоръка: изравнете версиите — закачете @vitest/coverage-v8 на 4.1.7 (тази версия съществува и peer-ът ѝ е точно vitest@4.1.7), или вдигнете vitest на 4.1.10.
| "test": { | ||
| "dependsOn": ["^build"] | ||
| "dependsOn": ["^build"], | ||
| "outputs": ["coverage/**"] |
There was a problem hiding this comment.
Кеширането на coverage/** като изход на test таска е рисково: ако test бъде изпълнен веднъж без --coverage, turbo кешира резултат без директория coverage/. Последващ pnpm test -- --coverage може да е cache hit и да НЕ генерира отчет, при което scripts/check-coverage.mjs пада с „No coverage report for …“.
Уверете се, че CI винаги подава --coverage последователно, или че cache ключът различава наличието на флага (напр. отделен test:coverage таск). Иначе ще имате спорадични CI провали или, по-лошо, тихо пропускане на ratchet проверката при частичен кеш.
The 'defaults today' test only asserted the plan window was date-shaped
(/^\d{4}-\d{2}-\d{2}$/), which a mutation to any hard-coded ISO string would
survive. Pin the system clock and assert plan.to equals that exact date, so
the new Date() default is actually verified. Addresses the reviewer's
determinism note on real-clock reliance.
|
Благодаря за прегледа. CI вече е зелен (условието за APPROVE). По бележките: Отстранено (
Към #216 (вендорнатият harness, не този клон) — уреждат се при rebase след merge на #216:
Оставащо, несблокиращо:
|
A prior commit on this branch reshaped fakeDbFromFreshness so prepare() ignores the SQL and always returns the freshness row, dropping the guard that threw when raw staging was read for planning. Restore it as the stronger positive invariant: the catch-up planner must read data_freshness, never raw_*, so a regression that reads raw staging for the max-loaded date fails loudly. Also fix a BG typo in the config test name (областти -> области).
ydimitrof
left a comment
There was a problem hiding this comment.
Обобщено ревю на PR — тестово покритие ≥95% за всички workspaces (надгражда #216)
Какво прави PR-ът
PR-ът добавя тестово покритие с цел ≥95% за всички workspaces в монорепото, заедно със съпътстваща CI и конфигурация за покритие. Промените са изцяло тестови/CI/инструментариум — няма промени в продукционен или рънтайм код. Обхватът включва:
- Тестови файлове в
apps/etl,apps/web,packages/config,packages/dbи др. - CI pipeline (
.github/workflows/ci.yml),.gitignore,turbo.json - Конфигурация за покритие:
vitest.shared.ts,vitest.config.ts,coverage-baseline.json,tsconfig.node.json,package.json - Скрипт
scripts/check-coverage.mjs(ratchet спрямо baseline) и неговите тестове - Нова dev-зависимост
@vitest/coverage-v8иpnpm-lock.yaml
Сигурност и цялост на данните — ЧИСТО ✅
- Няма hardcode-нати тайни, backdoor-и, инжекции, обфускация или
eval. Стойности катоBGGPT_API_KEY: 'k',NONCE = 'abc123nonce','sha256-abc'са тестови фикстури. Хостовете (https://x/,https://gw.example/v1и т.н.) са фалшиви/тестови;evil.exampleприсъства само в позитивен тест, който проверява блокиране на пренасочване към чужд хост. - GitHub Actions: добра практика —
permissions: contents: readпо подразбиране;pull-requests: writeе стеснено само до job-аcoverage-commentи до PR-и от същото repo (fork PR-и не получават write токен). Actions са пиннати по SHA. - Зависимости: транзитивното дърво на
@vitest/coverage-v8@4.1.10е сверено точно спрямо npm registry. Съмнителният на пръв погледobugе потвърден като легитимен форк наdebugи официална зависимост, не typosquat. - Тестовете затвърждават добри защити:
sql-guard/run_sqlотхвърлят опасни заявки и не изтичат сурови D1 грешки към потребителя;render-formatблокира инжекция вentityHref;WORKSPACE_KEYregex отхвърля path traversal и__proto__ключове.
Качество на тестовете
Тестовете са смислени и адверсариални, не „cheater“ — целят конкретни клонове и гранични случаи (rAF coalescing, capped/zero-ingest, finally-drop при грешка, backward pagination, prototype-ключове, taxonomy integrity, знак U+2212, нефинитни стойности). Добро покритие на branch-овете и чисто разделяне на чисти функции от I/O логиката.
Незблокиращи забележки за адресиране преди merge
- Съвместимост на версиите на vitest (най-важна).
@vitest/coverage-v8@4.1.10декларира точен peervitest 4.1.10, а проектът е наvitest@4.1.7. Неудовлетворен peer може да предизвика фини несъвместимости и/или--coverageда гръмне в CI. Препоръка: вдигнетеvitestна4.1.10. - Catch-22 в
scripts/check-coverage.mjs --update.validateBaseline()се изпълнява безусловно преди клонаif (update), затова нов workspace сtestскрипт води доexit(1), преди--updateизобщо да може да го bootstrap-не — въпреки че съобщението съветва точно тази команда. Пропуснете проверката „missing entry“ в режим--update. - Сверете SHA на
upload-artifact/download-artifactс коментираните версии (# v7.0.1/# v8.0.1). - tsconfig обхват: уверете се, че
tsc -bне връща „file is not under rootDir“ за../../vitest.shared.tsприcomposite: true(и чеtsconfigнаpackages/config/packages/dbвключват shared файла). @vitest/coverage-v8вpackage.jsonнарушава азбучния ред наdevDependencies(риск при sort-lint).turbo.jsonкешираcoverage/**за таскtest. Cache hit при пускане без--coverageможе да възстанови празна папка и да провалиcheck-coverage.mjs(fail-closed, но объркващо). Уверете се, че CI винаги пуска с--coverage.- Заглавие ↔ механизъм: „≥95%“ реално е ratchet спрямо baseline с толеранс, а не абсолютен праг. Потвърдете, че стойностите в
coverage-baseline.jsonдействително са ≥95, иначе ratchet-ът заключва по-ниско ниво. - Интеграционна увереност:
apps/etlтестовете мокват изцяло@sigma/ingestи./eop, което е коректно за unit-ниво, но покритие ≥95% при широко мокване може да надценява реалната интеграционна увереност — потвърдете, че отделен integration слой покрива реалните staging writer-и.
Присъда
COMMENT — Няма блокиращи проблеми със сигурността или целостта на данните. Преди APPROVE адресирайте най-вече несъответствието на версията на vitest (т.1) и catch-22 при --update (т.2), както и потвърдете локално, че тестовете преминават и покритието отговаря на прага.
| pnpm check:coverage | ||
| - name: Upload coverage report | ||
| if: ${{ !cancelled() }} | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 |
There was a problem hiding this comment.
Supply-chain проверка: action-ът е пиннат по SHA (добре), но коментарът # v7.0.1 за actions/upload-artifact (и # v8.0.1 за download-artifact по-долу) не съответства на познатите официални мажорни версии. Моля, потвърдете, че закаченият SHA 043fb46d... сочи именно към официалния таг преди merge — подвеждащ коментар за версия върху пиннат SHA е класически вектор за компрометиране на pipeline.
| pull-requests: write | ||
| continue-on-error: true | ||
| steps: | ||
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 |
There was a problem hiding this comment.
coverage-comment job-ът е добре ограден (само PR-и от същото repo, pull-requests: write само тук, continue-on-error). Тялото на коментара идва от coverage-summary.md в артефакта, рендериран от scripts/check-coverage.mjs (извън тази партида). Тъй като съдържанието се публикува дословно като PR коментар, моля потвърдете, че генераторът екранира/санитизира стойности от coverage JSON, за да не може име на workspace/път да инжектира Markdown/HTML в коментара.
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20260521.1", | ||
| "@vitest/coverage-v8": "^4.1.7", |
There was a problem hiding this comment.
@vitest/coverage-v8 е фиксиран на ^4.1.7. Този пакет трябва да съвпада по major версия с vitest — ако vitest не е 4.x, --coverage ще се провали в CI. Моля потвърдете съответствието. Дребна бележка: записът е вмъкнат преди @types/node, което нарушава азбучния ред на devDependencies (риск при sort-lint/prettier-plugin-packagejson).
midt-bg#216 (coverage harness) merged to main, so the vendored harness reconciles to the canonical one. Also folds in the feature work that landed since the branch base — midt-bg#263 (worker-native FX load, rewritten refresh Workflow), midt-bg#252 (Bulstat EIK control code), midt-bg#210 (similar-contracts cohort) — and re-validates the whole suite against the moved source. Conflict resolutions: - apps/etl/vitest.config.ts: take upstream (needs the SQL text-module plugin + cloudflare:workers alias for midt-bg#263's real-Workflow index.ts). - vitest.shared.ts: keep our fixtures/json/md/d.ts excludes; add **/src/test/** (SQLite/workers stubs are test scaffolding, not product code). - coverage-baseline.json: keep our floors (baseline reconciliation to the merged tree's actuals follows in a separate commit). - apps/etl/src/index.test.ts: take upstream's FX integration test; restore the orchestration coverage it does not cover in a new mock-based control-flow test. - packages/db/src/queries/home.test.ts: union both added fakeDb params (singleOffer + capture). Post-merge fixes for source drift: - search.suggest.test.tsx: stub getDb (the route now wraps env in getDb()). - index.control-flow.test.ts (new): capped-window, zero-ingest, FX-uncovered, integrity-gate logger + failure (Error and non-Error), scheduled — restoring the run()/scheduled() branch coverage displaced by taking upstream's test. Full suite green: config 27, shared 56, ingest 116, etl 40, db 458, web 469.
… to merged-tree actual Restore ingest to its line floor and hold branches after the merge: - fx.ts: a malformed (unpadded) staged contract_date surfaces through findFxCoverageGaps' MIN/MAX, so loadFxRates must reject the range before fetching — asserted (skips the currency, warns, never fetches). - ocds.ts: a release with no bids block nulls bids_received via the optional chain instead of throwing. Reconcile apps/etl branches 98.5 -> 97 (achieved 97.46). etl LINES hold at 100. The two uncovered branches are provably unreachable: eop.ts parseBucketKeys `m[1] ?? ''` (a matched regex group is never undefined — the `??` is required only by noUncheckedIndexedAccess) and integrity.ts `summary.message ?? '...'` (dead — summarizeIntegrity guarantees a non-null message on the throw path). 97 is still far above the harness's original etl branch floor (58.2); covering the branches would require a fake or a source edit, both disallowed. check:coverage passes for all six workspaces.
…ersons) midt-bg#226 (свързани лица — the conflict-of-interest data foundation) landed on main, adding a large new surface: packages/db related-persons queries, the /conflicts routes + ConflictCards, the apps/web conflicts lib and rate limiter, shared company-name-key, and the scripts/cacbg pipeline. The merge itself was clean — no conflicts; both overlapping test files (identity.test.ts, search.test.ts) auto-merged. The coverage ratchet did fail, though: midt-bg#226 ships its own tests but to a lower bar than this branch's floors, dropping apps/web to 98.86% lines / 93.01% branches (floors 99.4/96) and packages/db branches to 97.77% (floor 98.3). Per the never-lower rule this is fixed with tests — NO baseline floor is touched. Every case below targets a genuinely reachable branch; nothing is faked and no source is edited to suit a test. - ui.test.tsx (new): the shared editorial primitives had no direct test, only incidental page coverage of whichever variant that page used. Covers every optional-prop branch — Chip tones, ЕИК URL-encoding + rel=noopener, the OwnershipChip null/three-kind split, Flag variants, ShareBar clamping outside 0–1, Callout h3-default vs explicit h2 (heading order) and its variant class, Section with and without a hint. - conflicts.render.test.tsx: an undated + unnumbered + authority-less contract (no timeline, „—" body, generic „договор" label, never „№ null"), a sub-threshold „под 0,1%" share with no plotted bar, a no-denominator „—" share, an empty drill-down, and an all-outside-the-window contract set. - conflicts.test.ts: unknown temporal tag falls back to the raw value, year-0 / non-numeric signing dates are refused by parseYear, and authority-share ordering with unknown denominators (plottable first, money breaks a tie). - conflict.pages.render.test.tsx: meta() on both conflict pages with the loader data absent — the error-boundary render must degrade to the generic noun, not interpolate "undefined" into a public page title. - conflicts.loaders.test.ts: route params absent entirely, not merely blank. - conflicts-rate-limit.test.ts: non-GET/HEAD methods bypass the budget (it guards read scraping, and a write is not that vector). - related-persons.test.ts: a NULL joined authority maps to '' so the card never renders a literal null, plus isMissingConflictTableError against a non-Error rejection and against a core (non-0003) missing table. Gate green with no floor lowered: web 99.16/95.84, db 99.82/97.96, etl 100/97.46, ingest 100/98.36, config 100/100, shared 98.87/98.33. Full suite: config 27, shared 65, ingest 119, etl 40, db 505, web 592.
…acbg gate midt-bg#281) Two commits landed on main while the midt-bg#226 sync was in flight — an undici override bump to ^7.29.0 and a cacbg completeness-gate fix (phantom rows / --limit). Clean merge; the surface is pnpm-workspace.yaml + scripts/cacbg, none of it inside the six measured workspaces, so the coverage baseline is untouched and the gate stays green. Verified against the merged tree: scripts tests 51 pass, cacbg pipeline tests 84 pass (run via the register-ts loader, as the CI job does), coverage ratchet green for all six workspaces.
|
@ydimitrof @todorkolev PR-ът е синхронизиран с текущия main и CI е зелен — готов за повторен преглед. Състояние: merge-нат е целият main до момента — #216 (coverage harness), #263 (FX в cron), #252 (Булстат ЕИК), #210 (сходни договори), #226 (свързани лица), #281/#282. Клонът е на 0 зад upstream. И двата workflow-а минават: Какво се промени след последния преглед на @ydimitrof:
Единственото сваляне на праг в целия PR: Финално: gate зелен за всичките 6 workspace-а, 1348 workspace теста + 51 scripts + 84 cacbg, typecheck 7/7. Merge редът вече е без значение — #216 е merge-нат, PR-ът е върху него. |
|
Този клон е в конфликт с |
Надгражда coverage харнеса от #216 с реалните тестове и вдига ratchet прага на ≥96% клонове и ≥98.8% редове за всеки workspace (повечето на 100%). Само тестове — нулева промяна в продуктов код.
Този PR трябва да се слее след #216 (
ydimitrof:feat/coverage-ratchet), който все още е отворен.#216 въвежда самия харнес —
scripts/check-coverage.mjs+check-coverage.test.mjs, споделенияvitest.shared.ts, per-workspacevitest.config.ts,coverage-baseline.json(с началните, ниски прагове) и CI стъпката. Тук този харнес е vendor-нат едно към едно (двата скрипта са байтово идентични с #216), за да могат тестовете да се пишат и пускат срещу реалната порта преди #216 да е слят.Слят преди #216, този клон би въвел харнеса, CI стъпката и конфигурациите за втори път → конфликт.
След като #216 се слее, клонът се rebase-ва върху
main; vendor-натият commit (69d0a34) се свива (идентично съдържание), а нетното участие остава:vitest.shared.ts: изключване на**/fixtures/**,**/*.json,**/*.md,**/*.d.tsот инструментирането (данни/типове/докове, не изпълним код — иначе висят на постоянни 0% и v8 remap-ът чупи на markdown);coverage-baseline.json: праговете вдигнати от началните на test: измерване на coverage и ratchet праг в CI (#93) #216 до текущите (виж таблицата).Резултат
Общо (информативно): 99.73% редове / 97.51% клонове.
node scripts/check-coverage.mjsминава; собственият му self-test (check-coverage.test.mjs) също. CI е зелено (typecheck, lint, тестове).Бележки
??fallback-и върху regex групи, които винаги съвпадат;if(done)re-entry на stream (stream не дърпа следclose());split().pop() ??(pop винаги връща стойност); homoglyph картата (всеки съвпаднал символ е мапнат);minContracts ?? DEFAULT, което оркестраторът вече нормализира; env-gatedPRODпътища под vitest. Оставени са непокрити, а не заобиколени чрез отслабване на кода илиv8 ignoreпрагми.Свързани: #216 (харнес), #93 (епик за покритие).