Close test-coverage gaps and enforce a coverage threshold #62

Closed
opened 2026-06-28 15:13:25 +02:00 by myrmidex · 2 comments
Owner

Goal

Close the identified unit-coverage gaps (back + front) and add a coverage threshold to lock the line, so coverage can't silently regress as the app grows. Framed as "close real gaps + enforce a floor" rather than a blunt "100%" — the point is meaningful coverage of real logic, not line-hitting framework boilerplate.

Backend gaps (PHPUnit, from dev-coverage)

The validator family has its validate() exercised only via functional/integration paths, so isolated-unit coverage shows 0–50% methods. Add direct unit tests (mirroring UniquePriorityPerScenarioValidatorTest, which is the 100% reference) covering each validator's full logic matrix:

  • App\Validator\BufferOnlyForFixedLimitValidator — 0% methods / 81.82% lines
  • App\Validator\CompatibleAllocationTypeValidator — 0% methods / 81.82% lines
  • App\Validator\SingleOverflowPerScenarioValidator — 50% methods / 87.5% lines (its UnexpectedTypeException / non-Bucket-ignored branches aren't unit-hit)
  • App\Doctrine\AbstractOwnerScopeExtension — 75% methods / 92.86% lines (one uncovered branch — identify and cover or document why it's unreachable)

Boilerplate to EXCLUDE (not worth testing):

  • App\Repository\UserRepository::upgradePassword — Symfony maker-generated PasswordUpgraderInterface impl; framework boilerplate, no app logic. Either add a coverage @codeCoverageIgnore (or phpunit <exclude>) so the report is honest, or document the exclusion. Do NOT write a contrived test for it.

Frontend gaps (Vitest)

The frontend has no coverage gate yet (test harness landed in #52). Establish one:

  • Add a coverage config to Vitest (@vitest/coverage-v8), a test:coverage script, and a dev-fe-coverage shell.nix helper.
  • Review the report; cover any real gaps in src/ logic (components, the api client, auth provider/guards) that the existing tests miss.
  • Reasonable exclusions: pure presentational placeholders, main.tsx (the mount point), generated/config files.

Threshold enforcement

  • Backend: a phpunit coverage <report> min threshold (or a CI step that fails under the floor).
  • Frontend: Vitest coverage.thresholds in config.
  • Pick a floor that reflects the post-cleanup reality (likely high-90s lines) rather than a round 100 — the goal is "no silent regression," not gaming the number. Document the chosen floor + any exclusions.

Notes

  • This pairs with CI (#35) — the threshold checks belong in the pipeline. Coordinate so the coverage gate runs there.
  • Reference: App\Validator\UniquePriorityPerScenarioValidator + its test are the model for the validator unit tests (constraint-type guard, non-entity ignored, repo-count delegation, violation-at-path).
## Goal Close the identified unit-coverage gaps (back + front) and add a coverage **threshold** to lock the line, so coverage can't silently regress as the app grows. Framed as "close real gaps + enforce a floor" rather than a blunt "100%" — the point is meaningful coverage of real logic, not line-hitting framework boilerplate. ## Backend gaps (PHPUnit, from `dev-coverage`) The validator family has its `validate()` exercised only via functional/integration paths, so isolated-unit coverage shows 0–50% methods. Add direct unit tests (mirroring `UniquePriorityPerScenarioValidatorTest`, which is the 100% reference) covering each validator's full logic matrix: - `App\Validator\BufferOnlyForFixedLimitValidator` — 0% methods / 81.82% lines - `App\Validator\CompatibleAllocationTypeValidator` — 0% methods / 81.82% lines - `App\Validator\SingleOverflowPerScenarioValidator` — 50% methods / 87.5% lines (its `UnexpectedTypeException` / non-Bucket-ignored branches aren't unit-hit) - `App\Doctrine\AbstractOwnerScopeExtension` — 75% methods / 92.86% lines (one uncovered branch — identify and cover or document why it's unreachable) **Boilerplate to EXCLUDE (not worth testing):** - `App\Repository\UserRepository::upgradePassword` — Symfony maker-generated `PasswordUpgraderInterface` impl; framework boilerplate, no app logic. Either add a coverage `@codeCoverageIgnore` (or phpunit `<exclude>`) so the report is honest, or document the exclusion. Do NOT write a contrived test for it. ## Frontend gaps (Vitest) The frontend has no coverage gate yet (test harness landed in #52). Establish one: - Add a coverage config to Vitest (`@vitest/coverage-v8`), a `test:coverage` script, and a `dev-fe-coverage` shell.nix helper. - Review the report; cover any real gaps in `src/` logic (components, the api client, auth provider/guards) that the existing tests miss. - Reasonable exclusions: pure presentational placeholders, `main.tsx` (the mount point), generated/config files. ## Threshold enforcement - Backend: a phpunit coverage `<report>` min threshold (or a CI step that fails under the floor). - Frontend: Vitest `coverage.thresholds` in config. - Pick a floor that reflects the post-cleanup reality (likely high-90s lines) rather than a round 100 — the goal is "no silent regression," not gaming the number. Document the chosen floor + any exclusions. ## Notes - This pairs with CI (#35) — the threshold checks belong in the pipeline. Coordinate so the coverage gate runs there. - Reference: `App\Validator\UniquePriorityPerScenarioValidator` + its test are the model for the validator unit tests (constraint-type guard, non-entity ignored, repo-count delegation, violation-at-path).
myrmidex added this to the v0.3.0 milestone 2026-06-28 15:13:25 +02:00
myrmidex added the
enhancement
label 2026-06-28 15:13:25 +02:00
myrmidex self-assigned this 2026-06-28 15:13:25 +02:00
Author
Owner

Add: enable TypeScript strict mode (frontend)

Discovered during #52 review: the frontend tsconfig does NOT set "strict": true — only erasableSyntaxOnly, noUnusedLocals, noUnusedParameters, noFallthroughCasesInSwitch. So as casts and implicit-any have no safety net (e.g. a as FieldErrors cast in the register form is unchecked by the compiler).

As part of the quality-hardening here:

  • Enable "strict": true in frontend/tsconfig.app.json (and node config as appropriate).
  • Fix whatever it surfaces (likely a batch — as casts, possibly-undefined access, implicit anys).
  • Pairs with the coverage threshold + CI (#35): strict typecheck belongs in the pipeline gate too.

Isolated here rather than enabling mid-#52 (it could surface many errors at once).

## Add: enable TypeScript `strict` mode (frontend) Discovered during #52 review: the frontend `tsconfig` does NOT set `"strict": true` — only `erasableSyntaxOnly`, `noUnusedLocals`, `noUnusedParameters`, `noFallthroughCasesInSwitch`. So `as` casts and implicit-any have no safety net (e.g. a `as FieldErrors` cast in the register form is unchecked by the compiler). As part of the quality-hardening here: - Enable `"strict": true` in `frontend/tsconfig.app.json` (and node config as appropriate). - Fix whatever it surfaces (likely a batch — `as` casts, possibly-undefined access, implicit anys). - Pairs with the coverage threshold + CI (#35): strict typecheck belongs in the pipeline gate too. Isolated here rather than enabling mid-#52 (it could surface many errors at once).
Author
Owner

Resolved on release/v0.3.0 (commit 196cbfb). code-reviewer: clean, no blockers.

Coverage — backend now 100% real, frontend ~98.5%

  • The cited "0% methods" was a measurement artifact (constructor-less validators); the real gap was 2 guard lines per validator (wrong-constraint-type + non-Bucket early-return). Covered by mirroring UniquePriorityPerScenarioValidatorTest.
  • AbstractOwnerScopeExtension anon-branch covered via a new tests/Doctrine/ScenarioOwnerExtensionTest.php (tests the base through its simplest concrete subclass + a Security stub).
  • Two reflection-called framework-glue methods @codeCoverageIgnore'd rather than contrived-tested: UserRepository::upgradePassword and Kernel::getAllowedEnvs (the latter was the stubborn 99.74% holdout — already PHPStan-ignored for the same reflection reason). Backend is now an honest 100% (Classes/Methods/Lines 32/32, 124/124, 380/380).
  • Frontend: @vitest/coverage-v8 set up; covered api.ts (get/post/patch/delete + path guard), useAuth out-of-provider throw, AuthProvider non-401 probe branch, Login/Register generic-error fallback, DigitalProgressBar zones → 98.58% stmts / 100% lines.

TS strict — enabled

strict: true + noUncheckedIndexedAccess on both tsconfigs (done now while the code is minimal — blast radius was just 2 errors in DigitalProgressBar.getZone, fixed with a typed non-optional fallback). strict-null core surfaced zero issues.

Threshold enforcement

  • Frontend: coverage.thresholds in vite.config (statements/functions/lines 95, branches 90).
  • Backend: dev-coverage-check shell helper (PHPUnit 13 has no native fail-under-N%, so it parses the Lines % against a 95 floor).
  • Floors set below current deliberately — catch regressions, don't game 100. dev-check-all enforces both locally.
  • New helpers: dev-fe-coverage, dev-coverage-check. coverage/ git+prettier-ignored.

CI wiring of these gates → tracked on #35.

Resolved on `release/v0.3.0` (commit `196cbfb`). code-reviewer: clean, no blockers. ## Coverage — backend now 100% real, frontend ~98.5% - **The cited "0% methods" was a measurement artifact** (constructor-less validators); the real gap was 2 guard lines per validator (wrong-constraint-type + non-Bucket early-return). Covered by mirroring `UniquePriorityPerScenarioValidatorTest`. - `AbstractOwnerScopeExtension` anon-branch covered via a new `tests/Doctrine/ScenarioOwnerExtensionTest.php` (tests the base through its simplest concrete subclass + a Security stub). - **Two reflection-called framework-glue methods `@codeCoverageIgnore`'d** rather than contrived-tested: `UserRepository::upgradePassword` and `Kernel::getAllowedEnvs` (the latter was the stubborn 99.74% holdout — already PHPStan-ignored for the same reflection reason). Backend is now an honest **100%** (Classes/Methods/Lines 32/32, 124/124, 380/380). - Frontend: `@vitest/coverage-v8` set up; covered api.ts (get/post/patch/delete + path guard), useAuth out-of-provider throw, AuthProvider non-401 probe branch, Login/Register generic-error fallback, DigitalProgressBar zones → **98.58% stmts / 100% lines**. ## TS strict — enabled `strict: true` + `noUncheckedIndexedAccess` on both tsconfigs (done now while the code is minimal — blast radius was just 2 errors in `DigitalProgressBar.getZone`, fixed with a typed non-optional fallback). strict-null core surfaced zero issues. ## Threshold enforcement - Frontend: `coverage.thresholds` in vite.config (statements/functions/lines 95, branches 90). - Backend: `dev-coverage-check` shell helper (PHPUnit 13 has no native fail-under-N%, so it parses the Lines % against a 95 floor). - Floors set *below* current deliberately — catch regressions, don't game 100. `dev-check-all` enforces both locally. - New helpers: `dev-fe-coverage`, `dev-coverage-check`. `coverage/` git+prettier-ignored. **CI wiring of these gates → tracked on #35.**
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lvl0/buckets#62
No description provided.